跳至主要內容
高級

Peer Management

深入了解 Bitcoin Core 的對等節點管理,連接類型、驅逐策略和網路拓撲。

12 分鐘

什麼是 Peer Management?

Peer Management 是 Bitcoin Core 管理與其他節點連接的系統。 它決定連接到哪些節點、維護多少連接、以及何時斷開或驅逐節點, 目標是維護健康、多樣化的網路連接。

連接類型

類型 預設數量 功能
Outbound Full-Relay 8 完整中繼(區塊、交易、地址)
Outbound Block-Relay 2 只中繼區塊
Inbound 最多 117 接受其他節點的連接
Feeler 1-2 測試新地址的短連接
Manual 不限 用戶手動添加

出站連接

class OutboundConnectionManager {
  // 出站連接目標
  private readonly FULL_OUTBOUND_CONNECTIONS = 8;
  private readonly BLOCK_RELAY_CONNECTIONS = 2;

  // 選擇出站連接時確保多樣性
  async selectOutboundPeer(): Promise {
    const connectedGroups = this.getConnectedGroups();

    for (let attempts = 0; attempts < 100; attempts++) {
      // 從 AddrMan 選擇地址
      // 50% 機率從 New 表,50% 從 Tried 表
      const addr = this.addrman.select();
      if (!addr) continue;

      // 檢查是否已連接
      if (this.isConnected(addr)) continue;

      // 確保網段多樣性(/16 網段)
      const group = this.getGroup(addr);
      if (connectedGroups.has(group)) {
        // 同一網段已有連接
        continue;
      }

      // 檢查是否被封禁
      if (this.isBanned(addr)) continue;

      return addr;
    }

    return null;
  }

  // 維護出站連接
  async maintainOutbound(): Promise {
    const fullRelayCount = this.countByType('full-relay');
    const blockRelayCount = this.countByType('block-relay');

    // 補充 full-relay 連接
    while (fullRelayCount < this.FULL_OUTBOUND_CONNECTIONS) {
      const peer = await this.selectOutboundPeer();
      if (peer) {
        await this.connectTo(peer, 'full-relay');
      } else {
        break;  // 無法找到更多節點
      }
    }

    // 補充 block-relay 連接
    while (blockRelayCount < this.BLOCK_RELAY_CONNECTIONS) {
      const peer = await this.selectOutboundPeer();
      if (peer) {
        await this.connectTo(peer, 'block-relay');
      } else {
        break;
      }
    }
  }
}

入站連接

class InboundConnectionManager {
  private readonly MAX_INBOUND = 117;  // 125 總連接 - 8 出站

  // 接受入站連接
  onIncomingConnection(socket: Socket): boolean {
    const addr = socket.remoteAddress;

    // 1. 檢查是否被封禁
    if (this.isBanned(addr)) {
      socket.close();
      return false;
    }

    // 2. 檢查是否達到限制
    if (this.inboundCount >= this.MAX_INBOUND) {
      // 嘗試驅逐一個現有連接
      if (!this.evictInbound()) {
        socket.close();
        return false;
      }
    }

    // 3. 每個網段的連接限制
    const group = this.getGroup(addr);
    if (this.countInboundByGroup(group) >= 2) {
      // 同一 /16 網段最多 2 個入站連接
      socket.close();
      return false;
    }

    // 接受連接
    this.addInbound(socket);
    return true;
  }
}

驅逐策略

當需要為新連接騰出空間時,Bitcoin Core 使用複雜的驅逐算法, 保護有價值的節點(提供區塊快、交易多、連接時間長)。

class EvictionManager {
  // 驅逐入站連接以騰出空間
  evictInbound(): boolean {
    let candidates = this.getInboundPeers();

    // 保護各類有價值的節點
    // 每一步都移除一些候選者

    // 1. 保護最近發送區塊的節點(4 個)
    candidates = this.removeTopN(candidates, 4, peer =>
      peer.lastBlockTime
    );

    // 2. 保護最近發送交易的節點(4 個)
    candidates = this.removeTopN(candidates, 4, peer =>
      peer.lastTxTime
    );

    // 3. 保護 ping 時間最低的節點(4 個)
    candidates = this.removeTopN(candidates, 4, peer =>
      -peer.pingTime  // 負數,最低的會被保護
    );

    // 4. 保護連接時間最長的節點(8 個)
    candidates = this.removeTopN(candidates, 8, peer =>
      peer.connectedTime
    );

    // 5. 保護來自本地網路的節點
    candidates = candidates.filter(peer =>
      !peer.isLocal
    );

    // 6. 保護 Onion 節點(最多 4 個)
    const onionPeers = candidates.filter(p => p.isTor);
    if (onionPeers.length > 4) {
      candidates = candidates.filter(p => !p.isTor);
    }

    // 從剩餘候選者中選擇要驅逐的
    if (candidates.length === 0) {
      return false;  // 無法驅逐
    }

    // 選擇來自最大網段組的節點
    const victim = this.selectFromLargestGroup(candidates);
    this.disconnect(victim);
    return true;
  }
}

保護優先級

  • 1 最近發送區塊的節點(快速傳播)
  • 2 最近發送交易的節點(活躍中繼)
  • 3 低延遲節點(網路質量好)
  • 4 長期連接節點(穩定可靠)
  • 5 Tor 節點(網路多樣性)

Feeler 連接

// Feeler 連接用於測試 AddrMan 中的地址
class FeelerConnection {
  // 定期從 New 表選擇地址測試
  async runFeeler(): Promise {
    // 從 New 表選擇一個地址
    const addr = this.addrman.selectFromNew();
    if (!addr) return;

    try {
      // 嘗試連接
      const socket = await this.connect(addr, { timeout: 5000 });

      // 完成握手
      await this.handshake(socket);

      // 連接成功 - 將地址移到 Tried 表
      this.addrman.good(addr);

      // 立即斷開(feeler 只是測試)
      socket.close();
    } catch (error) {
      // 連接失敗 - 從 AddrMan 移除或降低優先級
      this.addrman.attempt(addr);
    }
  }
}

// Feeler 每 2 分鐘運行一次

管理命令

# 查看所有連接的節點
bitcoin-cli getpeerinfo

# 簡化輸出
bitcoin-cli getpeerinfo | jq '.[] | {addr, connection_type, ping: .pingtime}'

# 手動添加節點
bitcoin-cli addnode "192.168.1.100:8333" "add"

# 查看手動添加的節點
bitcoin-cli getaddednodeinfo

# 手動斷開節點
bitcoin-cli disconnectnode "192.168.1.100:8333"

# 查看網路資訊
bitcoin-cli getnetworkinfo

# 查看連接數統計
bitcoin-cli getnetworkinfo | jq '.connections, .connections_in, .connections_out'

配置選項

# bitcoin.conf

# 最大連接數
maxconnections=125

# 最大上傳流量(MB/天,0 = 無限制)
maxuploadtarget=5000

# 手動指定節點
addnode=192.168.1.100:8333

# 只連接到指定節點
connect=192.168.1.100:8333

# 白名單(不會被驅逐或封禁)
whitelist=192.168.1.0/24

# 監聽入站連接
listen=1

# 綁定地址
bind=0.0.0.0:8333

# 使用 Tor
proxy=127.0.0.1:9050
onlynet=onion

總結

  • 出站連接:8 個 full-relay + 2 個 block-relay
  • 入站連接:最多 117 個,有驅逐策略
  • 驅逐保護:保護有價值的節點
  • Feeler:定期測試新地址
已複製連結
已複製到剪貼簿