news 2026/9/12 9:42:28

FHEVM Listener Block Computer 交易类型编码规范:从 EIP-2718 到 Polygon PIP-74 的多链区块根校验实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FHEVM Listener Block Computer 交易类型编码规范:从 EIP-2718 到 Polygon PIP-74 的多链区块根校验实战解析

FHEVM Listener Block Computer 交易类型编码规范:从 EIP-2718 到 Polygon PIP-74 的多链区块根校验实战解析

【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm

本文以 fhevm 仓库中 Block Computer — Transaction Type Encoding Specification 为核心,系统讲解 listener 组件中EvmBlockComputer如何通过逐笔 RLP 重编码交易来复算区块的transactionsRoot,覆盖标准 Ethereum 类型 0–4、Optimism/Base 类型 126、Arbitrum 类型 100–106 以及 Polygon Bor 类型 127(PIP-74 state sync)的完整编码格式、字段映射与源码实现,并给出跳过机制、收据编码与测试验证的完整方案。读完本文,你将掌握跨链区块完整性校验中"未知交易类型"这一核心难题的工程化解法,并能直接在 evm_block_computer.rs 中定位每一处关键实现。

1. 背景与问题:Block Computer 为什么要逐类型重编码交易

1.1 区块完整性验证的三重根校验

fhevm 的 listener 组件负责监听 EVM 兼容链(Ethereum、Optimism、Arbitrum、Base、Polygon 等)上的区块与事件。为了保证从 RPC 拉取的数据没有被篡改,EvmBlockComputer 对每个区块执行三项独立校验:

  1. 交易根(transactionsRoot)校验:将区块内每一笔交易按规范 RLP 编码后,通过ordered_trie_root构造交易 trie 根,与区块头中记录的transactions_root比对;
  2. 收据根(receiptsRoot)校验:对每笔交易的收据同样 RLP 编码后构造 trie 根,与区块头receipts_root比对;
  3. 区块哈希(blockHash)校验:将 RPC 返回的 header 各字段重建为 alloy 的 consensusHeader,调用hash_slow()与 RPC 返回的hash比对。

其中,交易根校验要求精确重现每一笔交易的字节级编码,这正是整个系统的难点所在——因为交易编码格式随链、随 EIP、随硬分叉而不同。

1.2 当前实现的问题

从 evm_block_computer.rs 的safe_encode_transaction可以看出,实现只覆盖了三类情况:

  • 标准 Ethereum 类型 0–4:由 alloy 原生处理(AnyTxEnvelope::Ethereum+encoded_2718());
  • Optimism/Base 存款交易类型 126(0x7E):自定义encode_deposit_transaction
  • Arbitrum 内部交易类型 106(0x6A):自定义encode_arbitrum_internal_transaction

其余任何未知类型都会落入_分支,返回BlockVerificationError::UnsupportedTransactionType { tx_type, index }。正如规范文档所指出,截至 Polygon 主网区块 85,523,136,类型 127(0x7F,即 PIP-74 引入的 state sync 交易)就会触发该失败,从而阻塞 cursor 的正常处理。源码中的_分支注释也印证了这一点(evm_block_computer.rs):

// TODO: Arbitrum specific encoding for transactions. // 100 => encode_arbitrum_deposit_transaction(unknown, index), // 101 => encode_arbitrum_unsigned_transaction(unknown, from, index), // 102 => encode_arbitrum_contract_transaction(unknown, index), // 104 => encode_arbitrum_retry_transaction(unknown, from, index), // 105 => encode_arbitrum_submit_retryable_transaction(unknown, index), // TODO: Specific polygon 127 transaction. // 127 => encore transaction 127 type from polygon, source: raw json.

BlockVerificationError的完整定义位于 evm_block_computer.rs,其中包括UnsupportedTransactionTypeTransactionEncodingFailedTransactionFieldMissing等与交易编码直接相关的错误变体,以及三类根校验失配错误。

2. 完整交易类型清单

2.1 标准 Ethereum 类型 0–4(无需处理)

类型 0–4 由 alloy 通过AnyTxEnvelope::Ethereum → encoded_2718()原生处理,规范文档给出的编码如下:

类型EIP名称编码
0LegacyRLP([nonce, gasPrice, gasLimit, to, value, data, v, r, s])
12930Access list0x01 + RLP([chainId, nonce, gasPrice, gasLimit, to, value, data, accessList, signatureYParity, signatureR, signatureS])
21559Dynamic fee0x02 + RLP([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, accessList, signatureYParity, signatureR, signatureS])
34844Blob0x03 + RLP([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, accessList, maxFeePerBlobGas, blobVersionedHashes, signatureYParity, signatureR, signatureS])
47702Set code0x04 + RLP([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, accessList, authorizationList, signatureYParity, signatureR, signatureS])

这几类统称为 EIP-2718 的 "Typed Transaction Envelope" 体系:type_byte || rlp(payload)。类型 1–4 均在类型字节后紧跟一个 RLP 列表。

2.2 Optimism / Base 类型 126(0x7E,已实现)

类型名称编码
126 (0x7E)Deposit transaction0x7E + RLP([sourceHash, from, to, mint, value, gas, isSystemTx, data])

来源为 OP Stack 规范。一个关键细节是:from来自 RPC 恢复出的签名者(recovered signer),而不是OtherFields,因为存款交易没有签名。这一点在源码 encode_deposit_transaction 的注释与实现中均有体现:

fn encode_deposit_transaction( unknown: &UnknownTxEnvelope, from: Address, index: usize, ) -> Result<Vec<u8>, BlockVerificationError> { let fields = &unknown.inner.fields; let source_hash: B256 = extract_field(fields, "sourceHash", index)?; // Note: `from` is passed as parameter since it comes from the RPC response's signer field let to: Option<Address> = extract_optional_address(fields, "to"); let mint: U256 = extract_field(fields, "mint", index).unwrap_or_default(); let value: U256 = extract_field(fields, "value", index)?; let gas: u64 = extract_u64(fields, "gas", index)?; let is_system_tx: bool = extract_field(fields, "isSystemTx", index).unwrap_or(false); let data: Bytes = extract_field(fields, "input", index)?; // Note: "input" not "data" let mut payload = Vec::new(); source_hash.encode(&mut payload); from.encode(&mut payload); encode_optional_address(to, &mut payload); mint.encode(&mut payload); value.encode(&mut payload); gas.encode(&mut payload); is_system_tx.encode(&mut payload); data.encode(&mut payload); let mut result = vec![0x7e]; let header = alloy::rlp::Header { list: true, payload_length: payload.len(), }; header.encode(&mut result); result.extend_from_slice(&payload); Ok(result) }

注意其中的两个易错点:交易数据字段在 JSON RPC 中名为"input"而非"data"mintisSystemTx使用了unwrap_or_default()做容错(部分 RPC 可能不返回这两个字段)。

2.3 Arbitrum 类型 100–106

Arbitrum 的修改版 Geth 会把所有自定义字段以 JSON 形式序列化,因此以下类型都可以从OtherFields中编码出来,字段提取由 extract_field、extract_u64 与 extract_optional_address 三个辅助函数完成。其中extract_u64同时兼容十六进制字符串("0x...")与 JSON 数字两种 RPC 返回格式;extract_optional_address在字段缺失时返回None,RLP 编码为空的 bytes(对应合约创建场景)。

Type 100(0x64)— ArbitrumDepositTx

L1→L2 ETH 存款,经 bridge 产生,凡处理了 bridge 存款的区块都会出现。编码格式:

0x64 + RLP([chainId, l1RequestId, from, to, value])
字段JSON keyRust 类型备注
chainId"chainId"u64十六进制字符串
l1RequestId"requestId"B256
from"from"Address使用恢复出的签名者
to"to"Address恒存在(非可选)
value"value"U256
fn encode_arbitrum_deposit_transaction( unknown: &UnknownTxEnvelope, from: Address, index: usize, ) -> Result<Vec<u8>, BlockVerificationError> { let fields = &unknown.inner.fields; let chain_id: u64 = extract_u64(fields, "chainId", index)?; let l1_request_id: B256 = extract_field(fields, "requestId", index)?; let to: Address = extract_field(fields, "to", index)?; let value: U256 = extract_field(fields, "value", index)?; let mut payload = Vec::new(); chain_id.encode(&mut payload); l1_request_id.encode(&mut payload); from.encode(&mut payload); to.encode(&mut payload); value.encode(&mut payload); encode_typed_tx(0x64, payload) }
Type 101(0x65)— ArbitrumUnsignedTx

L1 用户经 bridge 调用 L2 合约,无签名(unsigned)。编码格式:

0x65 + RLP([chainId, from, nonce, gasFeeCap, gas, to, value, data])
字段JSON keyRust 类型备注
chainId"chainId"u64
from"from"Address使用恢复出的签名者
nonce"nonce"u64
gasFeeCap"maxFeePerGas"U256Arbitrum 在 JSON 中将 gasFeeCap 映射为 maxFeePerGas
gas"gas"u64
to"to"Option<Address>合约创建时可为 None
value"value"U256
data"input"Bytes注意是 "input" 而非 "data"
fn encode_arbitrum_unsigned_transaction( unknown: &UnknownTxEnvelope, from: Address, index: usize, ) -> Result<Vec<u8>, BlockVerificationError> { let fields = &unknown.inner.fields; let chain_id: u64 = extract_u64(fields, "chainId", index)?; let nonce: u64 = extract_u64(fields, "nonce", index)?; let gas_fee_cap: U256 = extract_field(fields, "maxFeePerGas", index)?; let gas: u64 = extract_u64(fields, "gas", index)?; let to: Option<Address> = extract_optional_address(fields, "to"); let value: U256 = extract_field(fields, "value", index)?; let data: Bytes = extract_field(fields, "input", index)?; let mut payload = Vec::new(); chain_id.encode(&mut payload); from.encode(&mut payload); nonce.encode(&mut payload); gas_fee_cap.encode(&mut payload); gas.encode(&mut payload); encode_optional_address(to, &mut payload); value.encode(&mut payload); data.encode(&mut payload); encode_typed_tx(0x65, payload) }
Type 102(0x66)— ArbitrumContractTx

L1 合约调用 L2 合约,与 ArbitrumUnsignedTx 类似,但用requestId替代nonce。编码格式:

0x66 + RLP([chainId, requestId, from, gasFeeCap, gas, to, value, data])
字段JSON keyRust 类型备注
chainId"chainId"u64
requestId"requestId"B256
from"from"Address使用恢复出的签名者
gasFeeCap"maxFeePerGas"U256
gas"gas"u64
to"to"Option<Address>
value"value"U256
data"input"Bytes
fn encode_arbitrum_contract_transaction( unknown: &UnknownTxEnvelope, from: Address, index: usize, ) -> Result<Vec<u8>, BlockVerificationError> { let fields = &unknown.inner.fields; let chain_id: u64 = extract_u64(fields, "chainId", index)?; let request_id: B256 = extract_field(fields, "requestId", index)?; let gas_fee_cap: U256 = extract_field(fields, "maxFeePerGas", index)?; let gas: u64 = extract_u64(fields, "gas", index)?; let to: Option<Address> = extract_optional_address(fields, "to"); let value: U256 = extract_field(fields, "value", index)?; let data: Bytes = extract_field(fields, "input", index)?; let mut payload = Vec::new(); chain_id.encode(&mut payload); request_id.encode(&mut payload); from.encode(&mut payload); gas_fee_cap.encode(&mut payload); gas.encode(&mut payload); encode_optional_address(to, &mut payload); value.encode(&mut payload); data.encode(&mut payload); encode_typed_tx(0x66, payload) }
Type 104(0x68)— ArbitrumRetryTx

重试执行失败的 L1→L2 retryable ticket。编码格式:

0x68 + RLP([chainId, nonce, from, gasFeeCap, gas, to, value, data, ticketId, refundTo, maxRefund, submissionFeeRefund])
字段JSON keyRust 类型备注
chainId"chainId"u64
nonce"nonce"u64
from"from"Address使用恢复出的签名者
gasFeeCap"maxFeePerGas"U256
gas"gas"u64
to"to"Option<Address>
value"value"U256
data"input"Bytes
ticketId"ticketId"B256Arbitrum 特有字段
refundTo"refundTo"AddressArbitrum 特有字段
maxRefund"maxRefund"U256Arbitrum 特有字段
submissionFeeRefund"submissionFeeRefund"U256Arbitrum 特有字段
fn encode_arbitrum_retry_transaction( unknown: &UnknownTxEnvelope, from: Address, index: usize, ) -> Result<Vec<u8>, BlockVerificationError> { let fields = &unknown.inner.fields; let chain_id: u64 = extract_u64(fields, "chainId", index)?; let nonce: u64 = extract_u64(fields, "nonce", index)?; let gas_fee_cap: U256 = extract_field(fields, "maxFeePerGas", index)?; let gas: u64 = extract_u64(fields, "gas", index)?; let to: Option<Address> = extract_optional_address(fields, "to"); let value: U256 = extract_field(fields, "value", index)?; let data: Bytes = extract_field(fields, "input", index)?; let ticket_id: B256 = extract_field(fields, "ticketId", index)?; let refund_to: Address = extract_field(fields, "refundTo", index)?; let max_refund: U256 = extract_field(fields, "maxRefund", index)?; let submission_fee_refund: U256 = extract_field(fields, "submissionFeeRefund", index)?; let mut payload = Vec::new(); chain_id.encode(&mut payload); nonce.encode(&mut payload); from.encode(&mut payload); gas_fee_cap.encode(&mut payload); gas.encode(&mut payload); encode_optional_address(to, &mut payload); value.encode(&mut payload); data.encode(&mut payload); ticket_id.encode(&mut payload); refund_to.encode(&mut payload); max_refund.encode(&mut payload); submission_fee_refund.encode(&mut payload); encode_typed_tx(0x68, payload) }
Type 105(0x69)— ArbitrumSubmitRetryableTx

创建 retryable ticket,并附带 L1→L2 费用托管。编码格式:

0x69 + RLP([chainId, requestId, from, l1BaseFee, depositValue, gasFeeCap, gas, retryTo, retryValue, beneficiary, maxSubmissionFee, feeRefundAddr, retryData])
字段JSON keyRust 类型备注
chainId"chainId"u64
requestId"requestId"B256
from"from"Address使用恢复出的签名者
l1BaseFee"l1BaseFee"U256Arbitrum 特有字段
depositValue"depositValue"U256Arbitrum 特有字段
gasFeeCap"maxFeePerGas"U256
gas"gas"u64
retryTo"retryTo"Option<Address>L2 上的目标地址
retryValue"retryValue"U256Arbitrum 特有字段
beneficiary"beneficiary"AddressArbitrum 特有字段
maxSubmissionFee"maxSubmissionFee"U256Arbitrum 特有字段
feeRefundAddr"feeRefundAddr"AddressArbitrum 特有字段
retryData"retryData"BytesArbitrum 特有字段
fn encode_arbitrum_submit_retryable_transaction( unknown: &UnknownTxEnvelope, from: Address, index: usize, ) -> Result<Vec<u8>, BlockVerificationError> { let fields = &unknown.inner.fields; let chain_id: u64 = extract_u64(fields, "chainId", index)?; let request_id: B256 = extract_field(fields, "requestId", index)?; let l1_base_fee: U256 = extract_field(fields, "l1BaseFee", index)?; let deposit_value: U256 = extract_field(fields, "depositValue", index)?; let gas_fee_cap: U256 = extract_field(fields, "maxFeePerGas", index)?; let gas: u64 = extract_u64(fields, "gas", index)?; let retry_to: Option<Address> = extract_optional_address(fields, "retryTo"); let retry_value: U256 = extract_field(fields, "retryValue", index)?; let beneficiary: Address = extract_field(fields, "beneficiary", index)?; let max_submission_fee: U256 = extract_field(fields, "maxSubmissionFee", index)?; let fee_refund_addr: Address = extract_field(fields, "feeRefundAddr", index)?; let retry_data: Bytes = extract_field(fields, "retryData", index)?; let mut payload = Vec::new(); chain_id.encode(&mut payload); request_id.encode(&mut payload); from.encode(&mut payload); l1_base_fee.encode(&mut payload); deposit_value.encode(&mut payload); gas_fee_cap.encode(&mut payload); gas.encode(&mut payload); encode_optional_address(retry_to, &mut payload); retry_value.encode(&mut payload); beneficiary.encode(&mut payload); max_submission_fee.encode(&mut payload); fee_refund_addr.encode(&mut payload); retry_data.encode(&mut payload); encode_typed_tx(0x69, payload) }
Type 106(0x6A)— ArbitrumInternalTx(已实现)
0x6A + RLP([chainId, data])

这是当前仓库中已落地实现的 Arbitrum 类型,对应 encode_arbitrum_internal_transaction,字段提取为chainId(u64)与input(Bytes),随后按0x6a类型字节 + RLP 列表组装。

2.4 Polygon Bor 类型 127(0x7F)— PIP-74 State Sync

类型 127 由Madhugiri 硬分叉(2024 年 12 月,激活区块 80,084,800)引入,对应 PIP-74 提案"在区块体中规范化纳入 StateSync 交易"。编码格式:

类型名称编码
127 (0x7F)State sync (PIP-74)0x7F + RLP([\[encStateSyncData, ...]])

其中每个encStateSyncData由四部分构成:

字段类型描述
IDuint64State sync 事件 ID
ContractaddressL2 上的接收合约
DatabytesABI 编码的 payload
TxHashbytes32L1 交易哈希

该类型无法从 JSON RPC 编码——内部 payload 不在交易字段中。RPC 返回的是一个归一化视图:from=0x0, to=0x0, gas=0, input=0x, v/r/s=0,真实的 state sync 数据只能通过eth_getRawTransactionByHash获取。

State sync 交易出现在sprint 区块上(Polygon 每 16 个区块一个 sprint,即区块号可被 16 整除时)。规范文档给出了区块 85,523,136 中 index 375 交易的真实原始字节样例:

0x7f <- type byte f9025f <- RLP list header (607 bytes) f9013d <- first encStateSyncData 83 30364e <- ID: 3159630 94 a6fa...c0aa <- Contract: 0xa6fa4fb5f76172d178d61b04b0ecd319c5d1c0aa b90100 87a7811f... <- Data: 256 bytes a0 378a5f6c...1e1e <- TxHash: 0x378a5f6c... f9011c <- second encStateSyncData 83 30364f <- ID: 3159631 94 8397...8a28a <- Contract: 0x8397259c983751daf40400790063935a11afa28a b8e0 0000... <- Data: 224 bytes a0 458eea10...a51f <- TxHash: 0x458eea10...
支持类型 127 的三种方案

方案 A — 跳过交易根校验(当前采用)

遇到类型 0x7F 时,跳过整个区块的交易根校验。区块哈希校验 + 收据根校验仍然执行。其中,区块哈希校验本身足以证明区块头的真实性(包括头部中存储的transactionsRoot)。代价是:恶意 RPC 可能在不被发现的情况下为含 state-sync 的区块提供被篡改的交易——这在信任 RPC 端点的前提下可以接受。

方案 B — 通过eth_getRawTransactionByHash获取原始字节

SemEvmRpcProvider中新增get_raw_transaction_by_hash方法;fetcher 在调用校验前扫描未知类型并获取原始字节,通过HashMap<B256, Vec<u8>>传给 block computer。需要改动三处:

  • sem_evm_rpc_provider.rs:新增get_raw_transaction_by_hash
  • evm_block_fetcher.rs:新增fetch_raw_unknown_txs,并将build_fetched_block改为 async;
  • evm_block_computer.rsverify_block/verify_transaction_root/safe_encode_transaction增加raw_txs参数。

代价是每个 state-sync 区块多一次 RPC 调用,且需要改动 fetcher 架构。

方案 C — 从收据日志重建

收据日志包含 state sync 事件 ID(位于0x0000...1001合约事件 topics 中)和合约地址,但不包含完整 payload 数据与 L1 交易哈希,因此不可行

2.5 其他链情况汇总

自定义类型?说明
BNB/BSC仅标准类型 0–2
Avalanche C-Chain仅标准类型 0–2(自定义区块头在verify_block_hash中处理)
Monad标准类型 0–4
Base仅类型 126与 Optimism 相同(OP Stack)
zkSync类型 113 (0x71)EIP-712 zkSync 交易,除非新增 zkSync 支持否则无关
Polygon zkEVM仅标准类型

其中 Avalanche 的情况值得注意:虽然交易类型是标准的,但其自定义区块头需要在verify_block_hash中特殊处理,当前实现先尝试标准哈希,失败后再尝试 Avalanche 变体(对应BlockVerificationError::BlockHashVerificationExhausted,见 evm_block_computer.rs)。

3. 共享 RLP 助手 encode_typed_tx

为减少各编码器之间的重复代码,规范建议抽取一个共享的encode_typed_tx

/// Build a typed transaction envelope: type_byte + RLP list wrapping the payload. fn encode_typed_tx(type_byte: u8, payload: Vec<u8>) -> Result<Vec<u8>, BlockVerificationError> { let mut result = vec![type_byte]; let header = alloy::rlp::Header { list: true, payload_length: payload.len(), }; header.encode(&mut result); result.extend_from_slice(&payload); Ok(result) }

同时把已有的encode_deposit_transaction(0x7E)与encode_arbitrum_internal_transaction(0x6A)也重构到该助手之上。从当前源码看,这两个函数仍各自内联了vec![0x7e]/vec![0x6a]+Header的组装逻辑(evm_block_computer.rs 与 evm_block_computer.rs),与文档中的重构建议一致,属于待落地的优化项。

4. 跳过机制设计:让未知类型不再阻塞

4.1 现状:allow_skipping 参数与调用链

当前仓库中,跳过机制已经部分落地。verify_block接受allow_skipping: bool参数(evm_block_computer.rs):开启时三项校验任一失败只打 ERROR 日志并继续,同时递增对应指标;关闭时任一失败立即返回错误(stalling=true)。

规范文档指出一个关键缺口:当前verify_block确实捕获了verify_transaction_root的错误并继续,但错误仍会从safe_encode_transaction一路传播出来。文档给出的目标流程为:

safe_encode_transaction └── type 127 or _ → Err(UnsupportedTransactionType { tx_type, index }) verify_transaction_root └── on UnsupportedTransactionType: error!(tx_type, index, block_number, "Skipping tx root verification: ...") return Ok(()) // skip, don't propagate verify_block (unchanged) └── verify_transaction_root → Ok (skipped) or Ok (verified) └── verify_receipt_root → must pass └── verify_block_hash → must pass

即:把"跳过"的判定点从verify_block下移到verify_transaction_root,针对UnsupportedTransactionType这一特定错误返回Ok(()),而其他错误(如根失配)仍然传播,从而避免"因一个未知类型而丢弃整个区块的根校验"或"把真正的根失配也一并吞掉"两种极端。

规范还建议使用 ERROR 日志保证可见性,并新增listener_block_verification_skipped_total计数器(labels:chain_id,reason)用于告警。

4.2 配置入口:compute_block_allow_skipping

该跳过行为在配置层面对应compute_block_allow_skipping,定义于 config.rs,默认值为true(见default_compute_block_allow_skipping)。注释明确指出其用途正是"Polygon type 0x7F transaction root 等场景下跳过校验并以 ERROR 日志代替硬失败"。相关示例配置可见 config-polygon-compute.yaml、config-avalanche.yaml 等:

compute_block: true compute_block_allow_skipping: true

该配置经由 evm_listener.rs 传入 fetcher 的with_verify_block_allow_skipping,最终进入EvmBlockFetcher::FetchConfig.verify_block_allow_skipping(evm_block_fetcher.rs),在抓取区块后调用EvmBlockComputer::verify_block(evm_block_fetcher.rs)。整条链路为:

config.compute_block_allow_skipping → EvmListener::build_fetcher (evm_listener.rs) → FetchConfig.verify_block_allow_skipping (evm_block_fetcher.rs) → EvmBlockComputer::verify_block(..., allow_skipping, chain_id) (evm_block_computer.rs)

4.3 可观测性:metric 与仪表盘

当前实现中实际使用的指标为listener_compute_transaction_failure_totallistener_compute_receipt_failure_totallistener_compute_block_failure_total,均带chain_idstalling两个 label。stalling=false表示被宽容跳过的数据质量问题,stalling=true表示硬停机(不变量被破坏)。dashboard.md 的告警约定是:stalling=true的 compute failure必须恒为零,一旦出现即为不变量违规,应立即调查;而stalling=false在 L2 上允许存在且与链相关,若持续增长则需排查 RPC 数据质量,很可能是不受支持的 L2 交易类型。

5. 自定义类型的收据编码:无需改动

收据编码对所有非 0x7E 类型统一使用AnyReceiptEnvelope::encoded_2718(),产出type_byte || rlp(status, cumulativeGasUsed, bloom, logs)。这适用于:

  • Arbitrum 类型 100–106:标准收据格式,只是类型字节不同;
  • Polygon 类型 127:标准收据格式(status=1、gasUsed=0、标准 logs)。

因此上述所有类型都不需要修改收据编码。唯一需要特殊处理的是 Optimism 特有的存款收据(类型 0x7E,含depositNonce/depositReceiptVersion两个 Canyon 升级字段)——该逻辑已实现于 encode_deposit_receipt 与 encode_receipt:

// Check for L2 deposit receipt (type 126) if receipt_type == 0x7e { let deposit_nonce: Option<u64> = receipt .other .get("depositNonce") .and_then(|v| v.as_str()) .and_then(|s| u64::from_str_radix(s.trim_start_matches("0x"), 16).ok()); // ... if deposit_nonce.is_some() || deposit_receipt_version.is_some() { return encode_deposit_receipt(&consensus_receipt, deposit_nonce, deposit_receipt_version); } }

encode_receipt会先判断收据类型是否为 0x7E,若存在depositNonce/depositReceiptVersion则走 L2 专用编码,否则回落到标准encoded_2718()

6. 测试与验证策略

6.1 Polygon state sync 区块

选定的测试区块为 Polygon 主网85,523,136(0x518FAC0),共 376 笔交易,index 375 是类型 0x7F。sprint 区块每 16 个区块出现一次:

TX hash: 0x4f8e7a02f12c3573bf9a7c83ac19f77f0537d614a1f05f68b39278cc02d652e5 RPC: https://polygon-bor-rpc.publicnode.com

测试需验证三点:

  1. verify_block不报错(交易根被跳过,收据根 + 区块哈希通过);
  2. verify_receipt_root独立通过;
  3. verify_block_hash独立通过。

6.2 Arbitrum

report_block_verification测试中启用 "Arbitrum One"(目前注释在 evm_block_computer_tests.rs),使用https://arb1.arbitrum.io/rpc。多数区块包含类型 106(internal),偶尔出现类型 100、104、105。

该测试框架的整体设计(evm_block_computer_tests.rs)也值得借鉴:它对每个目标链抓取最新区块,以三层回退策略抓取收据(先eth_getBlockReceipts,再批量eth_getTransactionReceipt,最后逐笔调用),然后分别独立报告交易根 / 收据根 / 区块哈希三项校验结果,便于精准定位是哪一层失败。

6.3 字段映射验证

对每个新增的 Arbitrum 编码器,从 Arbiscan 挑选一笔该类型的真实交易,进行双重比对:

  1. 通过eth_getBlockByNumber(..., true)抓取——核对 JSON 字段名与规范一致;
  2. 通过eth_getRawTransactionByHash抓取——将我们的编码结果与原始字节比对。

只有编码结果与原始字节完全一致,才能证明字段顺序、类型宽度与 RLP 头都正确。

7. 参考与延伸阅读

  • PIP-74:Canonical Inclusion of StateSync Transactions in Block Bodies(Polygon 提案,Madhugiri 硬分叉引入,Bor v2.5.0 实现);
  • Arbitrum Nitroarb_types.go:Arbitrum 修改版 Geth 中全部自定义交易类型的权威定义(类型 100–106 的字段来源);
  • Arbitrum inside Nitro文档:L1→L2 消息流、retryable ticket 与各交易类型的业务语义;
  • EIP-2718 Typed Transaction Envelope:标准类型 0–4 统一采用type_byte || rlp(payload)的编码基础。

仓库内可直接延伸阅读的配套文档与实现:

  • 本规范文档:spec-block-computer-tx-types.md;
  • 核心实现:evm_block_computer.rs;
  • RPC provider(SemEvmRpcProvider,信号量限流的 RPC 客户端):sem_evm_rpc_provider.rs;
  • 区块抓取器(含 5 种抓取策略与校验入口):evm_block_fetcher.rs;
  • 校验测试:evm_block_computer_tests.rs;
  • 配置项compute_block_allow_skipping:config.rs;
  • 校验失败指标与告警约定:dashboard.md。

综上,Block Computer 的交易类型编码是跨链区块完整性校验中最具挑战性的部分:标准链依赖 alloy 原生支持,L2 链则需要精确还原各自修改版客户端的私有交易格式。理解本文的编码表与字段映射,你就掌握了为 listener 新增任意 EVM 兼容链支持时最核心的"字节级还原"能力。

【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 9:37:07

Dify工作流进阶指南:节点原理、调优与智能工单实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 9:36:35

零基础用ESP32+MAX30102实现心率检测

1. 为什么是ESP32 MAX30102&#xff1f;——从“听心跳”这个说法讲起你第一次看到“让ESP32拥有‘听心跳’的能力”这个说法&#xff0c;可能会下意识皱眉&#xff1a;ESP32是块开发板&#xff0c;又没耳朵&#xff0c;怎么听&#xff1f;MAX30102是个传感器&#xff0c;它连…

作者头像 李华
网站建设 2026/9/12 9:34:30

MAX v24.2.1 发布解析:从 `max.graph.ops` 直接导入 Graph 算子

MAX v24.2.1 发布解析&#xff1a;从 max.graph.ops 直接导入 Graph 算子 【免费下载链接】mojo The Modular Platform (includes MAX & Mojo) 项目地址: https://gitcode.com/GitHub_Trending/mo/mojo 导读 本文聚焦 MAX 平台 v24.2.1 版本发布说明中的一项核心 A…

作者头像 李华
网站建设 2026/9/12 9:33:52

职业转型的底层逻辑与高成功率路径设计

1. 职业转型的底层逻辑分析"趁早转行"这个观点背后反映的是当前就业市场的结构性变化。从职业发展角度看&#xff0c;每个行业都有其生命周期曲线&#xff0c;从业者需要敏锐察觉行业拐点。我观察到一个现象&#xff1a;当某个领域的初级岗位开始批量消失时&#xff…

作者头像 李华