风险提示:请广大读者树立正确的货币观念和投资理念,理性看待区块链,切实提高风险意识。
2023-04-19 17:11

以太坊的新分片之旅--Danksharding(二):EIP-4844

11.1万

原文:小d仙人


二、EIP-4844提案


EIP-4844提案有两个目标:

1、以兼容danksharding的方式率先支持rollup的扩容,提出一个携带大数据量的新型交易
2、将danksharding中有确定解决方案的、改造风险系数低的部分先更新,为danksharding的正式推出打好基础


2.1 rollup数据存储到以太坊,究竟有多贵?


v神在2021年写了一篇关于rollup的文章‌,里边对rollup的上链成本做了分析,简要来说,rollup在以太坊上链成本分为两个部分:每批交易固定的合约执行gas(Fixed gas cost per batch)和每笔交易的存储gas(Per-transaction on-chain gas costs)。有研究人员调研‌了现在的rollup项目optimism、arbitrum、zkSync的gas花费,如下表



可以发现,rollup在以太坊上链的gas确实是笔巨大的开销,降低rollup的上链成本是一件必须要做的事情。每批交易固定的合约执行gas消耗是由合约执行的过程决定,主要依赖于rollup合约对上链数据的验证规则设计,相比于optimistic rollup,zkrollup由于需要对上链数据进行zk验证,导致花费的gas更高,因此以太坊可以提供更加节约成本的验证方式供rollup团队选择。对于每笔交易的存储gas的定价,是由以太坊决定的,因此以太坊可以对rollup的数据做存储开销的优化


2.2 rollup数据的存储成本优化


只有理解了rollup数据在以太坊上的存储方式,才能做针对的存储开销优化。在当前以太坊上,rollup的数据都是以calldata的形式存储到以太坊,calldata是以太坊交易中的合约函数调用参数的存储空间,calldata有两个优势:

1、交易触发的合约函数中,不可修改calldata数据(存放到链上后就只能读)
2、相对便宜的gas

对于以太坊交易不太了解的读者,可以不关注calldata的实现原理,只需要知道这个空间可以用来存储相对较大的只读数据。所以为了尽可能的减少存储成本,rollup绝大部分都是将自身数据存储在以太坊的calldata中。虽然calldata的存储成本很低,但架不住rollup需要存储的数据量非常大

降低存储费用,如果直接降低calldata的存储gas费用可不可行?从长远来看,答案是不行。原因在于,直接降低calldata的存储gas,会导致极端情况下,单个区块变得非常大。不妨来计算下,calldata空间在以太坊的存储成本为1个Byte=16gas,目前以太坊设定的区块最大gas limt是30M,那区块的理论最大值为(30M/16) byte,约等于1.78MB,在可以接受的范围。如果calldata的存储gas从16至3,那相当于区块大小增大了5倍,接近10MB,这就很难接受了,因为会造成一些网络比较差的节点同步区块过慢而被淘汰

那还有什么解决办法呢?或许我们可以从rollup对以太坊的使用方式上受到启发

rollup的数据上链,只是为了实现数据可用性(公证数据),让这些数据能够被所有人看到就行,rollup合约不需要感知rollup的大部分公证数据,只要能正确的维护rollup的状态根即可

在以太坊链上,不需要存储rollup的所有公证数据,只需要存储这些数据相关证明,并推动以太坊上的rollup合约状态进行更新就可以了剩下的绝大部分公证数据都放到以太坊节点的链下存储。当需要验证这些数据时,可以从节点链下读取出数据,随后校验真正上链的相关证明是否正确即可

链下公证数据可以拥有一个公示期,在公示期内,是能够在以太坊节点上查询获取的。如果超出公示期,以太坊节点可以选择删除这些过期的rollup数据,减少存储成本。存储完整数据的任务应该交给对这些数据有保存动机的节点,比如以太坊归档节点、rollup的官方团队节点等等。公示期间,有动机保存数据的节点可以在以太坊上完成数据的获取

综上来看,rollup原来上链的公证数据有大部分数据是可以存在于链下的,不占用合约计算资源,并且数据过期后还可以删除,因此链下存储的成本是非常低的,非常适合rollup的这种模式

V神采纳的就是这种思路,并发表了一篇名为blob transactions的文章‌,文章中提出一种新的以太坊交易格式,专门用于支持rollup的数据存放。简单来说,Blob交易是在原有的以太坊交易的基础上,还会携带名为Blob的额外数据以及其对应的Blob证明,Blob存储空间可以存储2组Blob数据,每组数据约为128kb,这种交易有几个特点:

1、Blob的数据存储gas费用要比普通数据的存储费用便宜,相比以太坊交易的数据存储字段calldata,也要便宜一些

2、作为存储gas便宜的代价之一,合约在执行交易的过程中,无法访问Blob的数据,但是可以查看Blob的相关证明(原文称为commitment)

3、作为存储gas便宜的代价之二,Blob的数据具有保存期,超过保存期后,节点可以选择将其删除
4、Blob证明采用的是KZG承诺

5、一组Blob数据被划分成4096个32byte,后面会有说明为什么这么设计



Blob交易网络包由Blob交易和附加数据两部分组成,分开来讲:

1、Blob交易和普通交易一样,会进入执行链执行并上链,该交易有两个两个关键字段,blob_versioned_hashes和max_fee_per_data_gas,max_fee_per_data_gas字段遵循EIP-1559‌的规则,涉及到blob的计费问题,会在后面的章节详细描述。blob_versioned_hashes字段是由blob_commitments计算而来,为了向后的兼容性,比如未来可能会有其他的承诺算法替代KZG承诺,所以对kzg_commitment做一次hash操作,这样获得的version_hash就是一个通用的结构

2、附加数据会随着交易网络包在以太坊节点间传输,包含blobs数据、blobs对应的KZG承诺blob_commitment、KZG聚合承诺证明kzg_aggreated_proof。根据KZG聚合承诺证明,是可以验证blobs和blob_commitment是否匹配。这里的验证

节点收到这个交易网络包后,需要验证这个网络包,具体为:

1、Blob交易内的blob_versioned_hashes必须是以VERSIONED_HASH_VERSION_KZG开头
2、Blob的数量不能超过2个(随时会变)
3、blobs、blob_commitment、version_hashes必须匹配

关于Blob交易的数据结构,这里只粗略列出供参考,想要深入了解的读者可以查看EIP-4844

     class BlobTransaction(Container):
     chain_id: uint256
     nonce: uint64
     max_priority_fee_per_gas: uint256
     max_fee_per_gas: uint256
     gas: uint64
     to: Union[None, Address] # Address = Bytes20
     value: uint256
     data: ByteList[MAX_CALLDATA_SIZE]
     access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE]
     max_fee_per_data_gas: uint256
     blob_versioned_hashes: List[VersionedHash, MAX_VERSIONED_HASHES_LIST_SIZE]
 class BlobTransactionNetworkWrapper(Container):
     tx: SignedBlobTransaction
     # KZGCommitment = Bytes48
     blob_kzgs: List[KZGCommitment, MAX_TX_WRAP_KZG_COMMITMENTS]
     # BLSFieldElement = uint256
     blobs: List[Vector[BLSFieldElement, FIELD_ELEMENTS_PER_BLOB], LIMIT_BLOBS_PER_TX]
     # KZGProof = Bytes48
     kzg_aggregated_proof: KZGProof
 def kzg_commitment_to_versioned_hash(kzg_commitment: KZGCommitment) -> VersionedHash:
     return VERSIONED_HASH_VERSION_KZG + hash(kzg_commitment)[1:]
 VERSIONED_HASH_VERSION_KZG = Bytes1('0x01')
 hash() = keccak256()


2.3 KZG承诺


KZG承诺提供的功能类似于默克尔树根(默克尔树+hash),可以用来证明某个数据是存在于某个数据集的,比如某笔交易在某个交易集内。KZG承诺具有如下特性:

1、KZG是算术型承诺,可与数据可用性抽样+纠删码兼容;相对的,使用hash的默克尔树根是非算术型承诺,无法与数据可用性抽样兼容
2、KZG能很好的适配rollup体系,支持rollup以更廉价的方式实现数据验证
3、KZG承诺的数据量很小,能够放到区块头中
4、验证时间很短
5、需要进行可信设置的初始化

KZG承诺本身是一项针对多项式的承诺,既然是多项式,那所有基于这个多项式的计算都是以数字类型承载,也就是说要将Blob数据转化成数字类型。当前Blob数据是4096个uint256类型的数字,每个uint256可以表示为32字节,换算下来,相当于Blob数据量为 4096 * 32 = 128kb

KZG承诺的证明涉及到许多数学知识,感兴趣的可以参考这里‌。这里仅做简单描述,以现有的Blob举例:

一个Blob中有4096个数字,我们可以根据KZG的算法,把这4096个数字当做点,计算出一个经过这些点的多项式,并根据这个多项式计算出KZG证明和KZG承诺,当需要验证Blob时,我们只需要拿到KZG证明和KZG承诺,就能重新打开这个多项式,并验证Blob数据的4096个点是否在这个多项式上,若全部点都在,则说明Blob和KZG承诺匹配,验证通过


2.4 Blob交易具体是如何存储的?


由于以太坊合并后,实际有两条链(信标链、执行链)在运行,每条链各运行了一个客户端程序,以太坊数据也相应地在这两条链、两个客户端程序各存了一部分。为了方便理解,我们先把以太坊当做“一条链和一个客户端”,以太坊所有的数据都存在这条链和这个客户端程序内

以太坊节点可以抽象出三个存储区,区块数据存储区、Blob数据存储区、合约数据存储区

1、区块存储区就是用来存储整个历史区块的,Blob交易也会存储在这
2、Blob数据存储区是专门存放Blob数据的,Blob数据必须和Blob交易中的相关证明(versioned_hash)相匹配
3、rollup合约会存放在合约数据存储区,其保存着rollup合约的状态,Blob交易可以推动rolluo合约的状态更新
blob交易抽象存储区
blob交易抽象存储区

回到实际存储层面,上面讲述的区块数据存储区和合约数据存储区可以认为是由以太坊执行链维护,属于执行层,Blob数据存储区则被划分到共识层,由信标链维护。

Blob数据由信标链保存的原因有以下几点:

1、Blob数据并没有真正上链,可以存储到执行层之外的地方

2、以太坊区块共识是由信标链负责,节点间共识区块是在信标链网络中广播,Blob数据的验证也由信标链负责,Blob数据存到信标链也相对合理
3、Blob数据不存到执行链,执行链的改动量小,实现简单,版本更新相对容易


在下图中可以看到,信标链的区块体维护了该区块锚定的所有Blob交易对应的KZG 承诺,同时在信标链的链下存储相应的Blob数据和KZG聚合证明;执行链维护了Blob交易以及rollup合约

blob存储实际存储
blob存储实际存储


2.5 Blob交易的生命周期


在EIP-4844提案中,有设计Blob交易网络包和信标区块的传输细节,具体实现如下:


1:Rollup的定序器(Sequencer)准备构造Blob交易网络包,将Rollup的交易塞进Blob交易附加数据Blobs内,计算相关证明后,构造Blob交易,最终将Blob交易和附加数据一起封装成网络包,发给以太坊节点交易池

2:以太坊网络中某个节点收到Blob交易网络包后,不会自动去广播该交易包,而是会生成一个叫做"NewPooledTransactionHashes"的消息,并把这个消息广播出去,其他网络节点收到这个消息后,如果有意愿获取这个Blob交易网络包,可以通过“GetPooledTransactions”接口从有网络包的节点获取

3:以太坊出块节点从交易池中捞取到Blob交易网络包(假设出块节点已经获取到这个交易网络包),将网络包拆成Blob交易(Execution Payload)和Blob附加数据(Blobs)。Blob交易将和普通交易一样,打包到信标链区块中,并将Blob的KZG承诺置入信标区块头中;Blob附加数据转为一个名叫sidecar的对象(sidecar后面有描述),将sidecar和信标区块打包后一起广播,消息topic为“beacon_block_and_blobs_sidecar”

4:以太坊验证节点获取到消息后,准备验证区块,验证分为两个部分:
4.1 信标链区块内的执行payload验证,在执行层验证
4.2 信标链区块本身的验证,在共识层验证
4.3 sidecar的验证,在共识层验证,这里会验证Blob交易的versioned_hash、Blob数据、信标区块体内的Blob KZG承诺是否匹配

5:第三方也可以通过以太坊节点的beacon_block_and_blobs_sidecar_by_root获取sidecar相关数据

上述过程中引入了sidecar对象,其目的是为之后的danksharding打基础。在未来danksharding实施后,会有大量的Blob数据(远比现在大得多),这时和信标区块绑定传输将会造成巨大的带宽损耗,因此引入了sidecar机制。Blob数据通过sidecar单独传输将会有效的减轻这个问题

此外,以太坊网络中新增加一个topic : beacon_block_and_blobs_sidecar, 该topic用来标识信标区块和其sidecar绑定在一起的网络包。区块生成节点在生成信标区块后,同时会将blob数据打包成blob sidecar对象,该对象中包含信标区块根、slot、blob数据以及对于的KZG承诺证明,最后将信标区块和sidecar对象打包成SignedBeaconBlockAndBlobsSidecar,通过p2p广播出去,以下是一些实现细节,具体参考EIP-4844实现‌:

 class BlobsSidecar(Container):
     beacon_block_root: Root
     beacon_block_slot: Slot
     blobs: List[Blob, MAX_BLOBS_PER_BLOCK]
     kzg_aggregated_proof: KZGProof
 class SignedBeaconBlockAndBlobsSidecar(Container):
     beacon_block: SignedBeaconBlock
     blobs_sidecar: BlobsSidecar
 class BeaconBlockBody(Container):
     randao_reveal: BLSSignature
     eth1_data: Eth1Data  # Eth1 data vote
     graffiti: Bytes32  # Arbitrary data
     # Operations
     proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS]
     attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS]
     attestations: List[Attestation, MAX_ATTESTATIONS]
     deposits: List[Deposit, MAX_DEPOSITS]
     voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS]
     sync_aggregate: SyncAggregate
     # Execution
     execution_payload: ExecutionPayload  # [Modified in EIP-4844]
     bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES]
     blob_kzg_commitments: List[KZGCommitment, MAX_BLOBS_PER_BLOCK]  # [New in EIP-4844]

验证节点收到这个topic的网络包后,除了需要验证信标区块外,还需要验证sidecar对象,具体包括:

1、信标区块中的KZG承诺编码是否正确
2、信标区块中的KZG承诺是否和其blob交易的version hashes匹配
3、sidecar中的slot是否是当前slot
4、sidecar中的blob的值是否符合格式
5、sidecar中的KZG证明编码是否正确
6、sidecar中的blob是否满足信标区块的KZG承诺

此外,节点客户端还提供了几个额外的API接口,包括:

1、根据root获取信标区块和其sidecar: beacon_block_and_blobs_sidecar_by_root
2、根据起始slot和查询数量获取sidecar: blobs_sidecars_by_range


2.6 Blob Gas是怎么计算的?一切交给市场


对于普通交易来说,calldata数据会和整个交易一起计算Gas,其费用计算所需的gas price和整个交易共用同一个。但对于Blob交易来说,Blob数据的存储gas和calldata不同,其单独计算gas费用,具有独立的gas价格和最大限制,有自己的市场定价,不和整个交易共用一个gas price,而是交给市场决定。这一想法来源于V神发表的一篇文章‌。Blob存储的gas限制如下表所示:

在EIP-4844中,规定区块的Blob目标数量是2个,最大为4个,在未来danksharding真正实施后,这个值应该是会增大的

Blob gas费用计算规则

Blob的费用依旧使用gas支付,但这个gas费的单价是动态变化的,根据区块的历史平均blob数量来做调整,当区块的历史平均blob数量大于目标平均数量时,最新的区块blob gas单价会指数级调高,反之则会调低。因此从长远来看,区块的历史平均blob数量会趋于目标平均数量

具体实现如下:

 def validate_block(block: Block) -> None:
     ...
     for tx in block.transactions:
         ...
         // 计算交易发起者账户余额是否大于需要支付的gas
         assert signer(tx).balance >= tx.message.gas * tx.message.max_fee_per_gas + get_total_data_gas(tx) * tx.message.max_fee_per_data_gas
         // 确保交易发起者愿意支付当前的gas price
         assert tx.message.max_fee_per_data_gas >= get_data_gasprice(parent(block).header)
 def calc_data_fee(tx: SignedBlobTransaction, parent: Header) -> int:
     return get_total_data_gas(tx) * get_data_gasprice(header)
 def get_total_data_gas(tx: SignedBlobTransaction) -> int:
     return DATA_GAS_PER_BLOB * len(tx.message.blob_versioned_hashes)
 DATA_GAS_PER_BLOB = 2**17
 def get_data_gasprice(header: Header) -> int:
     return fake_exponential(
         MIN_DATA_GASPRICE,
         header.excess_data_gas,
         DATA_GASPRICE_UPDATE_FRACTION
     )

本文链接:https://www.defidaonews.com/article/6814368
转载请注明文章出处

下载
分享
收藏
阅读
上一篇
下一篇