如何降低运行区块链全节点成本?这里有全新轻
摘要: Ultrain在比特币及以太坊方案基础上,提出了轻节点世界状态校验技术方案
随着区块链网络的运行,节点数据量越来越大,运行区块链全节点的成本越来越高,现在一个区块链的全节点,区块数据存储量动辄就要几百G到上T,一方面直接导致运行全节点的区块链计算机的存储成本极大提高,如果我们想把区块链节点跑在一台普通PC上,光区块链数据就使用了普通PC一大半的硬盘存储空间,这显然是很难让人接收的;一方面也大大限制了区块链技术的应用场景,无论是手机还是IoT物联网设备,现在都不具备加载几T存储硬盘的能力。
为了解决这个问题,不同的区块链平台都提出了各自的轻节点解决方案,其中比较典型的是比特币的SPV方案和以太坊的状态校验方案。但是这两种方案都存在一定的不足,比特币的SPV方案可验证交易确实发生过,但无法验证在某个时刻账户的具体数值;以太坊的状态校验方案即可以验证交易发生过,也可以验证某个时刻账户的具体数值,但由于该方案需要将数据写入块头,导致共识的性能下降以及健壮性降低。
Ultrain在比特币的SPV方案和以太坊的状态校验方案基础上,提出了新的轻节点方案,既可以满足验证交易发生过和验证某个时刻账户的具体数值的功能性要求,同时也能满足共识在性能和健壮性方面的要求,其中在性能方面是以太坊方案的10倍以上。
采取轻节点模式的节点不需要同步巨量的区块数据,利用世界状态默克尔校验方式,节点不需要重放历史区块即可校验状态的正确性,从而可以使能更多应用场景(比如在资源受限的嵌入式设备运行轻节点等)。本文将从世界状态校验问题引出,比特币SPV方案,以太坊世界状态校验方案以及Ultrain所采取的具体方案等方面对轻节点世界状态校验技术方案进行详细阐述。
一、 节点同步数据量
区块链节点网络可以理解为一个分布式的状态机,各个节点从相同的创世状态开始,根据每个区块内包含的交易,将创世状态逐块更新,形成不断更新的世界状态。随着时间的推移,每个节点本地存储的数据会不断增长,主要包括历史区块数据以及不断更新的世界状态数据。比特币创世至今135个月(2009/01~2020/03)所生产的区块累积数据量已经达263.6GB,且现在以每个月四个多GB的速度在增长。
图1 比特币区块大小
以太坊全节点以full模式进行同步时(节点会从网络同步所有的区块头,区块体并重放区块中的交易以生成世界状态数据),当前需同步的数据已经达到两三百GB,如对历史世界状态进行archive,则数据量已经超过4T。以太坊节点还支持fast模式进行同步,该模式与full模式的区别在于不重放区块中的交易去重构世界状态,而是从网络同步状态数据。此外,以太坊节点也支持light同步模式,此种模式下节点仅同步区块头数据,不同步区块体和状态数据,仅在需要相应的区块和状态时去从网络上获取。
图2 以太坊全节点同步数据量
图3 以太坊全节点Archive模式数据量
从以上比特币和以太坊的数据我们可以看出,部署运行一个全节点所需同步的数据量越来越大,从而导致节点机器硬件规格要求以及网络带宽门槛越来越越高。一个新节点需要花数天时间才能完成历史数据同步,然后才能开始参与共识过程进行出块。运行全节点成本比较高,在比特币白皮书中提到了轻节点可采用的SPV方案,可以在不同步全部区块数据的情况下,也能进行支付验证。这里的支付验证,只是验证该笔交易支付发生过,并不是验证交易的合法性(比如账户余额是否大于转账金额等)。
二、 比特币SPV方案
比特币白皮书中描述了SPV ( Simple Payment Verification ),即简单支付验证。SPV是一个在轻节点环境下,不用运行全节点、只需保存所有的区块头,就能验证支付有效性的技术方案。比特币中区块结构分为区块头和区块体两部分,区块头包含区块的必要属性,仅 80 字节大小;区块体包含当前区块下的所有交易,一般一个区块包含成百上千笔交易,每笔交易一般要 400 多字节。
图4 比特币区块结构
如图4所示,比特币使用了默克尔树的数据结构来组织区块体内包含的交易:将区块内所有交易成对分组,并对交易进行哈希处理,然后对所得的哈希继续进行哈希处理,重复此过程,直到只剩下一个哈希,称为默克尔根(merkle root)。默克尔树底部的节点,就是交易数据的哈希值。每一个父节点,都是两个子节点哈希值组合后的哈希值。通过层层往上计算,最终算得根节点。这个默克尔根数值存储于比特币的区块头中。
轻节点从区块链网络上获取了最长链上的所有区块头并在本地进行存储后,即可进行进行SPV支付验证:轻客户端首先计算待验证支付交易的哈希值;然后定位到包含该交易哈希所在的区块,并从区块中获取构建待验证支付交易默克尔树所需的哈希值序列;根据待校验交易的哈希值以及其所需哈希值序列,计算出默克尔根;将计算所得默克尔根数值与区块头中的默克尔根进行比对,如相等则证明交易是真实存在的。
比特币区块头的大小始终是固定的80个字节,每小时出块约为6个,每年出块52560个,则每年新增的存储量约为4M 字节,SPV方案极大节省了轻节点的存储空间。从上述过程我们可以看出,比特币的这种SPV方案,仅确保校验的交易确实发生过,但不能用来校验交易所导致的世界状态的变化。以太坊在区块头中存储了交易的默克尔根数值,可以适用类似比特币的SPV方案;此外以太坊的区块头中还存储了世界状态的默克尔根数值,可以用来进行世界状态数值的校验。
三、 以太坊世界状态校验方案
以太坊的账户包含四个属性,nonce,balance,storageRoot和codeHash;以太坊用stateObject来管理账户状态,账户以Address为唯一标示;所有账户对象逐一插入Merkle-PatricaTrie(MPT)结构里,形成stateTrie。区块头数据结构中的Root字段存储了stateTrie的root数值,即世界状态的哈希值。
图5 以太坊区块头世界状态哈希字段
我们假设在以太坊区块链上有两个账户A和账户B,初始账户余额均为10个以太币,在编号为1000的区块中发生过这样一笔交易:账户A向账户B转账一个以太币,将账户A的余额修改为9个以太币,账户B的余额修改为11个以太币(不考虑矿工费)。在与比特币SPV类似的以太坊的轻客户端中,我们可以利用在区块编号为1000块的块头中存储的交易的默克尔根数据,校验确实发生过账户A向账户B转账一个以太币这样一笔交易,但是仅利用交易的默克尔根数据,不能校验账户A的余额是不是9个以太币(或者账户B的余额是不是11个以太币),即不能校验世界状态的具体数值。
在以太坊的区块头的世界状态默克尔根数值(字段Root)可以用来完成世界状态具体数值的校验。该字段为以太坊StateDB中的“state Trie”的根节点的RLP哈希值。区块中,每个账户以stateObject对象表示,账户以Address为唯一标示,其信息在相关交易(Transaction)的执行中被修改。所有账户对象可以逐个插入一个Merkle-PatricaTrie(MPT)结构里,形成“state Trie”。
以太坊EIP-1186中增加了eth_getProof接口用于返回账号(account)及其状态(storage values)的默克尔校验所需的信息(merkle path),用于完成世界状态数值校验。eth_getProof有三个输入参数:
1) DATA: 20字节 – 为账户的地址(外部账户/合约账户)
2) ARRAY:32字节 – 为代校验状态数据的地址
3) QUANTITY|TAG: – 为指定的区块编号或者字符串"latest" 或 "earliest"
图6 以太坊eth_getProof接口请求参数
该接口返回数据如下:
1) balance:账户余额
2) codeHash:合约账户代码哈希数值,外部账户则返回固定数值
3) nonce:账户nonce值,表示发了多少笔交易或创建了多少个合约
4) storageHash:状态数值的默克尔根数值
5) accountProof:账户校验所需的默克尔序列数组
6) storageProof:状态数值校验所需的信息数组,包括以下字段:
Ø key:状态数值对应地址
Ø value:状态具体数值
Ø proof:状态数值校验所需的默克尔序列数组
图7 以太坊eth_getProof接口返回数据
轻节点通过eth_getProof接口获取上述返回信息后,首先根据返回的四个属性值[nonce, balance, codeHash,storageHash]构建账户对象,进行RLP编码后进行哈希操作,得到的数值与accountProof结合可以与在区块头中存储的世界状态默克尔树根数值进行校验;校验通过后,则相当于确认了storageHash字段的正确性,再结合storageProof中的默克尔序列可以完成对状态数值的校验。相关校验示例代码如下所示:
图8 以太坊状态校验示例代码
四、 Ultrain世界状态校验方案
以太坊采用MPT树(Merkle-PatricaTrie)管理所有账户对象,stateTrie存储了所有账户的信息,比如余额,发起交易次数,虚拟机指令数组等信息,随着每次交易的执行,stateTrie 其实一直在变化,区块内所有交易完成后,所有账户信息的即时状态的默克尔根数值会记录到区块头中。
Ultrain采用chainbase存储所有世界状态信息,区块内交易执行会更新chainbase内相关对象的状态数值,因此chainbase内存储了世界状态的实时信息。客户端通过链上接口(get_table_records)查询chainbase内信息时,实际上是信任接口提供方,从而信任其提供的数据的正确性;且该接口只能提供实时状态信息,不能查询指定块高状态数值。Ultrain世界状态校验方案可指定块高查询状态信息,并提供该状态信息校验所需的默克尔路径信息,从而去除对接口提供方的信任依赖。
Ultrain世界状态校验方案中,每个矿工节点在区块交易执行结束时,将本区块内所有交易对世界状态所做的所有修改(即对chainbase内存储数据的修改)进行序列化处理,并按一定顺序组织为默克尔树的叶子节点,再逐层计算得出该区块世界状态变化的默克尔根数值,计算所得的默克尔根数值会保存到内存中。当区块高度到一定间隔(比如每100块)时,将间隔内所有区块的世界状态变化的默克尔根数值再做一次默克尔根数值计算,并将该结果(称之为世界状态变化累计默克尔根数值)以及间隔内每个区块的块高与对应的默克尔根数值写入文件系统。为防止对主线程性能造成影响,这部分逻辑采用单独的线程完成。所有矿工节点会将世界状态变化累计默克尔根数值通过链上交易写入系统合约,只有超过2/3矿工汇报相同默克尔根数值时,该数值才会生效。
为提供世界状态校验查询功能,需设置提供世界状态查询服务的节点。选取非矿工节点(即只接收交易和区块,不参与共识过程的节点)在每个区块内交易执行完全结束后,将该区块内所有对世界状态的修改序列化后存储到文件系统,并响应校验查询节点的数据请求,将状态变化数据发送给查询节点进行存储。查询节点采用rocksdb将每个区块对世界状态的修改进行存储,包括块高(block_num),修改序号(sequence),修改内容(修改后的具体状态内容);查询节点在存储每块数据的同时,与矿工节点类似计算每个区块对应的世界状态变化的默克尔根数值,并将该数值存储到rocksdb中。此外,查询节点为轻节点提供查询接口,响应其查询请求。
图9 Ultrain世界状态校验查询节点与非矿工节点交互
轻节点对特定世界状态进行查询校验的时候需经过如下三个步骤:
1)首先采用 get_table_rows接口查询具体世界状态数值,该接口需输入要查询的世界状态所在的数据表信息(code,scope,table)以及限定的区块块高信息:
返回数据包括该条状态的具体信息(如示例中data字段的账户余额)以及该信息对应的二进制字节(示例中的raw字段,该字段也可以由轻节点根据合约的ABI文件自己生成);此外,该接口会返回这条世界状态修改对应的块高(block_num)及序号信息(sequence)。
2)根据get_table_rows接口返回的块高及序号信息,获取对应的校验该状态所需的默克尔树序列信息,轻节点调用如下接口:
该接口返回的paths字段即为默克尔树序列信息:
3)根据get_table_rows返回的raw数据以及get_table_row_proof返回的paths数据,轻节点可以调用如下接口,计算默克尔树根数值(轻节点也可以自己实现根据raw和paths数据计算默克尔根的逻辑):
该接口返回计算所得的默克尔根数值,该值可以与矿工节点通过交易写入系统合约的对应的数值进行比对,相同则表示校验通过(该示例中发生世界状态改变的区块高度为34186,如矿工节点所选取的区块间隔为100的话,则应该从系统合约读取块高34200对应的默克尔根数值进行比对)。
综上所述,轻节点与查询服务节点交互流程如下图所示:
图10 Ultrain世界状态校验接口流程图
文中所述比特币的SPV方案,以太坊的状态校验方案以及Ultrain的状态校验方案,我们可以看到轻节点不需要再同步大量的数据,即可验证交易确实发生过,以及验证在某个时刻世界状态具体数值,由此轻节点所需的存储及计算资源大幅降低,可以在资源受限的嵌入式设备上运行轻节点,比如物联网设备,实现物联网与区块链技术的结合,使能更多商业场景。
作者:Ultrain超脑链;来自链得得内容开放平台“得得号”,本文仅代表作者观点,不代表链得得官方立场凡“得得号”文章,原创性和内容的真实性由投稿人保证,如果稿件因抄袭、作假等行为导致的法律后果,由投稿人本人负责得得号平台发布文章,如有侵权、违规及其他不当言论内容,请广大读者监督,一经证实,平台会立即下线。如遇文章内容问题,请发送至邮箱:linggeqi@chaindd.com