Substrate学习(三)Runtime存储设计

177 阅读3分钟

大纲

  • 区块链存储和传统互联网存储的不同点和约束
  • substrate提供的存储单元的数据类型支持的有哪些
  • 如何使用substrate的存储单元进行一个创始区块的初始化。
  • substrate存储设计的最佳实践

区块链存储的不同点

区块链存储有以下几个特点

  • 代码是完全开源的,所有人都可以去审查,并且由对等网络组成的去中心的网络
  • 通过引入延迟和随机在不同点之间达成共识
  • 在链上增量的存储数据

区块链应用程序,通常使用高效的键值对数据库

  • 比特币 使用的level DB
  • 以太坊和substrate使用的rocks DB

区块链存储的限制

  • 大文件存储的代价极大
  • 不方便索引历史数据
  • Runtime中不支持浮点数(运算结果与编译器和CPU的架构有直接关系)

Substrate支持的存储类型

  • 单值类型 StorageValue
  • 简单映射类型 StorateMap
  • 双键映射类型 StorateDoubleMap
  • 多键值映射类型 StorateNMap
   #[pallet::storage]
    pub type Something<T> = StorageValue<_, u32>;

StorageValue 单值类型

支持的类型

  • integer: u8, 18, u32,132, u64,164, u128, i128
  • large integer: U256, U512
  • boolean: bool
  • collection: Bounded Vec, BTreeMap, BTreeSet
  • fixed point number: Percent, Permill, Perbill, FixedU128
  • fixed-size hash type: H160, H256, H512
  • complex types: Option<T>, tuple, enum, struct
  • build-in custom types: Moment, Accountld

单值常用api

  • put(number);
  • get();
  • mutate(|v|v+1);
  • kill();

数值常用的安全检查

对于存储的数值类型,在区块链应用开发的时候要使用一些安全性的操作避免一些溢出性的错误

  • 返回一些none类型的一些安全操作。
    • checked_add()
    • checked_sub()
    • checked_mul()
    • chekced_sub()
  • 溢出时返回饱和值
    • saturating_add(100000)

存储bool

默认是一个option的query,对于value query,默认值是false

#[pallet::storage]
#[pallet::getter(fn my_bool)]
pub type MyBool<T> = StorageValue<_, bool>;

存储集合

substrate里,不能直接使用rust内置集合Vector。需要使用自定义的BoundedVec集合类型。

#[pallet::storage]
#[pallet::getter(fn my_string)]
pub type MyString<T> = StorageValues_, BoundedVec<u8, ConstU32<100>>, ValueQuery>;

Percent,permill,Perbill 方法

  • 构造函数
    • Permill::from_percent(value)
    • Permill::from_parts(value)
    • Permill::from_rational(p,q)
  • 计算方法
    • permill_one.saturating_mul(permill_two)
    • my_permill * 20000 as u32

Moment 时间

pub trait Config:pallet_timestamp::Config + frame_system::Config{
    //
}

#[pallet::storage]
#[pallet:getter(fn my_time)
pub type MyTime<T: Config> = StroateValue<_,T::Moment>;

AccountId

系统模块提供的AccountId。一般是用户私钥对应的公钥信息。

#[pallet::storage]
#[pallet::getter(fn my_account_id)]
pub type MyAccountId<T: Config> = StorageValue<_, T::AccountId>;

struct

#[derive(Clone,Encode,Decode,Eq,PartialEq,RuntimeDebug,Default)]
pub struct People {
name: Vec<u8>,
age: u8,
}

#[pallet::storage]
#[pallet::getter(fn my_struct)]
9 pub type MyStruct<T: Config> StorageValue<_, People>;

Enum 类型

多值类型

StorateMap

  • 存储键值对
  • StorageValue 可以当key,也可以当value
   #[pallet::storage]
   #[pallet::getter(fn my_map)]
   pub type MyMap<T> = StorageMap<_, Blake2_128Concat, u8, T::Hash, ValueQuery>;

常用方法

  • 插入元素
    • MyMap::<T>::insert(key, value);
  • 通过key获取value
    • MyMap::<T>::get(key);
  • 删除元素
    • MyMap::<T>::remove(key);
  • 更新元素
    • MyMap::<T>::insert(key, new_value);
    • MyMap::<T>::mutate(key, lold_valuel old_value+1);

StorageDoubleMap

#[pallet::storage]
   #[pallet::getter(fn my_map)]
   pub type MyMap<T> = StorageDoubleMap<_, Blake2_128Concat, u32, u32, u32, u32, ValueQuery>;
  

方法

  • 插入元素
    • MyDoubleMap::<T>::insert(key1,key2, value);
  • 通过key获取value
    • MyDoubleMap::<T>::get(key1,key2);
  • 删除元素
    • MyDoubleMap::<T>::remove(key1,key2);
  • 删除所有元素通过key1
    • MyDoubleMap::<T>::remove_prefix(key1, None);

存储初始化

创始区块的部分实现

#[pallet::genesis_config]
pub struct GenesisConfig {
    pub value: u8,
}
#[cfg(feature ="std")]
impl Default for GenesisConfig<T>{
    fn default() -> Self {
        Self { value: Default::default())
    }
}
#[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig<T>
{
    fn build(&self) {
        MyValue::<T>::put(&self.value);
    }
}

最佳实践

  • 最小链上存储
    • 存储哈希的值
    • 谨慎的设置集合类型的容量
  • 先验证,再写入
  • 通过transactional的宏,保证原子化的操作