面向开发者 / JSON-RPC
与以太坊的差异
Pickle 的形状像以太坊,但不等价于以太坊。这是一份完整清单,列出标准客户端会拿到意料之外的答案的每一处,按每一条用笨办法发现要花掉多少时间排序。
revert 原因无法解码
error.data 从不被填充
A revert arrives as message text - "execution reverted: 0x<returndata>" - and error.data is absent.
Libraries decode custom errors and revert strings out of error.data, so on this chain they cannot. ethers and viem will both surface the raw message instead of a decoded error. If you need the reason, parse the hex out of the message yourself and decode it against your own ABI.
如果你需要那个原因,返回数据就在消息文本里,你可以自己解码:
try {
await client.readContract({ /* … */ });
} catch (err) {
// "execution reverted: 0x08c379a0…"
const hex = String(err.message).match(/0x[0-9a-fA-F]+/)?.[0];
// Decode that hex against your own ABI's errors, or against Error(string).
}没有历史状态
eth_getBalance、eth_getTransactionCount、eth_getCode 和 eth_getStorageAt 会解析地址,然后读取最新发布的 快照。区块参数是被接受并被无声忽略的:一个请求区块 100 处余额的调用不会 报错,它回答的是当下的余额。
eth_call 和 eth_estimateGas 也一样,并且还会忽略你传给它们的 gas、gasPrice 和 nonce:模拟永远以 30,000,000 的区块 gas 上限、对着最新状态运行。
这一条会无声地失败,所以它排在第二
任何归档式的做法,读取一个历史余额、在一个过去的区块上重放状态、计算一份快照,在这里返回 的都是自信的、看起来合理的错误答案,而不是一个错误。任何涉及历史的东西,改从浏览器读。
区块标签会塌缩
latest、pending、safe 和 finalized 全都 解析到同一个东西:被保留的链尖。而 earliest 解析到最早的仍被保留的 区块,不是区块 0,所以它会随着窗口滑动而向前移动。
EIP-1898 对象是被接受的:{ blockNumber } 或{ blockHash },其中哈希通过索引解析,当它不再被保留时回答 null。其他任何东西都是 -32602 "block tag"。
一个靠区分 safe 与 finalized 来决定何时相信一个结果的客户端,从这条链上得不到任何信号。这里 的最终性是 L1 结算,通过这些标签根本看不见。
根与布隆都是零
在区块和回执上,stateRoot、transactionsRoot 和 receiptsRoot 都是零,而 logsBloom 是 512 个零字符。
两个后果。一个会校验根的客户端在这里什么都校验不了。而一个在取日志之前先按布隆过滤的客户端 什么都匹配不到:请用 eth_getLogs 过滤,它读的是一份真正的索引。
除此之外区块头是规规矩矩的 Cancun 形状,包括 mixHash、withdrawalsRoot、blobGasUsed、excessBlobGas 和 parentBeaconBlockRoot。mixHash 是承重的而不是装饰性的:没有它, 每一个基于 revm 的客户端,包括 forge script,都会在模拟之前就以 prevrandao not set 拒绝这个区块头。
回执与交易字段
- 不管你实际发的是什么信封,
type永远是"0x0",而一笔交易报告的是 单一的、扁平的gasPrice:回来的东西里没有maxFeePerGas、maxPriorityFeePerGas或accessList。带类型的交易在进来时是被 接受的;只是响应的形态不反映这个类型。 cumulativeGasUsed就是那笔交易自己的gasUsed,不是整个区块的 累计值。把它在一个区块上加起来不会重复计数,但也不会告诉你任何东西。- 在执行与 EVM 封存之间,
blockHash和blockNumber都是null。回执在那个窗口里就已经存在,所以「回执不为 null」和「已被包含进一个 区块」并不是同一个判断。 - 一笔交易会带上一些非标准的额外字段,
raw、miniBlockNumber、miniBlockHash,而回执会带上后两个。它们很有用,而且它们不可移植。 - 带
fullTransactions: true的eth_getBlockByNumber可能返回一个混合数组:回执仍被索引的交易是对象,已被逐出的则是裸的哈希字符串。 eth_getBlockReceipts会无声地略去已被逐出的回执,所以这个数组可能比区块的 交易列表更短。
logIndex 按交易编号
logIndex 是在它自己那份回执的日志上编号的,而不是在整个区块上。同一个区块里的 两条日志可以都是 logIndex 0。
如果你把一个事件库按 (blockNumber, logIndex) 做键,也就是索引器惯用的那个复合 主键,你会撞键。请把 transactionHash 也加进去。
pending 不是 mempool
这里没有一个可供 pending 的公开 mempool。一个哈希只有在交易已经执行并被索引之后,才会到达 eth_newPendingTransactionFilter 和 newPendingTransactions 订阅。
所以它是一条顶着误导性名字的已执行交易流。你没法在交易落地之前盯住进来的交易,而且通过这个 接口也没有任何东西可以抢跑。
用常量作答的方法
有两个方法看起来能用,返回的值却不是测量结果。两个都在参考里做了标记,而且两个都比一个会 报错的方法更危险:
eth_feeHistory只读blockCount,完全忽略newestBlock和rewardPercentiles,并把baseFeePerGas作为重复的0x1、把gasUsedRatio作为重复的0.0返回。eth_createAccessList不看参数也不看状态,直接返回{ accessList: [], gasUsed: "0x0" }。
第三组方法诚实地作答,只是答案是空的,因为那些概念不适用:eth_accounts 永远为 空,eth_mining 为 false,eth_hashrate 为零,而四个 uncle 方法在不 校验参数的情况下返回零或 null。
不存在的命名空间
rpc_modules 宣告 eth、net、web3 和 pickle。缺席且未实现的是:debug、trace、txpool 和 admin。所以没有 debug_traceTransaction: 要调用轨迹的话,用浏览器,它有。
那些签名方法是注册了的,并且刻意拒绝:eth_sendTransaction、eth_sign、eth_signTransaction、personal_sign 和 eth_coinbase 全都回答 -32601。节点不持有任何用户密钥:在本地签名, 然后用 eth_sendRawTransaction。