大纲
- 区块链存储和传统互联网存储的不同点和约束
- 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的宏,保证原子化的操作