存款合约是 ETH 和验证者存款数据进入以太坊权益证明系统的链上入口。它不仅仅接收 ETH;它会验证每笔存款的结构,计算存款数据的 Merkle 根,并发出信标链客户端消费的事件。本文解释了存款数据格式、合约执行的验证规则,以及使用标准 JSON-RPC 调用验证存款的可复现流程。还涵盖了常见故障模式、合约能检查与不能检查的边界,以及如何为验证工作流选择可靠的 RPC 访问。
范围与存款合约的双重含义
在一般法律和金融用法中,存款合约是私人协议,一方将资金存放于另一方作为担保、预订付款或履约保证。该含义出现在财产预订、租赁保证金和银行存款协议中。本文不涵盖这些工具。它聚焦于区块链工程中占主导地位的技术含义:以太坊信标存款合约,这是一个部署在以太坊主网上的智能合约,接受质押存款并发出信标节点激活验证者所需的数据。
这种区分很重要,因为两个概念共享名称但几乎无其他共同点。法律存款合约由法院执行,并规定返还或没收条件。链上存款合约由字节码执行,并规定固定的 48 字节公钥、32 字节提款凭证、96 字节签名和 32 字节存款数据根。理解链上版本需要阅读其验证逻辑而非法律条款。
- 法律存款合约:私人协议,依赖司法管辖区,涵盖预订或担保资金。
- 以太坊存款合约:不可变智能合约,验证存款结构,发出 DepositEvent。
- 本指南仅涵盖链上合约及其周围的验证工作流。
存款合约实际验证的内容
存款合约刻意保持极简。对于每笔收到的存款,它检查 ETH 金额是否为 1 ETH 的整数倍,提供的存款数据根是否与根据公钥、提款凭证和签名计算出的哈希树根匹配,以及存款数据中编码的金额是否与发送的值匹配。任何检查失败,交易都会回滚。所有检查通过,合约将存款追加到内部 Merkle 树,更新存款计数,并发出包含公钥、提款凭证、金额、签名和索引的 DepositEvent。
关键的是,合约不验证 BLS 签名,不确认验证者是否有资格激活,也不检查提款凭证是否指向合法地址或有效的 BLS 提款密钥。这些检查稍后在链下由信标链客户端软件完成。这种分离是有意为之:签名验证成本高昂且属于共识层,而执行层合约仅保证数据完整性和事件发出。
- 链上强制执行:1 ETH 粒度、存款数据根匹配、金额一致性。
- 链上不强制执行:BLS 签名有效性、验证者激活资格、提款凭证所有权。
- 每笔存款发出:公钥、提款凭证、金额、签名、Merkle 索引。
存款数据格式与 Merkle 根
存款由四个字段定义:pubkey(48 字节)、withdrawal_credentials(32 字节)、signature(96 字节)和 amount(8 字节,小端序,以 Gwei 为单位)。存款数据根是这些字段的 SHA-256 哈希树根,使用整个共识层通用的 SSZ 容器规则计算。合约从 calldata 重新计算此根,并与 deposit_data_root 参数比较;不匹配则回滚交易。
合约还维护所有存款的 Merkle 树。每笔新存款插入下一个索引,合约存储重建根所需的分支。信标节点读取 DepositEvent 日志和存款计数以重建完整存款列表。这种设计意味着,只有当事件和 Merkle 状态一致时,存款才对共识层有用,这就是为什么验证应同时检查事件日志和存款计数,而不仅仅是交易收据。
验证工作流的前提条件
要遵循以下流程,你需要一个支持 eth_getLogs、eth_call 和 eth_getTransactionReceipt 的以太坊 JSON-RPC 端点。公共端点通常对日志查询限速,因此对于重复验证,请使用多链 RPC 端点指南中的托管端点,或如果需要可预测的吞吐量,则配置专用节点。你还需要存款交易哈希和你正在检查的网络的存款合约地址。
存款合约地址是网络特定的。主网和测试网使用不同的部署,因此在查询前务必根据网络的官方配置确认地址。不要未经检查就硬编码教程中的地址,而应与你实际使用的链核对。以下示例使用 RPC_URL 和 DEPOSIT_CONTRACT_ADDRESS 作为占位符;替换为你独立验证过的值。
- 支持日志和调用功能的 RPC 端点。
- 存款交易哈希。
- 目标网络已验证的存款合约地址。
- 可选:本地 SSZ 或哈希树根库,用于独立重新计算根。
使用 curl 和 JSON-RPC 验证存款
第一步检查是交易收据。成功的存款交易状态为 0x1,并包含至少一个由存款合约发出的日志。以下请求检索收据并确认合约发出了事件。用你的值替换占位符。
回滚的交易返回状态 0x0 且无存款事件。如果收据完全缺失,交易可能仍在待处理,或 RPC 节点尚未索引。在这种情况下,在断定存款失败之前,请针对不同端点重试。
curl -s -X POST "$RPC_URL" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_getTransactionReceipt",
"params": ["0xYOUR_TX_HASH"]
}'在 JavaScript 中读取 DepositEvent 日志
仅凭收据不能证明存款数据格式正确;它只证明交易成功。要检查存款负载,请解码 DepositEvent 日志。事件签名为 DepositEvent(bytes,bytes,bytes,bytes,bytes),五个数据字段分别是公钥、提款凭证、金额、签名和索引。以下示例使用 ethers 获取并解码日志。它打印字段,以便你与质押工具生成的存款数据文件进行比较。
针对上面使用的同一交易哈希运行此代码。如果解码的公钥与你打算存款的验证者密钥不匹配,请停止并调查,然后再假设存款有效。不匹配通常表明提交了错误的存款数据文件。
import { JsonRpcProvider, Interface } from "ethers";
const provider = new JsonRpcProvider(process.env.RPC_URL);
const depositContract = process.env.DEPOSIT_CONTRACT_ADDRESS;
const txHash = process.env.DEPOSIT_TX_HASH;
const iface = new Interface([
"event DepositEvent(bytes pubkey, bytes withdrawal_credentials, bytes amount, bytes signature, bytes index)"
]);
const receipt = await provider.getTransactionReceipt(txHash);
if (!receipt || receipt.status !== 1) {
throw new Error("Deposit transaction did not succeed");
}
for (const log of receipt.logs) {
if (log.address.toLowerCase() !== depositContract.toLowerCase()) continue;
const parsed = iface.parseLog(log);
console.log({
pubkey: parsed.args.pubkey,
withdrawalCredentials: parsed.args.withdrawal_credentials,
amountGwei: parsed.args.amount,
signature: parsed.args.signature,
index: parsed.args.index
});
}交叉检查存款计数和 Merkle 状态
存款合约暴露 get_deposit_count() 函数,返回迄今为止记录的存款数量,编码为小端序字节数组。读取它确认合约状态在你的交易后推进。以下 eth_call 检索原始返回数据;将其解码为小端序整数以获取计数。
如果需要更强证据,比较交易前后的计数。发出事件但未增加计数的存款将表明 RPC 不一致或重组,而非有效存款。对于高价值操作,查询两个独立端点并比较结果,然后再将存款视为最终。
curl -s -X POST "$RPC_URL" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_call",
"params": [{
"to": "'"$DEPOSIT_CONTRACT_ADDRESS"'",
"data": "0x621fd130"
}, "latest"]
}'预期验证结果
正确提交的存款产生状态为 0x1 的收据,来自存款合约的恰好一个 DepositEvent,解码的公钥与你的验证者密钥匹配,金额为 1 ETH 的整数倍,且存款计数增加一。提款凭证应与你预期的格式匹配:BLS 提款凭证为 0x00 前缀,执行层提款地址为 0x01 或 0x02 前缀。
如果所有这些都成立,存款的执行层部分就完成了。验证者不一定会立即激活;激活取决于信标链存款队列、验证者位置和网络条件。验证存款合约事件是激活的必要但不充分条件。
- 收据状态为
0x1。 - 来自预期合约地址的一个
DepositEvent。 - 解码的 pubkey 与预期验证者密钥匹配。
- 金额为 1 ETH 的整数倍且与存款数据匹配。
- 存款计数增加一。
排查常见存款失败
大多数存款失败属于少数几类。根不匹配导致的交易回滚通常意味着存款数据根计算错误,或存款数据文件在生成根后被编辑。金额错误导致的交易回滚意味着发送的 ETH 值不是 1 ETH 的整数倍,或与存款数据中编码的金额不匹配。成功交易但公钥意外通常意味着使用了错误的存款数据文件。
链上成功但从未激活的存款是另一类问题。常见原因包括无效的 BLS 签名、提款凭证不对应你控制的密钥,或验证者已在同一密钥下存款。这些问题在共识层诊断,而非通过读取存款合约。使用信标节点工具或验证者客户端的存款验证命令进行调查。
- 根不匹配:重新生成存款数据并重新计算根。
- 金额不匹配:确认 1 ETH 粒度和编码金额。
- 错误 pubkey:对照验证者密钥验证存款数据文件。
- 未激活:链下检查 BLS 签名和提款凭证。
局限性与权衡
存款合约的极简主义是一种特性,但它将责任转移给存款人。由于合约不验证签名,签名无效的存款仍会在链上成功并消耗 ETH,但验证者永远不会激活。此类错误没有链上退款路径。这就是质押工具在广播前本地生成并验证存款数据的原因。
通过 JSON-RPC 验证也有局限。日志查询可能被限速、修剪,或由落后于链头的节点提供。来自滞后节点的收据可能缺失或不一致。对于生产工作流,使用具有文档化一致性保证的端点,并考虑查询多个提供商。RPC 定价页面和API 服务概览描述了托管访问的选项,而网络目录有助于确认支持哪些网络。
大规模存款验证的后续步骤
如果你偶尔验证存款,上面的 curl 和 JavaScript 示例就足够了。如果你运营质押服务或监控许多验证者,构建一个小型验证服务,获取收据、解码事件,并与内部存款记录比较。将交易哈希、存款索引和解码的公钥一起存储,以便后续审计不依赖重新查询历史日志。
对于在存款窗口期间需要可预测 RPC 吞吐量的团队,请查看区块链节点托管指南并考虑专用基础设施。对于端点选择,RPC Assistant 可以根据你的网络和延迟要求推荐提供商;本文刻意避免指定单一提供商,因为正确选择取决于你的验证量和一致性需求。
- 将存款索引和解码的 pubkey 与交易哈希一起持久化。
- 对于高价值存款,查询两个独立端点。
- 监控存款计数变化作为一致性信号。
- 当验证量超过公共速率限制时,使用专用节点。