- 交易格式。
- 如何发送 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。
在 EIP-4844 的网络层中,blob 交易使用不同的格式进行发送。协议要求执行节点在传播时检查 blob 交易的有效性。
- 协议片段:
- 在交易 gossip 传播响应(
PooledTransactions
)期间,blob 交易的 EIP-2718TransactionPayload
被包装为:rlp([tx_payload_body, blobs, commitments, proofs])
。 - 节点必须验证
tx_payload_body
并针对其验证包装数据。Geth 示例 。有关VerifyBlobProof
的工作原理,请参阅 KZG-commitment 和trusted setups的介绍。
- 在交易 gossip 传播响应(
发送 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 命令,并且仅在测试网中进行测试以防止资金损失。
发送 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 将使用解锁的账户对交易进行签名。
构造非 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 基础费用具有确定性计算方法:
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。
- Blob 存储:每字节大约 1 gas(因为字段为
BLS_MODULUS
),按 Blob 单位收费。 - Calldata:每个非零字节 16 gas,每个零字节 4 gas。
注意:充分利用每个 Blob,避免支付未使用空间的费用。
- 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 甚至可能更便宜!
-
统计:
-
其他可能性:
- 使用私人交易服务(例如 flashbots),可以直接向构建者支付小费。
与 EIP-1559 交易一样,只需增加有效小费:min(exec tip, exec cap - base fee)
。
- Geth 和 Nethermind 在从交易池中选择交易时使用优先小费。
- 即使对于更复杂的 MEV 策略(例如解决多维背包问题),增加有效小费也会给区块构建者带来更高的收入。
由于 blob 交易池对最低提高比率有限制(例如 Geth 和 Nethermind),为了替换已发送的交易,需要至少将 exec tip
、exec cap
和 blob 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 <= 44791075238 queued + 100% replacement penalty
。
通过gossip协议在以太坊网络中传播交易,并临时存储在交易池中。由于 blob 交易携带大量有效负载,主要客户端在其交易池中实施了某些限制。强调这些限制中的一些对故障排除和防止 blob 交易被拒绝或优先级降低(卡住)可能至关重要。
- 一个地址不能同时在传统池和 blob 池中持有交易:
address already reserved
。 - 替换交易需要显著提高
exec tip
、exec cap
和blob cap
(100%):replacement transaction underpriced
。 - 每个账户的最大待处理 blob 交易数量限制:
account limit exceeded: pooled 16 txs
。 - Blob 交易驱逐 依赖于每个账户的 3 个最低费用(
exec tip
、exec cap
和blob cap
)。 - 限制每个交易中 Blob 的数量最多为 6(区块中允许的最大数量):
too many blobs in transaction: have 7, permitted 6
。 - 排除非 blob 交易:
blobless blob transaction
。 - 不允许nonce 间隔的 blob 交易:
nonce too high
。
- 明确设置标志以启用 blob 池。
- 地址不能同时在传统池和 blob 池中持有交易。
- 每个账户待出处理的 blob 交易有最大数量限制。
- 拒绝
MaxPriorityFeePerGas
低于 1 gwei 的 blob。 - 不允许 nonce 间隔的 blob 交易。
- 拒绝用较少的 blob 替换 blob 交易。
EIP-4844 引入了 BLOBHASH
操作码,其 Gas成本为 3。合约可以使用它来检索交易 blob 的哈希。它接受一个 index
参数,指定 blob 的 index
;如果 index
超出范围,则返回一个零的 bytes32 值。参见 Geth 实现。
在 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())
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
代码参考。
- Blobscan:Sepolia 和 Goerli。
- 区块: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 大小等。
- 开源: 支持私有部署 。
如果所有节点都宕机,用户可以在自己的计算机上运行一个节点,从 DA 同步以恢复链的状态,然后将他们的资金从 L2 提取到 L1。
- Beacon API 的 getBlobSidecars:
注意:流行的 RPC 提供商对共识客户端 API 的支持不佳。
注意:获取 blob 数据、kzg 承诺和 kzg 证明后,你可以在本地验证 blob 内容(因为 blob 哈希存储在链上),无需“信任”服务提供商。
注意:其他潜在的方式: 如果数据在 30 天后被删除,用户如何访问旧的 blob?。