区块和交易从出块节点到达全网的数万个节点,需要经过精妙的"中继策略"——如何优先传播高手续费交易?如何避免带宽浪费?如何确保全网对区块的接受时间尽可能一致?本节深入比特币与以太坊的消息中继经济学。
6.4.1 "首次传播"优先与库存向量的防重机制
在 P2P 网络中,同一个交易或区块可能通过多条路径到达同一个节点。为避免重复处理与传输,比特币网络为每个节点维护一个库存向量(Inventory Vector):
每个传入的 inv 消息都会通过该集合过滤。如果哈希已存在,则静默丢弃,不发 getdata。
首次传播(First-Seen)优先规则:
- 同一个交易哈希首次到达时,该节点将其标记为"已知";
- 如果同一笔交易稍后以不同版本(如 malleated 版本)到达,由于签名不同导致哈希不同,通常被视为新交易——但这恰好是交易延展性(Malleability)的核心风险;
- 在 SegWit 激活后,签名精确性消除了大部分延展性空间,使得"首次传播"规则更加可靠。
flowchart TD
A[收到 inv 消息] --> B{hash 在 inventory 中?}
B -->|是| C[丢弃]
B -->|否| D[加入 inventory]
D --> E[发送 getdata 请求]
E --> F[收到完整数据]
F --> G[验证通过后转发 inv 给邻居]
style B fill:#ffff99
6.4.2 交易池(Mempool)管理与手续费率排序
全节点接收到通过验证的交易后,将其存入本地内存池(Mempool)。Mempool 不是简单的 FIFO 队列,而是按手续费率(sat/vByte or gwei/gas)排序的优先级队列。
Bitcoin 的 Mempool 排序规则
其中 virtual size 在 SegWit 后按字节计,见证字节更便宜(75% 折扣)。
矿工从 Mempool 中按此优先级选取交易构建区块,直到填满 1-4MB 的区块空间。这形成了一种实时市场竞价。
以太坊的 Mempool 排序规则
以太坊采用 gas price 排序,自 EIP-1559 后简化为优先费(priority fee):
基础费用(base fee)会被销毁,不进入矿工钱包——这是为了消除矿工激励与交易包含之间的博弈。
最大容量与驱逐策略
Mempool 内存有限。当接近上限时,节点采用最低费率驱逐(Lowest-fee-rate eviction):
新交易的优先级必须严格高于被驱逐交易的最低值,否则直接拒绝。这确保了网络资源始终被"最有价值的交易"占用。
// mempool-priority-sim.ts
// 纯内置:模拟按手续费率排序的 mempool 与区块构建
class Mempool {
private txs: Array<{ id: string; feeRate: bigint; size: number }> = [];
private maxSize: number;
constructor(maxSize: number) { this.maxSize = maxSize; }
addTx(tx: { id: string; feeRate: bigint; size: number }): 'accepted' | 'rejected' {
if (this.currentSize() + tx.size > this.maxSize) {
// 驱逐最低费率交易
this.txs.sort((a, b) => (a.feeRate < b.feeRate ? -1 : 1));
const victim = this.txs[0];
if (!victim || tx.feeRate > victim.feeRate) {
if (victim) this.txs.shift();
} else {
return 'rejected';
}
}
this.txs.push(tx);
this.txs.sort((a, b) => (a.feeRate < b.feeRate ? 1 : -1)); // 降序
return 'accepted';
}
buildBlock(maxBlockSize: number): string[] {
const selected: string[] = [];
let size = 0;
for (const tx of this.txs) {
if (size + tx.size > maxBlockSize) break;
size += tx.size;
selected.push(tx.id);
}
return selected;
}
currentSize(): number { return this.txs.reduce((s, t) => s + t.size, 0); }
dump() {
console.log(`Mempool: {this.currentSize()} / ${this.maxSize}`);
for (const tx of this.txs.slice(0, 5)) {
console.log(` {tx.feeRate} sat/vB, ${tx.size} vB`);
}
}
}
// 模拟
const pool = new Mempool(5000); // 5,000 vByte 容量
const txs = [
{ id: 'txA', feeRate: 10n, size: 500 },
{ id: 'txB', feeRate: 30n, size: 250 },
{ id: 'txC', feeRate: 5n, size: 1000 },
{ id: 'txD', feeRate: 50n, size: 300 },
{ id: 'txE', feeRate: 20n, size: 400 },
];
txs.forEach(tx => pool.addTx(tx));
pool.dump();
const block = pool.buildBlock(1000); // 1,000 vByte 区块
console.log('Selected for block:', block);
// 输出:txD (300) + txB (250) + txE (400),总 950 vByte,
// 验证了按费率排序而非按到达时间排序6.4.3 交易验证层级:从语法到共识
交易在进入 Mempool 前必须通过至少三层验证:
| 层级 | 检查项 | 开销/严重性 |
|---|---|---|
| 语法检查 | 签名格式、脚本语法、字段长度 | 极低 / 低 |
| 上下文无关检查 | 双重花费(输入引用的 UTXO 是否已被用?) | 中 / 高 |
| 上下文相关检查 | 输入引用的 UTXO 是否在主链上、是否被确认 | 高 / 极高 |
| 共识规则检查 | 时间锁、脚本执行结果、区块大小等 | 极高 / 致命 |
graph LR
A[收到交易] --> B[语法校验]
B --> C[签名验证]
C --> D[UTXO 检查<br>双花检测]
D --> E[脚本/合约执行]
E --> F[通过?]
F -->|是| G[加入 Mempool]
F -->|否| H[拒绝并惩罚来源]
style F fill:#ff9900,color:#fff
6.4.4 紧凑区块(Compact Block):带宽优化极限
当矿工出块后,全网多数节点的 Mempool 中已经包含该区块的大部分交易。比特币 BIP152 引入了紧凑区块编码,避免在区块中附带完整交易数据:
| 字段 | 说明 | 大小 |
|---|---|---|
| Header | 80 字节标准区块头 | 80 B |
| Short IDs | 6 字节短标识符(短哈希/随机种子) | 6 B × tx_count |
| Prefilled | 不在收件方 Mempool 中的交易(完整数据) | 可变 |
接收方根据短标识符从本地 Mempool 中"配对"交易,只需请求缺失的几笔交易。带宽从 ~1-4MB 降至 ~10KB:
关键认知:中继策略不是简单的"尽快发出去",而是经济驱动的优先级排序。Mempool 的费率排序揭示了区块链的微观经济学——区块空间是最稀缺的资源,交易手续费是其价格信号。
← 6.3 Gossip 协议 | 前往 → 6.5 网络层攻击与防御
评论
0评论加载中…