一笔以太坊交易从用户点击"发送"到被全网最终确认,要经历构造、签名、广播、入池、排序、执行、出块、收据生成、执行状态确认等十余个环节。理解这一完整生命周期,是理解以太坊用户体验、交易延迟以及矿工/提取者可提取价值(MEV)的前提。
7.7.1 交易构造与签名
发送者使用私钥对交易的RLP 编码哈希进行签名。以太坊使用 ECDSA(secp256k1),与比特币相同曲线,但签名字段包含 v, r, s,其中 v 编码链 ID 和奇偶性。
交易字段(EIP-1559 类型 2 交易):
| 字段 | 类型 | 说明 |
|---|---|---|
| chainId | uint64 | 链 ID(主网=1) |
| nonce | uint64 | 发送者账户序号 |
| maxPriorityFeePerGas | uint256 | 给验证者的小费 |
| maxFeePerGas | uint256 | 最高可接受单价 |
| gasLimit | uint64 | 交易消耗的 Gas 上限 |
| to | address / null | 接收者地址或 null(表示合约创建) |
| value | uint256 | 转移的 ETH(Wei) |
| data | bytes | 调用输入数据(calldata) |
| accessList | 列表 | EIP-2930 热状态预声明(可选) |
| v, r, s | 签名组件 | ECDSA 签名 |
graph LR
A[用户私钥] -->|签名| B[RLP 编码交易 + v,r,s]
B --> C[广播到 P2P 网络]
C --> D[节点接收]
D --> E{签名验证通过?}
E -->|是| F[加入 mempool]
E -->|否| G[丢弃]
style A fill:#ffcccc
style F fill:#ccffcc
7.7.2 Mempool 与交易选择
交易进入各全节点的 内存池(mempool/txpool)。按前述 EIP-1559 规则,交易池按有效价格排序:
交易池的复杂性:
- 地址内顺序:同一地址的 tx 必须按 nonce 严格顺序执行,nonce 不连续时后续交易挂起;
- 链上状态依赖:若某交易的
to字段是合约,其执行结果(如SSTORE)影响后续从同一地址发出的交易 Gas 估算; - PBS(Proposer-Builder Separation):在 PoS 中,Builder 专门构建最优价值区块(含 MEV 提取),而 Proposer(验证者)仅选择最高价值区块头,自己无法查看交易内容(防止时间窃贼攻击)。
7.7.3 区块纳入与状态执行
Builder/验证者从交易池中选择交易,按顺序逐个执行:
- 对每笔交易:
- 检查 nonce 匹配;
- 检查签名有效性;
- 检查余额 >= GasLimit × MaxFeePerGas + value;
- 按当前 stateRoot 执行 EVM(更新内存、堆栈、存储、返回数据);
- 生成交易回执(Receipt)。
- Gas 累积:所有交易 GasUsed 之和不超过区块 GasLimit(30M)。
- 状态提交:执行完所有交易后,计算新 stateRoot。
- 区块打包:将交易列表、交易根、收据根、新 stateRoot、时间戳、parentHash 封装为新区块。
graph TD
A[mempool 排序] --> B[选择前 N 笔]
B --> C[逐笔执行 EVM]
C --> D{执行成功?}
D -->|是| E[更新状态树]
D -->|否| F[回滚该交易状态\n但仍收取已耗 Gas]
E --> G[生成回执]
F --> G
G --> H[计算新 stateRoot]
H --> I[封装区块]
style E fill:#ccffcc
style F fill:#ffcccc
7.7.4 交易回执与状态确认
每笔交易都有一个回执(Receipt),包含:
| 字段 | 说明 |
|---|---|
| status | 1(成功)或 0(失败/回滚) |
| gasUsed | 该交易实际消耗的总 Gas |
| logsBloom | 256 字节过滤器,加速日志查询 |
| logs | 交易执行中触发的所有事件(LOG0-LOG4 输出) |
| cumulativeGasUsed | 区块中截止该交易的累计 Gas |
用户如何确认交易成功?
- 钱包向 RPC 节点查询
eth_getTransactionReceipt; - 检查
status === 1; - 可选:监听
logs中合约触发的事件(如Transfer事件)确认具体业务逻辑。
ts
// tx-receipt-verify-sim.ts
// 纯内置:模拟交易生命周期状态检查
interface TxReceipt {
status: 0 | 1;
gasUsed: number;
logs: Array<{ topic0: string; data: string }>;
blockNumber: number;
blockHash: string;
}
function confirmTransaction(
receipt: TxReceipt | null,
currentBlock: number,
requiredConfirmations: number
): { confirmed: boolean; safe: boolean; reason: string } {
if (!receipt) return { confirmed: false, safe: false, reason: '交易尚未入块' };
if (receipt.status === 0) return { confirmed: true, safe: false, reason: '交易执行失败(回滚)' };
const confs = currentBlock - receipt.blockNumber + 1;
const safe = confs >= requiredConfirmations;
return {
confirmed: true,
safe,
reason: safe
? `已确认 {requiredConfirmations}`
: `已入块但仅有 {requiredConfirmations} 个`,
};
}
const receipt: TxReceipt = {
status: 1, gasUsed: 21000, logs: [], blockNumber: 100, blockHash: '0xabc...',
};
console.log(confirmTransaction(receipt, 101, 12));
// 输出: 1 确认,未达 12 安全阈值
console.log(confirmTransaction(receipt, 112, 12));
// 输出: 12 确认,安全7.7.5 最终性确认:LMD-GHOST + Casper FFG
在 PoS 以太坊中,"多少确认才算安全"的标准与 PoW 不同。
- 区块 head:区块已被提出并传播;
- Justified:2/3 验证者对该区块的 epoch 投票确认;
- Finalized:该区块及其所有祖先区块被 2/3 验证者"最终化",理论上不可被回滚(除非 1/3+ 质押被罚没)。
graph LR
A[已提出] --> B[已证明<br>Attestation 2/3]
B --> C[已确定<br>Justified]
C --> D[已最终化<br>Finalized]
D --> E[不可逆转<br>除非 1/3+ 罚没]
style D fill:#ccffcc
style E fill:#ccffcc
- 交易所通常等待 12-20 个 slot(约 2.5-4 分钟)视为"入账确认";
- 开发者的用户体验建议:finalized 块前不要视为最终(但大多数日常应用 1-2 slot 即可)。
关键认知六:交易不是"发送即完成"。从用户签名到链上可见(~12 秒),从可见到不易回滚(~2 分钟),从不易回滚到最终化(~13 分钟)——每个阶段有不同的安全假设。理解这个阶梯,才能设计不会在高并发场景下出错的 DApp。
← 7.6 预编译合约 | 前往 → ch07-summary
评论
0评论加载中…