LOADING...
LOADING...
LOADING...
当前位置: 玩币族首页 > 币圈百科 > SCRY技术分享Substrate链存储查询介绍

SCRY技术分享Substrate链存储查询介绍

2021-06-18 scry 来源:区块链网络

Substrate做为一个功能强大的区块链开发框架,能够让链开发者在短时间内就可以运行一条自己的链,由于其代码结构设计抽象化程度较高,使得开发者能够快速添加自己的特色功能。同时还提供了一整套工具,便于用户与链之间进行交互。

在Cashbox动态库功能的开发中,我们对基于Substate开发的链转账功能进行了支持。这篇文章的目的是借助介绍构造账户信息查询key的过程,顺便说明substrate中的存储key设计原理。

查询方式

Substrate提供了一整套RPC方法,涉及链信息、存储查询、交易提交等功能,由于不同的链启用的模块有所区别,针对具体使用链节点,可以通过默认的UI界面发送rpc_methods来查询当前节点所支持的所有的rpc方法;

针对本文接下来介绍存储查询,我们主要使用state_getStorage这个RPC方法来实现;该方法需要一个十六进制类型参数来构造rpc请求,请求格式如下:{"id":1,"jsonrpc":"2.0","method":"state_getStorage","params":["xxx"]}本文接下来重点主要用来介绍该参数的构造过程。了解存储key构造过程的朋友,可以忽略以下内容。

Substrate存储方式

Substrate使用??RocksDB来实现数据的持久化,为链平台提供快速高效的键值对存储服务,满足各客户端组件所需要的数据服务。这里不再对RocksDB进行详细的介绍。为了能够将链上所有数据存储到数据库中,同时还能实现对数据高效查询,隐私保护等要求,需要对存储数据的进行编码。这里涉及到键的构造,值的序列化过程。关于值的序列化与反序列化,打算后期再整理一篇文章来说明,在本文中先暂时忽略。

与链业务相关的功能是在Frame模块中实现,与存储相关的数据类型定义类似以下过程:

decl_storage!?{ ?????????trait?Store?for?Module<T:?Trait>?as?Example?{ ???????????????????Foo?get(fn?foo)?config():?u32=12; ???????????????????Bar?get(fn?bar):?map?hasher(blake2_128_concat)?T::AccountId?=>?T::Balance; ???????????????????pub?Zed?double_map?hasher(blake2_128_concat)?T::BlockNumber,?hasher(twox_64_concat)?T::BlockNumber?=>?Option<T::BlockNumber>; ?????????} ?}

我们称模块名字为module_prefix,存储项名称为storage_prefix。如上代码所示,Example是module_prefix,Foo、Bar、Zed是storage_prefix。其中针对存储项的类型主要有以下三种情况,键构造方式有所区别:

lStorage Value:这种类型的定义Foo: type,键的构造直接使用Twox128(module_prefix) ++ Twox128(storage_prefix)这种方式即可,整个构造过程比较简单;

lStorage Maps:这种类型的定义Foo: map hasher($hash) type => type,最终的键是由twox128(module_prefix) ++ twox128(storage_prefix) ++ hasher(encode(key))这三部分组合构成的,这里需要注意的是,由于Map是通过KV这种方式来存储数据的,所以针对Map来说,还需要对Map中的键进行hash计算,计算使用的算法即为hasher所表示的。当前具体支持的hash算法在稍后来进行说明;

lStorage DoublMap:这种类型的定义Foo: double_map hasher($hash1) u32, hasher($hash2) u32 => u32,用于支持更复杂的数据类型,最终的键是由Twox128(module_prefix) ++ Twox128(storage_prefix) ++ Hasher1(encode(key1)) ++ Hasher2(encode(key2))这四部分组合构成;

支持的hash算法

当前在存储类型定义时,使用的hasher有三种类型,他们对应的使用场景如下:

lblake2_128_concat:针对任一存储项是默认的选择,前缀blake2b表明该算法为加密安全的hash算法,最后将原始的数据附加在hash结果的后面;

ltwox_64_concat:该算法为不安全的hash算法,只能用于你确定该键不能由用户任意构造的场景,该算法拥有相当好的性能,内存利用率高,并且支持键和值的迭代;参数的构造过程同blake2_128_concat类似;

lidentity:这不是hash算法,它会直接使用键。由于它不进行hash计算或者追加值,因此速度是最快的,但是也是最不安全的。仅当该key是通过类似blake2算法计算出来的值时,才可以安全的使用它;

以Alice的Account ID?5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY为例,针对使用hasher算法为blake2_128_concat的存储项定义如下:

Bar?get(fn?bar):?map?hasher(blake2_128_concat)?T::AccountId?=>?T::Balance;

hasher计算的过程如下: 将对应Account ID转换为十六进制表示:0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d,通过blake2_128计算出来的结果为0xde1e86a9a8c739864cf3cc5ec2bea59f,最终的表示结果为

0xde1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d--------------?hash?--------------++++++++++++++++++++++++++?raw?key?+++++++++++++++++++++++++++++

关于这方面内容的介绍,可以查看源码frame/support/procedural/src/lib.rs中相关注释;

如何使用

通过前面的各种铺垫,将查询存储key值编码所需的准备工作已经完成。针对在cashbox中使用比较多的场景,是查询用户的余额,通过源码我们可以知道账户信息存放的位置:

trait?Store?for?Module<T:?Trait>?as?System?{ ????///?The?full?account?information?for?a?particular?account?ID. ??????????pub?Account?get(fn?account): ???????????????????map?hasher(blake2_128_concat)?T::AccountId?=>?AccountInfo<T::Index,?T::AccountData>; ????}

还是继续使用Alice这个账户为例,动态库根据cashbox flutter模块传递进来的相关参数,通过存储方式部分的内容知道该使用哪种方式来拼接最终结果,其中:twox128("System")得到的结果为0x26aa394eea5630e07c48ae0c9558cef7,twox128("Account")得到的结果为0xb99d880ec681799c0cf30e8886371da9,最终得到的查询key值为:0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d;

Flutter根据动态库返回的结果,构造JsonRpc请求{"id":1,"jsonrpc":"2.0","method":"state_getStorage","params":["0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"]},链服务端将返回该Account ID对应的账户信息,类似如下结果0x24000000000000000000ecfaea666eb30a13000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000;

总结

本文针对常规模块中的存储项查询key的编码方案进行了详细的说明,在了解了存储键的编码规则后,借助JsonRpc方法,能够很方便的查询到各个frame中定义的存储类型。针对查询出来的结果值解码方案,计划在下一篇文章中来进行说明.

—-

编译者/作者:scry

玩币族申明:玩币族作为开放的资讯翻译/分享平台,所提供的所有资讯仅代表作者个人观点,与玩币族平台立场无关,且不构成任何投资理财建议。文章版权归原作者所有。

LOADING...
LOADING...