跳至主要內容
進階

Mempool Message

了解 P2P 協議中的 mempool 消息,用於請求對等節點的交易池內容。

8 分鐘

Mempool 消息是比特幣 P2P 協議中用於請求對等節點交易池內容的消息。 收到此請求後,節點會回應包含其 mempool 中所有交易 ID 的 inv 消息。

Mempool 消息概述

Mempool 消息的作用:

請求流程:
┌─────────┐    mempool      ┌─────────┐
│ Node A  │ ─────────────→  │ Node B  │
│         │  inv(tx1,tx2,..)│         │
│         │ ←─────────────  │         │
└─────────┘                 └─────────┘

用途:
1. 啟動時同步 mempool
2. 發現遺漏的交易
3. 錢包恢復時找回未確認交易
4. 調試和分析

特點:
- 可能返回大量 inv
- 需要後續 getdata 獲取交易
- 可以被限制或禁用

消息格式

Mempool 消息格式:

┌─────────────────────────────────────────┐
│ (empty payload - 0 bytes)               │
└─────────────────────────────────────────┘

完整 P2P 消息:
┌──────────────────────────────────────────────┐
│ magic (4 bytes)                              │
│ command: "mempool" (12 bytes, padded)        │
│ length: 0 (4 bytes)                          │
│ checksum: 0x5df6e0e2 (4 bytes)               │
│ payload: (empty)                             │
└──────────────────────────────────────────────┘

回應:
- inv 消息
- 包含所有 mempool 中的交易
- 使用 MSG_TX 或 MSG_WTX 類型

// 非常簡單的請求消息

回應處理

處理 mempool 請求:

def on_mempool_request(peer):
    # 檢查是否允許
    if not peer.relay_txes:
        return  # 對方不接受交易中繼

    # 收集 mempool 中的所有交易
    txids = []
    for tx in mempool.get_all():
        if peer.wants_wtxid:
            txids.append((MSG_WTX, tx.wtxid))
        else:
            txids.append((MSG_TX, tx.txid))

    # 發送 inv
    # 可能分多個消息(每個最多 50000 項)
    for batch in chunks(txids, MAX_INV_SZ):
        send_inv(peer, batch)

限制:
MAX_INV_SZ = 50000  # 每個 inv 最多項目

如果 mempool 很大:
- 可能發送多個 inv 消息
- 接收方需要處理大量 inv

使用場景

Mempool 消息的使用場景:

1. 節點啟動
   - 新啟動的節點 mempool 為空
   - 發送 mempool 請求獲取交易
   - 快速填充 mempool

2. 重新連接
   - 斷線後重新連接
   - 可能錯過了一些交易
   - 使用 mempool 請求補齊

3. 錢包恢復
   - 導入舊錢包
   - 可能有未確認交易
   - 請求 mempool 尋找相關交易

4. 分析工具
   - 區塊鏈分析
   - 費率估算
   - 網路監控

5. 礦池
   - 獲取可打包的交易
   - 構建區塊模板

限制與安全

Mempool 請求的限制:

1. Bloom Filter 關聯
   BIP-37 規定:
   - 如果設置了 bloom filter
   - mempool 回應會被過濾
   - 只返回匹配的交易

2. 速率限制
   - 不應頻繁請求
   - 可能被視為濫用
   - 某些節點可能忽略

3. 隱私考量
   - mempool 內容洩露交易狀態
   - 可能被用於追蹤
   - 一些節點禁用此功能

4. 頻寬考量
   - 大 mempool 可能包含數千交易
   - 回應可能很大
   - 後續 getdata 更大

配置選項:
# bitcoin.conf
# 禁止 bloom filter(隱含影響 mempool)
peerbloomfilters=0

// 一些隱私增強配置可能限制 mempool 請求

與 Bloom Filter 的關係

BIP-37 Bloom Filter 與 mempool:

設置 filter 後:
1. 發送 filterload 設置過濾器
2. 發送 mempool 請求
3. 回應只包含匹配的交易

流程:
┌─────────┐   filterload    ┌─────────┐
│ SPV     │ ─────────────→  │ Full    │
│ Wallet  │    mempool      │ Node    │
│         │ ─────────────→  │         │
│         │  inv(filtered)  │         │
│         │ ←─────────────  │         │
└─────────┘                 └─────────┘

這用於:
- SPV 錢包查找自己的交易
- 減少頻寬使用
- 只獲取相關交易

注意:
- BIP-37 有隱私問題
- 已被 BIP-157/158 取代
- 仍在使用但不推薦

調試與監控

# 監控 mempool 相關活動

# 查看本地 mempool 狀態
bitcoin-cli getmempoolinfo
{
  "loaded": true,
  "size": 5000,
  "bytes": 2500000,
  "usage": 8000000,
  "total_fee": 0.5,
  "maxmempool": 300000000,
  "mempoolminfee": 0.00001,
  "minrelaytxfee": 0.00001
}

# 獲取 mempool 中所有交易
bitcoin-cli getrawmempool
["txid1", "txid2", ...]

# 日誌設置
# bitcoin.conf
debug=mempool
debug=net

# 日誌輸出:
# "Received mempool request from peer=5"
# "Sending 3500 inv items for mempool request"

# 估計 mempool inv 大小
tx_count = 5000
inv_size = tx_count * 36  # 180 KB

實現細節

Bitcoin Core 中的實現:

// 處理 mempool 請求
void PeerManager::ProcessMempool(CNode& node) {
    // 檢查是否中繼交易
    if (!node.m_tx_relay) {
        return;
    }

    // 檢查連接是否建立
    if (!node.fSuccessfullyConnected) {
        return;
    }

    // 收集 mempool 交易
    std::vector<CInv> vtxid;
    {
        LOCK(mempool.cs);
        vtxid.reserve(mempool.size());

        for (const auto& entry : mempool.mapTx) {
            const uint256& hash = node.m_wtxid_relay ?
                entry.GetTx().GetWitnessHash() :
                entry.GetTx().GetHash();

            vtxid.push_back(CInv(
                node.m_wtxid_relay ? MSG_WTX : MSG_TX,
                hash));
        }
    }

    // 應用 bloom filter(如果有)
    if (node.m_bloom_filter) {
        // 過濾...
    }

    // 發送 inv
    connman.PushMessage(&node,
        CNetMsgMaker(node.GetCommonVersion())
            .Make(NetMsgType::INV, vtxid));
}

// 發送 mempool 請求
void PeerManager::SendMempool(CNode& node) {
    connman.PushMessage(&node,
        CNetMsgMaker(node.GetCommonVersion())
            .Make(NetMsgType::MEMPOOL));
}

最佳實踐

  • 謹慎使用:mempool 請求可能返回大量數據
  • 啟動時使用:主要在節點啟動時發送一次
  • 不要頻繁請求:可能被視為濫用
  • 處理大回應:準備處理數千個 inv
  • 考慮隱私:請求可能洩露信息

相關概念

  • Mempool:交易池
  • Mempool Policy:交易池策略
  • Inv Message:庫存通告
  • Bloom Filters:BIP-37 過濾器
  • Transaction Broadcast:交易廣播
已複製連結
已複製到剪貼簿