< 返回版块

洋芋 发表于 2019-12-25 17:03

Tags:substrate,runtime, storage

Substarte中的存储

Substrate使用简单的Key-Value存储数据,该存储是个数据库(RocksDB)支持的修改过的Merkle树(又称为“trie”)结构。更多关于trie的信息,参见:

Substrate使用RocksDB实现其存储数据库,该数据库用于所有需要持久存储的组件,例如:

  • 客户端(clients)
  • 轻客户端(light-clients)
  • 链下工作机器(Off-chain workers)

Trie允许有效存储和共享历史区块的状态。trie root可以用来表示trie中的数据。也就是说,两个拥有不同数据的trie将始终具有不同的trie root。因此,两个区块链节点可以通过比较它们的trie root来验证它们是否具有相同的状态。

访问trie中的数据非常昂贵。每个读取操作花费O(log N)时间,其中N是存储在trie中元素的个数。使用key-value缓存可缓解这种情况。所有trie节点(node)都存储在RocksDB中,并且部分trie状态可以被修剪,即当key-value对超出非归档节点的修剪范围时,可以从存储中删除它。

基于Substrate的链只有一个状态树(State Trie),其根哈希值放置在每个区块头中。用于验证区块链的状态,并为轻量级客户端验证证据提供基础。此trie仅存储规范链(canonical chain)的内容,而不存储分叉链(fork chain)。有一个单独的state_db层,它对所有非规范链,使用内存中的引用计数维护trie状态。

Substrate还提供了一个API,用自己的根哈希生成新的子状态树(Child Trie),可以在runtime中使用。子trie与主状态trie相同,不同的是,子trie的根作为节点(node)被存储并更新,而主状态trie的根是在区块头中。由于它们的区块头是主状态trie的一部分,因此当包含子trie时,仍然很容易验证完整的节点状态。子trie是有用的。当想要具有独立根哈希的独立trie,可以使用它来验证该trie中的特定内容。由于trie的子部分没有类似于根哈希的表示形式,无法满足这个需求;这时就需要用子trie。

Runtime Storage

在Substrate中,使用Runtime Storage可以将数据存储在区块链中。通过Substrate的存储API,runtime模块可以容易地这些数据可以从runtime逻辑中访问,并被持久化在区块中。

存储类型

基本的存储项由名称和类型组成,常见的存储类型:

  • Value,单个值。
  • Map,key-value的hash map。
  • Linked Map,与Map相似,但是允许枚举存储的元素。
  • Double Map,具有两个keys的map实现。

定义Storage

可以使用宏decl_storage!创建新的runtime存储项。声明每种类型的存储项的示例:

decl_storage! {
    trait Store for Module<T: Trait> as Example {
        pub SomeValue: u64;
        pub SomeMap: map u64 => u64;
        pub SomeLinkedMap: linked_map u64 => u64;
        pub SomeDoubleMap: double_map u64, blake2_256(u64) => u64;
    }
}

存储项的完整定义格式:

#vis #name get(fn #getter) config(#field_name) build(#closure): #type = #default;

  • #vis:设置结构体的可见性。pub或不设置。
  • #name:存储项的名称,用作存储中的前缀。
  • get(fn #getter):可选,实现Module的名称为#getter的函数。
  • config(#field_name):可选,定义可在GenesisConfig中包含该存储项。如果设置了get,则field_name是可选的。
  • build(#closure):可选,定义闭包,调用它可以覆盖存储项的值。
  • #type:存储项的类型。
  • #default:定义默认值,即无值时的返回值。

定义示例:

decl_storage! {
	trait Store for Module<T: Trait> as Example {
		Foo get(fn foo) config(): u32=12;
		Bar: map u32 => u32;
		pub Zed build(|config| vec![(0, 0)]): linked_map u32 => u32;
	}
}

定义解读:

  • 使用(pub) trait Store for Module<T: Trait> as Example定义
    • traitStore,用来关联每一个存储项到Moudle
    • Example称为模块前缀,必须是唯一的。as Example,用来设置模块的每个存储项前缀。
  • 值类型:Foo,实现traitStorageValue
    • Twox128(module_prefix) ++ Twox128(storage_prefix)
  • Map类型:Bar,实现traitStorageMap
    • twox128(module_prefix) ++ twox128(storage_prefix) ++ hasher(encode(key))
  • LinkedMap类型:Zed,实现traitStorageLinkedMap

更多信息查看参考文档 宏decl_storage!

默认值

Substrate允许定义未设置存储项时返回的默认值。该值实际上没有存储在runtime存储中,但是runtime逻辑将在执行期间得到该值。为map中的所有项设置默认值的示例:

decl_storage! {
    trait Store for Module<T: Trait> as Example {
        pub SomeMap: map u64 => u64 = 1337;
    }
}

访问Storage

可以通过多种方式访问存储项:

  • 结构体:FooFoo::<T>取决于值类型是否通用。
  • traitStore结构体:<Module <T> as Store>::Foo
  • 调用模块的getter获得结构体:Module::<T>::foo()

Runtime 存储API

Substrate的Support模块提供了实用性工具,可为runtime模块的存储项生成唯一确定的keys。这些存储项放置在状态trie中,可通过key查询trie来访问它们。

存储API中,定义了5个trait:

  • StorageValue,单个值
  • StorageMap,key-value的hash map。
  • StorageLinkedMap,与StorageMap相似,但是允许枚举存储的元素。
  • StorageDoubleMap,有两个keys的map实现。
  • StoragePrefixedMap,用于唯一前缀存储所有值的map

更多信息查看参考文档 storage

存储API支持可以被Parity的SCALE编解码器编码的任何值。关于SCALE编解码器,可查看文档 SCALE Codec

原文链接:https://zhuanlan.zhihu.com/p/99362959

评论区

写评论

还没有评论

1 共 0 条评论, 1 页