EIP-4844 开发使用介绍

  • Tiny熊
  • 更新于 2024-03-11 22:40
  • 阅读 2487

EIP-4844 即将采用,看看如何开发使用。

本文内容涉及:

  • Blob 交易格式
  • 如何发送 blob 交易?
  • 新操作码和预编译
  • Blob 浏览器
  • 如何查询 blob 内容?

Blob 交易格式

Blob 交易是符合 EIP-2718:类型交易封装 的一种新交易类型。该格式定义了交易及其收据如下:

交易结构

  • TransactionType:交易类型的唯一标识符,对于 blob 交易,交易类型设置为BLOB_TX_TYPE (0x3)

  • TransactionPayload:blob 交易的有效载荷(payload)结构如下:rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, data, access_list, max_fee_per_blob_gas, blob_versioned_hashes, y_parity, r, s])

    • max_fee_per_blob_gas (uint256):发送方愿意支付的最大 blob gas 费用。实际收取的费用是区块的 blob 基础费用。
    • blob_versioned_hashes:可用于验证 blob 内容完整性的哈希数组。每个哈希是一个 0x01 字节(表示版本),后跟 KZG 的 SHA256 哈希的最后 31 个字节。此方法设计用于与 EVM 兼容和未来兼容性

注意gas_limit不包括 blob gas,blob gas 单独计算,每个 blob 为131072 (0x20000)

交易收据结构

  • ReceiptPayload:blob 交易的收据有效载荷定义为:rlp([status, cumulative_transaction_gas_used, logs_bloom, logs])

注意cumulative_transaction_gas_used仅反映执行交易所使用的累积 gas,不包括 blob gas。

发送 Blob 交易

网络形式

在 EIP-4844 的网络层中,blob 交易使用不同的格式进行发送。协议要求执行节点在传播时检查 blob 交易的有效性。

  • 协议片段
    • 在交易 gossip 传播响应(PooledTransactions)期间,blob 交易的 EIP-2718 TransactionPayload被包装为:rlp([tx_payload_body, blobs, commitments, proofs])
    • 节点必须验证tx_payload_body并针对其验证包装数据。Geth 示例 。有关VerifyBlobProof的工作原理,请参阅 KZG-commitmenttrusted setups的介绍。

Curl

eth_sendRawTransaction

发送 blob 交易:

curl --data '{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x03fa..."],"id":1}' -H "Content-Type: application/json" -X POST $RPC_PROVIDER_URL | jq
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x50dc1e2ec14cafb5acac600debe7b8765c73cbb7105ea33121284c3538ffbbc6"
}

获取 blob 交易体(使用标准的 EIP-2718 blob 交易TransactionPayload):

curl --data '{"jsonrpc":"2.0","method":"eth_getTransactionByHash","params":["0x50dc1e2ec14cafb5acac600debe7b8765c73cbb7105ea33121284c3538ffbbc6"],"id":1}' -H 'Content-Type: application/json' -X POST $RPC_PROVIDER_URL | jq
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "blockHash": "0xdd59ee9b848353ce4b30a907582d0e90f387e3622b34ca38dde796ab124cd5db",
    "blockNumber": "0x51c6d4",
    "from": "0xd932073c0350d17057b6da602356b2ae92648465",
    "gas": "0x6270",
    "gasPrice": "0x5da256f",
    "maxFeePerGas": "0x357dee4a",
    "maxPriorityFeePerGas": "0x14bb44",
    "maxFeePerBlobGas": "0x4d29c618fa",
    "hash": "0x50dc1e2ec14cafb5acac600debe7b8765c73cbb7105ea33121284c3538ffbbc6",
    "input": "0x",
    "nonce": "0x20",
    "to": "0xd932073c0350d17057b6da602356b2ae92648465",
    "transactionIndex": "0x7c",
    "value": "0x0",
    "type": "0x3",
    "accessList": [],
    "chainId": "0xaa36a7",
    "blobVersionedHashes": [
      "0x01ce755b14983c26efbad511bb2594f9aba54d199ffe762b507a1b5a9d4b3a61"
    ],
    "v": "0x1",
    "r": "0xeeec1c9f227c6886c9901c2a6792e88f694abae4cd1d9e19a0cb284a9b4e8567",
    "s": "0x5375e093b941ab9a25f53548b5b8728f6f2fb8de4822342a2d699fda362b6c4c",
    "yParity": "0x1"
  }
}

获取 blob 交易收据:

curl --data '{"jsonrpc":"2.0","method":"eth_getTransactionReceipt","params":["0x50dc1e2ec14cafb5acac600debe7b8765c73cbb7105ea33121284c3538ffbbc6"],"id":1}' -H 'Content-Type: application/json' -X POST $RPC_PROVIDER_URL | jq
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "blobGasPrice": "0x80679abe1",
    "blobGasUsed": "0x20000",
    "blockHash": "0xdd59ee9b848353ce4b30a907582d0e90f387e3622b34ca38dde796ab124cd5db",
    "blockNumber": "0x51c6d4",
    "contractAddress": null,
    "cumulativeGasUsed": "0xae3bec",
    "effectiveGasPrice": "0x5da256f",
    "from": "0xd932073c0350d17057b6da602356b2ae92648465",
    "gasUsed": "0x5208",
    "logs": [],
    "logsBloom": "0x0000...",
    "status": "0x1",
    "to": "0xd932073c0350d17057b6da602356b2ae92648465",
    "transactionHash": "0x50dc1e2ec14cafb5acac600debe7b8765c73cbb7105ea33121284c3538ffbbc6",
    "transactionIndex": "0x7c",
    "type": "0x3"
  }
}

用于生成 curl 命令的 Go 代码:使用go run main.go运行。

包含生成的 curl 命令的脚本:使用./blob_eth_sendRawTransaction.sh运行。

Etherscan:搜索非 blob 字段的信息,目前提供更好的用户体验和更多信息。

Blobscan:搜索 blob 字段的信息。

注意:需要 RPC provider URL,或者需要运行节点来执行上述 curl 命令,并且仅在测试网中进行测试以防止资金损失。

eth_sendTransaction

发送 blob 交易:

curl --data '{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[{"accessList":[],"blobVersionedHashes":["0x01ce755b14983c26efbad511bb2594f9aba54d199ffe762b507a1b5a9d4b3a61"],"blobs":["0x0001..."],"chainId":"0xaa36a7","commitments":["0x854288889c16ba728d66f58ef6f40a2e0041a89e0453b1af934bf45c8a0e26e48e35cb3abade84db8b39d65b85265e3f"],"from":"0xd932073c0350d17057b6da602356b2ae92648465","gas":"0x6270","gasPrice":null,"hash":"0x23f2cbce16c8a144a653d9f919741143129d701f2cbe6cd7649b343ae6d0f0d3","input":"0x","maxFeePerBlobGas":"0x385d3c6730","maxFeePerGas":"0xed46be3a46","maxPriorityFeePerGas":"0x2540be400","nonce":"0x29","proofs":["0xb54876f23a0bcf4d95d05bafd3091676562447b3a31ae1caaad208fb794a53aad24336fe0c636a882081aa57d220abb4"],"r":"0x0","s":"0x0","to":"0xd932073c0350d17057b6da602356b2ae92648465","type":"0x3","v":"0x0","value":"0x0","yParity":"0x0"}],"id":1}' -H "Content-Type: application/json" -X POST http://127.0.0.1:8545 | jq
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x158173e2e27938f0605232e32f5fd524506439b7555d027b273bb70d07a3c899"
}

eth_sendTransaction的 JSON 有效载荷:

{
  "jsonrpc": "2.0",
  "method": "eth_sendTransaction",
  "params": [
    {
      "accessList": [],
      "blobVersionedHashes": [
        "0x01ce755b14983c26efbad511bb2594f9aba54d199ffe762b507a1b5a9d4b3a61"
      ],
      "blobs": [
        "0x0001..."
      ],
      "chainId": "0xaa36a7",
      "commitments": [
        "0x854288889c16ba728d66f58ef6f40a2e0041a89e0453b1af934bf45c8a0e26e48e35cb3abade84db8b39d65b85265e3f"
      ],
      "from": "0xd932073c0350d17057b6da602356b2ae92648465",
      "gas": "0x6270",
      "gasPrice": null,
      "hash": "0x23f2cbce16c8a144a653d9f919741143129d701f2cbe6cd7649b343ae6d0f0d3",
      "input": "0x",
      "maxFeePerBlobGas": "0x385d3c6730",
      "maxFeePerGas": "0xed46be3a46",
      "maxPriorityFeePerGas": "0x2540be400",
      "nonce": "0x29",
      "proofs": [
        "0xb54876f23a0bcf4d95d05bafd3091676562447b3a31ae1caaad208fb794a53aad24336fe0c636a882081aa57d220abb4"
      ],
      "r": "0x0",
      "s": "0x0",
      "to": "0xd932073c0350d17057b6da602356b2ae92648465",
      "type": "0x3",
      "v": "0x0",
      "value": "0x0",
      "yParity": "0x0"
    }
  ],
  "id": 1
}

获取 blob 交易体(使用标准的 EIP-2718 blob 交易TransactionPayload):

curl --data '{"jsonrpc":"2.0","method":"eth_getTransactionByHash","params":["0x158173e2e27938f0605232e32f5fd524506439b7555d027b273bb70d07a3c899"],"id":1}' -H 'Content-Type: application/json' -X POST $RPC_PROVIDER_URL | jq
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "blockHash": "0x2daaeca77155d06e64c130170cb1b2f53ed8e26c0e02fe24b7d0208ecd782488",
    "blockNumber": "0x51e294",
    "from": "0xd932073c0350d17057b6da602356b2ae92648465",
    "gas": "0x6270",
    "gasPrice": "0x1ab5ea2e27",
    "maxFeePerGas": "0xed46be3a46",
    "maxPriorityFeePerGas": "0x2540be400",
    "maxFeePerBlobGas": "0x385d3c6730",
    "hash": "0x158173e2e27938f0605232e32f5fd524506439b7555d027b273bb70d07a3c899",
    "input": "0x",
    "nonce": "0x29",
    "to": "0xd932073c0350d17057b6da602356b2ae92648465",
    "transactionIndex": "0x16",
    "value": "0x0",
    "type": "0x3",
    "accessList": [],
    "chainId": "0xaa36a7",
    "blobVersionedHashes": [
      "0x01ce755b14983c26efbad511bb2594f9aba54d199ffe762b507a1b5a9d4b3a61"
    ],
    "v": "0x0",
    "r": "0xd20ae6b93cee8467802601846df41bac73948553ce513e7cbe0e1998ff7e6fb9",
    "s": "0x5edcbd6ccd4462d0a33a747a5d9bf5653703566808b38007e3ce4532a1611348",
    "yParity": "0x0"
  }
}

获取 blob 交易收据:

curl --data '{"jsonrpc":"2.0","method":"eth_getTransactionReceipt","params":["0x158173e2e27938f0605232e32f5fd524506439b7555d027b273bb70d07a3c899"],"id":1}' -H 'Content-Type: application/json' -X POST http://127.0.0.1:8545 | jq
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "blobGasPrice": "0x44831ac79",
    "blobGasUsed": "0x20000",
    "blockHash": "0x2daaeca77155d06e64c130170cb1b2f53ed8e26c0e02fe24b7d0208ecd782488",
    "blockNumber": "0x51e294",
    "contractAddress": null,
    "cumulativeGasUsed": "0xacdde",
    "effectiveGasPrice": "0x1ab5ea2e27",
    "from": "0xd932073c0350d17057b6da602356b2ae92648465",
    "gasUsed": "0x5208",
    "logs": [],
    "logsBloom": "0x0000...",
    "status": "0x1",
    "to": "0xd932073c0350d17057b6da602356b2ae92648465",
    "transactionHash": "0x158173e2e27938f0605232e32f5fd524506439b7555d027b273bb70d07a3c899",
    "transactionIndex": "0x16",
    "type": "0x3"
  }
}

用于生成 curl 命令的 Go 代码:使用go run main.go运行。

包含生成的 curl 命令的脚本:使用./blob_eth_sendTransaction.sh运行。

Etherscan:搜索非 blob 字段的信息,目前提供更好的用户体验和更多信息。

Blobscan:搜索 blob 字段的信息。

注意:大多数 RPC 提供程序(如 Infura 和 Alchemy)不提供eth_sendTransaction

注意:对于 Geth: 这是一个正在进行的功能,但几乎完成 ,目前需要调整一些代码。签名字段被忽略,Geth 将使用解锁的账户对交易进行签名。

Go-SDK(在底层使用eth_sendRawTransaction

构造非 blob 字段(与 EIP-1559 交易相同):

privateKey, err := crypto.HexToECDSA(os.Getenv("PRIVATE_KEY"))
if err != nil {
    log.Crit("failed to create private key", "err", err)
}
publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
    log.Crit("failed to cast public key to ECDSA")
}
fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)

client, err := ethclient.Dial(os.Getenv("RPC_PROVIDER_URL"))
if err != nil {
    log.Crit("failed to connect to network", "err", err)
}

chainID, err := client.NetworkID(context.Background())
if err != nil {
    log.Crit("failed to get network ID", "err", err)
}

nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
if err != nil {
    log.Crit("failed to get pending nonce", "err", err)
}

gasTipCap, err := client.SuggestGasTipCap(context.Background())
if err != nil {
    log.Crit("failed to get suggest gas tip cap", "err", err)
}

gasFeeCap, err := client.SuggestGasPrice(context.Background())
if err != nil {
    log.Crit("failed to get suggest gas price", "err", err)
}

gasLimit, err := client.EstimateGas(context.Background(),
    ethereum.CallMsg{
        From:       fromAddress,
        To:         &fromAddress,
        GasFeeCap:  gasFeeCap,
        GasTipCap:  gasTipCap,
        Value:      big.NewInt(0),
        // 这里提供 BlobHash 如果交易时合约调用,
        // 并且合约内使用 blobhash 操作码
    })
if err != nil {
    log.Crit("failed to estimate gas", "err", err)
}

构造 blob 字段:

// 估算待打包区块的 blobFeeCap  
parentHeader, err := client.HeaderByNumber(context.Background(), nil)
if err != nil {
    log.Crit("failed to get previous block header", "err", err)
}
parentExcessBlobGas := eip4844.CalcExcessBlobGas(*parentHeader.ExcessBlobGas, *parentHeader.BlobGasUsed)
blobFeeCap := eip4844.CalcBlobFee(parentExcessBlobGas)

blob := randBlob()
sideCar := makeSidecar([]kzg4844.Blob{blob})
blobHashes := sideCar.BlobHashes()

func makeSidecar(blobs []kzg4844.Blob) *types.BlobTxSidecar {
    var (
        commitments []kzg4844.Commitment
        proofs      []kzg4844.Proof
    )

    for _, blob := range blobs {
        c, _ := kzg4844.BlobToCommitment(blob)
        p, _ := kzg4844.ComputeBlobProof(blob, c)

        commitments = append(commitments, c)
        proofs = append(proofs, p)
    }

    return &types.BlobTxSidecar{
        Blobs:       blobs,
        Commitments: commitments,
        Proofs:      proofs,
    }
}

注意:一个 blob 交易可以有 0 到 6 个 blob,因为每个区块的最大 blob 数量为 MAX_BLOB_GAS_PER_BLOCK / GAS_PER_BLOB = 786432 / 131072 = 6。

注意:Geth 的交易池(最广泛采用的执行客户端)将拒绝0 blob 大小的 blob 交易,在将交易添加到交易池之前验证时返回 blobless blob transaction 错误( 验证交易之前添加到 tx pool)。

签名并发送交易:

tx := types.NewTx(&types.BlobTx{
    ChainID:    uint256.MustFromBig(chainID),
    Nonce:      nonce,
    GasTipCap:  uint256.MustFromBig(gasTipCap),
    GasFeeCap:  uint256.MustFromBig(gasFeeCap),
    Gas:        gasLimit * 12 / 10,
    To:         fromAddress,
    BlobFeeCap: uint256.MustFromBig(blobFeeCap),
    BlobHashes: blobHashes,
    Sidecar:    sideCar,
})

auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID)
if err != nil {
    log.Crit("failed to create transactor", "chainID", chainID, "err", err)
}

signedTx, err := auth.Signer(auth.From, tx)
if err != nil {
    log.Crit("failed to sign the transaction", "err", err)
}

err = client.SendTransaction(context.Background(), signedTx)
if err != nil {
    log.Crit("failed to send the transaction", "err", err)
}

完整实现:使用 go run main.go 运行。

费用估算和提高

估算 Blob 交易的费用

Blob 费用和 Gas

Blob 基础费用具有确定性计算方法:

def get_blob_base_fee(header: Header) -> int:
    return fake_exponential(
        MIN_BLOB_BASE_FEE,
        header.excess_blob_gas,
        BLOB_BASE_FEE_UPDATE_FRACTION
    )

def fake_exponential(factor: int, numerator: int, denominator: int) -> int:
    i = 1
    output = 0
    numerator_accum = factor * denominator
    while numerator_accum > 0:
        output += numerator_accum
        numerator_accum = (numerator_accum * numerator) // (denominator * i)
        i += 1
    return output // denominator

MIN_BLOB_BASE_FEE 为 1 wei。

excess_blob_gas 表示历史上比 TARGET_BLOB_GAS_PER_BLOCK * Totel Number of Blocks 多使用的“额外”累积的 gas,但它被限制为 0(>= 0)。

BLOB_BASE_FEE_UPDATE_FRACTION 为 3338477,它控制 blob 基础费用的增长比率。

fake_exponential 通过 Taylor 展开 确定性地(向下取整)计算 factor * e ** (numerator / denominator),以防止由于模拟指数函数的不同规则而导致共识分歧。

注意:blob 基础费用是基于 指数 EIP-1559 机制 计算的,其中 excess_blob_gas 将使 blob 基础费用增加到市场的预期价格。与此同时,每个区块的预期 blob 数量仍将是每个区块的目标的 Blob 数量,目前为 3。

Blob Gas:每个 Blob 131072(0x20000)gas,每字节 1 gas,但增加 gas 的最小单位是一个 Blob。

Gas 费用:Blob vs Calldata

Gas

  • Blob 存储:每字节大约 1 gas(因为字段为 BLS_MODULUS),按 Blob 单位收费。
  • Calldata:每个非零字节 16 gas,每个零字节 4 gas。

注意:充分利用每个 Blob,避免支付未使用空间的费用。

Gas 价格

  • Blob 交易:使用 Blob 基础费用计算成本。
  • EIP-1559 交易:成本由 EIP-1559 基础费用加上小费决定。

大小

  • Blob:每个 Blob > 127KiB 并且 < 128KiB,因为字段为 BLS_MODULUS
  • Calldata:受区块的 gas limit 限制,此外每个交易受执行客户端中的限制( 一个著名的 128KiB 限制 )。

结论

基于供需的多维费用市场 。难以事先确定哪个更便宜。

  • 一些直觉

    • Calldata 用于许多目的:合约调用、Rollup DA 等。→ blob 更便宜!
    • 仅 blob 承诺的 32 字节哈希存在于 EVM 中,设计用于 Rollup。→ blob 更便宜!
    • Blob 相对稀缺,目前目标为每个区块 3 个 Blob,而每个交易可以包含一个 Calldata 字段,每个区块可容纳数百个交易。→ 如果 blob 交易拥挤,Calldata 甚至可能更便宜!
  • 统计

    • Blobscan 的仪表板:SepoliaGoerli。请注意,Blob 中的零字节(不知道它是有效的 0 还是虚拟值)被视为 Calldata 中的零字节,因此节省的费用被高估。添加用于比较 gas 费用的仪表板将更有用。
  • 其他可能性

    • 使用私人交易服务(例如 flashbots),可以直接向构建者支付小费。

优先处理交易

与 EIP-1559 交易一样,只需增加有效小费:min(exec tip, exec cap - base fee)

  • GethNethermind 在从交易池中选择交易时使用优先小费。
  • 即使对于更复杂的 MEV 策略(例如解决多维背包问题),增加有效小费也会给区块构建者带来更高的收入。

提高待处理交易的费用(替换具有相同 nonce 的交易)

由于 blob 交易池对最低提高比率有限制(例如 GethNethermind),为了替换已发送的交易,需要至少将 exec tipexec capblob cap 提高至少 100%,这种防御措施是为了防止 DoS 攻击,因为 blob 交易的有效负载很大。

const escalateMultiplier = 2

// Bumping gas fee.
gasTipCap = new(big.Int).Mul(gasTipCap, big.NewInt(escalateMultiplier))
gasFeeCap = new(big.Int).Mul(gasFeeCap, big.NewInt(escalateMultiplier))
blobFeeCap = new(big.Int).Mul(blobFeeCap, big.NewInt(escalateMultiplier))

tx := types.NewTx(&types.BlobTx{
    ChainID:    uint256.MustFromBig(chainID),
    Nonce:      nonce,
    GasTipCap:  uint256.MustFromBig(gasTipCap),
    GasFeeCap:  uint256.MustFromBig(gasFeeCap),
    Gas:        gasLimit * 12 / 10,
    To:         fromAddress,
    BlobFeeCap: uint256.MustFromBig(blobFeeCap),
    BlobHashes: blobHashes,
    Sidecar:    sideCar,
})

auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID)
if err != nil {
    log.Crit("failed to create transactor", "chainID", chainID, "err", err)
}

signedTx, err = auth.Signer(auth.From, tx)
if err != nil {
    log.Crit("failed to sign the transaction", "err", err)
}

err = client.SendTransaction(context.Background(), signedTx)
if err != nil {
    log.Crit("failed to send the transaction", "err", err)
}

注意:替换待处理交易的罚金很高,通常发生在 Blob 交易拥挤时。可以先尝试重新提交交易,看看是否已被 Blob 交易池驱逐,否则提高 gas 价格。

注意:错误消息示例:replacement transaction underpriced: new tx gas fee cap 67186612857 &lt;= 44791075238 queued + 100% replacement penalty

基于 Blob 交易池实现的故障排除

通过gossip协议在以太坊网络中传播交易,并临时存储在交易池中。由于 blob 交易携带大量有效负载,主要客户端在其交易池中实施了某些限制。强调这些限制中的一些对故障排除和防止 blob 交易被拒绝或优先级降低(卡住)可能至关重要。

Geth(主要执行客户端,许多 RPC 提供者都是基于它的):

  • 一个地址不能同时在传统池和 blob 池中持有交易:address already reserved
  • 替换交易需要显著提高 exec tipexec capblob cap(100%):replacement transaction underpriced
  • 每个账户的最大待处理 blob 交易数量限制:account limit exceeded: pooled 16 txs
  • Blob 交易驱逐 依赖于每个账户的 3 个最低费用(exec tipexec capblob cap)。
  • 限制每个交易中 Blob 的数量最多为 6(区块中允许的最大数量):too many blobs in transaction: have 7, permitted 6
  • 排除非 blob 交易:blobless blob transaction
  • 不允许nonce 间隔的 blob 交易:nonce too high

参考Geth 的 blob pool "手册"

Nethermind(排名第二执行客户端):

  • 明确设置标志以启用 blob 池。
  • 地址不能同时在传统池和 blob 池中持有交易。
  • 每个账户待出处理的 blob 交易有最大数量限制。
  • 拒绝 MaxPriorityFeePerGas 低于 1 gwei 的 blob。
  • 不允许 nonce 间隔的 blob 交易。
  • 拒绝用较少的 blob 替换 blob 交易。

参考Blob Pool Unit Tests

新的操作码和预编译

BLOBHASH 操作码

EIP-4844 引入了 BLOBHASH 操作码,其 Gas成本为 3。合约可以使用它来检索交易 blob 的哈希。它接受一个 index 参数,指定 blob 的 index;如果 index 超出范围,则返回一个零的 bytes32 值。参见 Geth 实现

点评估(Point Evaluation)预编译

在 0x0A 处有一个预编译,用于验证 KZG 证明,该证明声称一个 blob(由承诺表示)在给定点上评估为给定值。每次调用消耗 50000 gas。

EIP-4844 中的演示代码

def point_evaluation_precompile(input: Bytes) -> Bytes:
    """
    给定与多项式 p(x) 相对应的承诺和 KZG 证明,验证 p(z) = y。
    还要验证所提供的承诺与所提供的版本控制哈希值(versioned_hash)是否匹配。 
    """
    #数据编码如下: versioned_hash | z | y | commitment | proof | with z and y being padded 32 byte big endian values
    assert len(input) == 192
    versioned_hash = input[:32]
    z = input[32:64]
    y = input[64:96]
    commitment = input[96:144]
    proof = input[144:192]

    # 验证承诺与 versioned_hash 是否匹配
    assert kzg_to_versioned_hash(commitment) == versioned_hash

    # 使用 z 和 y (大段格式) 验证 KZG 证明
    assert verify_kzg_proof(commitment, z, y, proof)

    #  返回 FIELD_ELEMENTS_PER_BLOB 和 BLS_MODULUS 扩展到 32 字节大端值
    return Bytes(U256(FIELD_ELEMENTS_PER_BLOB).to_be_bytes32() + U256(BLS_MODULUS).to_be_bytes32())

Geth 实现

示例

直接调用点评估预编译

pointEvaluationPrecompileAddress := common.HexToAddress("0x0A")
blob := randBlob()
sideCar := makeSidecar([]kzg4844.Blob{blob})
versionedHash := sideCar.BlobHashes()[0]
point := randFieldElement()
commitment := sideCar.Commitments[0]

proof, claim, err := kzg4844.ComputeProof(blob, point)
if err != nil {
    log.Crit("failed to create KZG proof at point", "err", err)
}

var calldata []byte
calldata = append(calldata, versionedHash.Bytes()...)
calldata = append(calldata, point[:]...)
calldata = append(calldata, claim[:]...)
calldata = append(calldata, commitment[:]...)
calldata = append(calldata, proof[:]...)

gasLimit, err := client.EstimateGas(context.Background(), ethereum.CallMsg{
    From:      fromAddress,
    To:        &pointEvaluationPrecompileAddress,
    GasFeeCap: gasFeeCap,
    GasTipCap: gasTipCap,
    Value:     big.NewInt(0),
    Data:      calldata,
})
if err != nil {
    log.Crit("failed to estimate gas", "err", err)
}

tx := types.NewTx(&types.DynamicFeeTx{
    ChainID:   chainID,
    Nonce:     nonce,
    GasTipCap: gasTipCap,
    GasFeeCap: gasFeeCap,
    Gas:       gasLimit,
    To:        &pointEvaluationPrecompileAddress,
    Value:     big.NewInt(0),
    Data:      calldata,
})

完整实现:使用 go run main.go 运行。

成功的示例(具有有效的 calldata):calldata + 转账(21000)+ 点评估预编译(50000)。

失败的示例(没有 calldata):回退,消耗了提供的所有 gas。

在合约内调用点评估预编译

一个玩具合约

// SPDX-License-Identifier: MIT
// EVM VERSION: cancun
// Enable optimization: 2000000
pragma solidity ^0.8.24;

contract PointEvaluationPrecompileDemo {
    address private constant POINT_EVALUATION_PRECOMPILE_ADDRESS = 0x000000000000000000000000000000000000000A;
    uint256 private constant BLS_MODULUS = 52435875175126190479447740508185965837690552500527637822603658699938581184513;
    uint256 private constant HASH_OPCODE_BYTE = 0x49;

    event ProofVerificationSuccess(bytes32 indexed versionedHash, uint256 indexed point, bytes32 indexed claim);
    event ProofVerificationFailure(bytes32 indexed versionedHash, uint256 indexed point, bytes32 indexed claim);

    function verifyProofAndEmitEvent(
        bytes32 claim,
        bytes memory commitment,
        bytes memory proof
    ) external {
        require(commitment.length == 48, "Commitment must be 48 bytes");
        require(proof.length == 48, "Proof must be 48 bytes");

        bytes32 versionedHash = blobhash(0);

        // Compute random challenge point.
        uint256 point = uint256(keccak256(abi.encodePacked(versionedHash))) % BLS_MODULUS;

        bytes memory pointEvaluationCalldata = abi.encodePacked(
            versionedHash,
            point,
            claim,
            commitment,
            proof
        );

        (bool success,) = POINT_EVALUATION_PRECOMPILE_ADDRESS.staticcall(pointEvaluationCalldata);

        if (success) {
            emit ProofVerificationSuccess(versionedHash, point, claim);
        } else {
            emit ProofVerificationFailure(versionedHash, point, claim);
        }
    }
}

部署的合约地址:尚未验证合约代码,因为 Etherscan 和 Blockscout 不支持 EVM VERSION: cancun例如

'fe'(Unknown Opcode)
LOG2
PUSH5 0x6970667358
'22'(Unknown Opcode)
SLT
SHA3
'b1'(Unknown Opcode)
PUSH25 0x82b1641cca545969b8e7de1dc1b6c11c110b36a71d2dda89e8
'b3'(Unknown Opcode)

使用 Go SDK 调用合约:硬编码 gas limit,因为 Geth 的 EstimateGas 实现在内部调用 blobhash 操作码的合约调用中存在问题(blobhash 字段未通过 RPC 传输),希望这已在 v1.13.13 版本的 geth 中得到修复

成功的示例

失败的示例:将声明数组中的第一个字节设置为 0,合约返回错误:error verifying kzg proof: can’t verify opening proof 代码参考

Blob 浏览器

  • BlobscanSepoliaGoerli
    • 区块:blob 大小、blob gas 价格、blob gas 使用量、blob gas 限制、blob 作为 calldata gas 等。
    • 交易:总 blob 大小、blob 费用、blob gas 使用量、blob gas 价格、blob 作为 calldata gas 等。
    • Blob:版本化哈希、承诺、大小、数据等。
    • 指标
      • 区块:区块数量、blob gas 使用量、blob 费用、blob gas 价格、节省的费用、节省的 gas 等。
      • 交易:交易数量、blob gas 费用、唯一发送方、唯一接收方等。
      • Blob:blob 数量、唯一 blob、blob 大小、平均 blob 大小等。
    • 开源支持私有部署

查询 Blob 内容

动机之一:从 DA 同步

如果所有节点都宕机,用户可以在自己的计算机上运行一个节点,从 DA 同步以恢复链的状态,然后将他们的资金从 L2 提取到 L1。

共识节点(未修剪的 Blob)

注意:流行的 RPC 提供商对共识客户端 API 的支持不佳。

Blob 服务提供商

注意:获取 blob 数据、kzg 承诺和 kzg 证明后,你可以在本地验证 blob 内容(因为 blob 哈希存储在链上),无需“信任”服务提供商。

注意:其他潜在的方式: 如果数据在 30 天后被删除,用户如何访问旧的 blob?


本翻译由 DeCert.me 协助支持, 在 DeCert 构建可信履历,为自己码一个未来。

点赞 4
收藏 5
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

1 条评论

请先 登录 后评论
Tiny熊
Tiny熊
0xD682...E8AB
登链社区发起人 通过区块链技术让世界变得更好而尽一份力。