SCRY技术分享Scale编解码算法

2023-4-2 来源:不详 浏览次数:

白癜风治疗最好的药 http://disease.39.net/bjzkbdfyy/170816/5629059.html

在上次介绍substrate存储键构成的文章中,主要分享了在链存储KV数据库中值的键是怎样来生成的,使用生成的键可以通过调用rpc提供的方法直接将对应的值查询出来。使用这种方式来实现存储值查询是一种偏底层的方式,但也是更通用的方式。区块链存储系统主要通过KV方式来实现区块数据的存储。通过这种底层方法将key对应的值查询出来,得到编码后的结果。

在substrate中使用编解码算法为简单串联聚合小端编解码(SCALECodec-SimpleConcatenatedAggregateLittle-Endian)。这篇文章的目的是对这个编解码算法进行分享,这也是cashbox在实现交易签名构造交易数据过程中比较重要的环节。

序列化与反序列化

我们知道,数据在存储、网络传输过程中最终都是通过二进制字节数组来实现的。为了能够实现将程序中实例数据转换为二进制数据,并且在接收方能够将二进制数据转换为正确的对象实例,实现数据的跨平台交互。针对数据的跨平台交互,存在不同的解决方案,比如:

在Java中,对象通过实现java.io.Serializable来实现将对象实例数据转换为二进制数据以及将二进制数据转换为Java实例;

在Grpc具有数据的跨平台,跨语言交互的能力,其底层是利用ProtocolBuffers来实现数据的序列化与反序列化;

通过这些序列化工具使得应用数据在跨平台之间的交互变得更加容易。

SCALE算法

由于Substrate的节点可以通过Wasm方式和Native这两种方式运行,为了使这些节点能够正常进行数据编解码,parity团队重新设计了一种编码方案,而不是直接使用已经广泛使用的rust编码库。它旨在用于高性能,无复制的编码和对资源受限的执行上下文中数据的编解码,如substrate中的runtime。该算法编码过程没有类型的描述信息,而且解码上下文也不知道已编码数据包含的数据类型;

为了能够是正确的解码编码后的数据,还原为对应的结构体实例,所需对存在的数据类型编码方式进行约定,下面简要的对该算法编码规则进行介绍。

整形

整形是在代码中使用的比较多的一种变量,根据所占的位数以及是否存在符号位,分别对应着不同的类型。为了在数据传输过程中更高效的利用内存空间、网络带宽,整形的编码分为固定长度的编码和变长编码,规则如下:

固定长度

直接使用使用固定长度的小端格式,比如

有符号8位整数69:0x45;

无符号16位整数42:0x2a00;

无符号32位整数:0xffff;

变长编码

使用固定长度编码的方式,每个编码后的结果都为固定长度,当数字较小位数定义较大的变量,会造成利用率不高的问题。使用紧凑整数编码方案能够完全解决大整数的编码而且针对大多数值来说,比固定长度版本更有效率;这种编码模式使用最低两位bit来表示所使用的模式,不同的模式分别用于编码值大小范围

0b00:单字节模式,实际表示的数用剩下的6bit来表示,因此单字节允许表示值的范围为:0~63。比如无符号数0,表示为0x00;

0b01:双字节模式,使用剩下的6bit和另外一个字节来表示真实的数,因此双字节允许表示数的范围为:64~2**14-1。比如无符号数1,0x04;

0b10:四字节模式,使用剩下的6bit和另外三个字节来表示真实的数,因此双字节允许表示数的范围为:2**14~2**30-1。比如无符号数42,0xa8;

0b11:大整数模式,使用剩下的6bit表示后面跟随的字节数,最少为4字节。最高位必须为非零值;合法的值为2**30)-(2**-1。比如无符号数69,编码为0x;

Boolean

使用单个字节的最低bit来表示相应的值,比如false编码为0x00,true编码为0x01

Options

针对存在的两种情况值,其中需要注意的是若Option处理的类型是boolean,还是使用一个字节来编码,规则如下:

None编码为0x00;

当结果为Some(true)时,编码为0x01;

当值为Some(false)时,编码的值为0x02;

若Option处理的值为另外的类型则直接将对应值的编码跟随在后面即可;

Results

Results一般是用于表示操作的成功与否,编码规则如下:

当操作成功时,使用0x00表示,后面再跟具体类型的返回值;

当操作失败时使用0x01来表示,后面跟再跟具体的错误信息;

Vectors

针对List,Sets这类集合类型,在编码的时候将集合中包含的成员数量使用紧凑编码的结果作为前缀,后面再依次添加针对集合类型中值的编码。以一个包含16位无符号整数的集合为:[4,8,15,16,23,42],包含元素的个数为6,对应的紧凑编码结果为0x18,所以最后的编码结果为:0xfa00;

Strings

由于String是一个包含元素为UTF-8字符的集合类型,因此编码规则同上述的Vectots相同;

Tuples

是由一系列固定大小类型值,每个类型可能不用但位置是固定的类型,针对这种类型的编码直接将个类型的值链接起来即可;比如(3,false)编码结果为0x0c00

数据结构

根据实际需要,使用上述的各种类型组合而成的,每个struct的定义都是有名字和对应的类型组成,在编码的时候名字会被忽略掉,只会编码对应的值,根据其对应的类型使用相应的编码方案;将各个类型编码的值链接起来就是最后结构体的编码结果。其中需要注意的是若存在集合类型。最终解码后的元素顺序取决于集合的性质,存在编码前后元素间顺序不一致的可能性。

需要注意的是在解码字节数组的时候,可能会存在数组编码顺序和解码结果顺序不一致的问题;

枚举类型

枚举类型是一个包含有几种可能取值的类型,每个枚举变量只能表示该枚举类型中的一种类型变量。针对枚举类型使用所在类型定义序号以及对应的值组成,其中序号使用第一个字节来表示,类型具体的值紧随其后。根据这个编码规则,当前支持的枚举类型包含的类型数不能超过.

enumIntOrBool{Int(u8),Bool(bool),}

比如:Int(42)0xaBool(true):0x

账户信息解码

在介绍了各种类型数据的编码规则后,我们来看一下在实际场景中的应用。这里使用cashbox查询eee链(基于substrate开发)上的账户信息为例,通过源码查询得到主要部分代码:

traitStoreforModuleT:TraitasSystem{///ThefullaccountinformationforaparticularaccountID.pubAccountget(fnaccount):maphasher(blake2__concat)T::AccountId=AccountInfoT::Index,T::AccountData;}

这里我们继续使用Alice这个账户为例,动态库根据cashboxflutter模块传递进来的参数得到该账户对应的key为:0x26aaeeae07c48ae0ccef7b99decc0cf30eda9de1e86a9a8ccf3cc5ec2bea59fdcfdd31cabd04a99fdcccde39ae7a56da27d;

通过向链节点发送JsonRpc请求

{"id":1,"jsonrpc":"2.0","method":"state_getStorage","params":["0x26aaeeae07c48ae0ccef7b99decc0cf30eda9de1e86a9a8ccf3cc5ec2bea59fdcfdd31cabd04a99fdcccde39ae7a56da27d"]},

链服

务端将返回该AccountID对应的账户信息,结果如下所示:0xdbb20a00;要想正确的解码出这部分的数据,我们需要知道构成这些数据对应的类型,通过源码能够知道AccountInfo定义如下:

///Indexofatransactioninthechain.pubtypeIndex=u32;///Balanceofanaccount.pubtypeBalance=u;///Typeusedtoencodethenumberofreferencesanaccounthas.pubtypeRefCount=u32;typeAccountData=pallet_balances::AccountDataBalance;///Informationofanaccount.#[derive(Clone,Eq,PartialEq,Default,RuntimeDebug,Encode,Decode)]pubstructAccountInfoIndex,AccountData{///Thenumberoftransactionsthisaccounthassent.pubnonce:Index,///Thenumberofothermodulesthatcurrentlydependonthisaccountsexistence.Theaccount///cannotbereapeduntilthisiszero.pubrefcount:RefCount,///Theadditionaldatathatbelongstothisaccount.Usedtostorethebalance(s)inalotof///chains.pubdata:AccountData,}///Allbalanceinformationforanaccount.#[derive(Encode,Decode,Clone,PartialEq,Eq,Default,RuntimeDebug)]pubstructAccountDataBalance{///Non-reservedpartofthebalance.Theremaystillberestrictionsonthis,butitisthe///totalpoolwhatmayinprinciplebetransferred,reservedandusedfortipping.//////Thisistheonlybalancethatmattersintermsofmostoperationsontokens.It///aloneisusedtodeterminethebalancewheninthecontractexecutionenvironment.pubfree:Balance,///Balancewhichisreservedandmaynotbeusedatall.//////Thiscanstillgetslashed,butgetsslashedlastofall.//////Thisbalanceisareservebalancethatothersubsystemsuseinordertosetasidetokens///thatarestillownedbytheaccountholder,butwhicharesuspendable.pubreserved:Balance,///Theamountthat`free`maynotdropbelowwhenwithdrawingfor*anythingexcepttransaction///feepayment*.pubmisc_frozen:Balance,///Theamountthat`free`maynotdropbelowwhenwithdrawingspecificallyfortransaction///feepayment.pubfee_frozen:Balance,}

根据结构体编码规则,该变量在编码时是将各变量值分别编码后依次连接起来的,对查询出来的结果值按照组成类型所占位数进行分割,得到如下结果:

{"nonce":"0x","refcount":"0x","data":{"free":"0xdbb20a00","reserved":"0x","miscFrozen":"0x","feeFrozen":"0x"}}

由于为小端编码,转换为十进制值为得到最终的结果为:{"nonce":37,"refcount":0,"data":{"free":89922260000,"reserved":0,"miscFrozen":0,"feeFrozen":0}};

总结

在实际代码编写过程中不需要按照变量所占位数来进行编解码操作,ParitySCALECodec已经在多种语言中实现了对应的库,能够直接对各种变量类型进行编解码操作。通过对数据进行拆分,能够清楚的知道查询结果是怎么构成的。针对在应用实现过程中,若在某些场景下需要构造对应的类型变量很复杂,则可以直接通过这种特性提取我们需要的数据。该编解码算法在substrate中广泛的应用,了解该算法可以对区块数据组织结构能够有更深入的了解。如同这个编解码算法名字所描述一样,编码规则简单、高效。当我们在做跨平台应用时,该编解码算法是一个可考虑的方案。

转载请注明:
http://www.weichiyen.com/szff/13280.html
  • 上一篇文章:

  • 下一篇文章: 没有了
  • 网站首页 版权信息 发布优势 合作伙伴 隐私保护 服务条款 网站地图 网站简介

    温馨提示:本站信息不能作为诊断和医疗依据
    版权所有 2014-2024
    今天是: