進階
Orphan Transactions
深入了解 Bitcoin Core 如何處理孤立交易,即引用未知父交易的交易。
10 分鐘
什麼是 Orphan Transactions?
Orphan transactions(孤立交易)是指引用了未知輸入的交易。當節點收到一筆交易, 但其父交易(提供輸入的交易)尚未到達時,該交易會被暫時存放在孤立交易池中。
孤立交易場景:
正常順序:
TX_A (父) ──────→ TX_B (子)
先到達 後到達
✓ 接受 ✓ 接受(父已知)
亂序到達:
TX_B (子) ──────→ TX_A (父)
先到達 後到達
? 孤立! ✓ 接受
(父未知) ↓
TX_B 從孤立池移到 mempool 產生原因
- 1 網路傳播順序
子交易可能比父交易更早到達某些節點
- 2 CPFP(Child Pays For Parent)
用戶發送子交易來加速父交易確認
- 3 Package Relay
交易包中的依賴關係
處理機制
interface OrphanTx {
tx: Transaction;
fromPeer: number; // 來源節點
timeExpire: number; // 過期時間
missingInputs: string[]; // 缺失的父交易 ID
}
class OrphanPool {
private orphans: Map = new Map();
private byPrevOut: Map> = new Map();
// 限制
private readonly MAX_ORPHAN_TRANSACTIONS = 100;
private readonly ORPHAN_TX_EXPIRE_TIME = 20 * 60; // 20 分鐘
// 添加孤立交易
addOrphanTx(tx: Transaction, fromPeer: number): boolean {
const txid = tx.txid;
// 1. 檢查是否已存在
if (this.orphans.has(txid)) {
return false;
}
// 2. 檢查大小限制
if (tx.size > 100000) { // 100KB 限制
return false;
}
// 3. 如果池滿了,隨機驅逐一個
if (this.orphans.size >= this.MAX_ORPHAN_TRANSACTIONS) {
this.evictRandom();
}
// 4. 添加到池中
const orphan: OrphanTx = {
tx,
fromPeer,
timeExpire: Date.now() + this.ORPHAN_TX_EXPIRE_TIME * 1000,
missingInputs: this.findMissingInputs(tx),
};
this.orphans.set(txid, orphan);
// 5. 建立按父交易查詢的索引
for (const input of tx.inputs) {
const prevOutKey = `${input.txid}:${input.vout}`;
if (!this.byPrevOut.has(prevOutKey)) {
this.byPrevOut.set(prevOutKey, new Set());
}
this.byPrevOut.get(prevOutKey)!.add(txid);
}
return true;
}
// 當父交易到達時處理孤立交易
onParentReceived(parentTxid: string): Transaction[] {
const resolved: Transaction[] = [];
// 找到所有等待這個父交易的孤立交易
for (const orphan of this.orphans.values()) {
if (orphan.missingInputs.includes(parentTxid)) {
// 移除已解決的依賴
orphan.missingInputs = orphan.missingInputs.filter(
id => id !== parentTxid
);
// 如果所有依賴都解決了
if (orphan.missingInputs.length === 0) {
resolved.push(orphan.tx);
this.removeOrphan(orphan.tx.txid);
}
}
}
return resolved; // 返回可以處理的交易
}
// 定期清理過期的孤立交易
expire(): number {
const now = Date.now();
let removed = 0;
for (const [txid, orphan] of this.orphans) {
if (orphan.timeExpire < now) {
this.removeOrphan(txid);
removed++;
}
}
return removed;
}
} 限制與保護
孤立交易池限制
| 限制 | 值 | 目的 |
|---|---|---|
| 最大數量 | 100 筆 | 防止記憶體耗盡 |
| 單筆大小 | 100 KB | 防止大交易 DoS |
| 過期時間 | 20 分鐘 | 清理無效的孤立交易 |
| 每個節點限制 | 追蹤來源 | 防止單節點濫用 |
DoS 防護
- ✗ 攻擊者可能發送大量引用不存在父交易的孤立交易
- ✓ 隨機驅逐策略防止特定交易被保留
- ✓ 大小限制防止記憶體耗盡
- ✓ 過期機制自動清理
解決流程
class MempoolManager {
private mempool: Mempool;
private orphanPool: OrphanPool;
// 接收新交易
async processTransaction(tx: Transaction, fromPeer: number): Promise {
// 1. 嘗試添加到 mempool
const result = await this.mempool.acceptTransaction(tx);
if (result.success) {
// 2. 成功!檢查是否有孤立交易在等待這個
const resolved = this.orphanPool.onParentReceived(tx.txid);
// 3. 遞迴處理解決的孤立交易
for (const orphanTx of resolved) {
await this.processTransaction(orphanTx, fromPeer);
}
} else if (result.error === 'missing-inputs') {
// 4. 缺少父交易,添加到孤立池
this.orphanPool.addOrphanTx(tx, fromPeer);
// 5. 可選:請求缺失的父交易
for (const input of tx.inputs) {
if (!this.mempool.hasTx(input.txid)) {
this.requestTx(fromPeer, input.txid);
}
}
}
// 其他錯誤:丟棄交易
}
} 孤立交易 vs 孤立區塊
| 特性 | 孤立交易 | 孤立區塊 |
|---|---|---|
| 缺失 | 父交易 | 父區塊 |
| 儲存位置 | 記憶體 | 記憶體 |
| 最大數量 | 100 | 無硬限制 |
| 常見原因 | CPFP、網路延遲 | 競爭區塊、同步 |
總結
- ✓ 定義:引用未知父交易的交易
- ✓ 處理:暫存在孤立池,父交易到達後處理
- ✓ 限制:最多 100 筆,單筆最大 100KB
- ⚠ 過期:20 分鐘後自動清理
已複製連結