Add opt-in bulk block streaming (-bulkblocksync)

A single getblockstrm request makes a peer stream a contiguous range of old
blocks back-to-back as ordinary BLOCK messages, amortizing the per-block
round-trip over the whole range instead of the MAX_BLOCKS_IN_TRANSIT_PER_PEER
window. This targets the bandwidth-delay-product ceiling that dominates IBD
from few/high-latency peers below the checkpoint.

Design (off by default; negotiated via a NODE_BULKBLOCKS service bit; the
default getdata IBD path is untouched when disabled):
- protocol: NODE_BULKBLOCKS service bit + getblockstrm/blockstream messages.
- requester: in SendMessages, after FindNextBlocksToDownload, when the first
  needed block is >= BULK_TIP_MARGIN (5000) below the network tip and the peer
  advertises the bit and we are in IBD, request a contiguous range (<=128
  blocks) instead of per-block getdata; mark the range in-flight.
- server: stream the range (caps 128 blocks / 8 MiB; reads outside cs_main;
  per-peer flood throttle), then a trailing blockstream header with the actual
  count sent. Self-suppresses while the server itself is in IBD.
- received blocks ride the existing BLOCK -> ProcessNewBlock path (fully
  validated; checkpoints below 2.84M still apply); the trailing header
  reconciles partial deliveries and the range is freed on a 90s timeout, so a
  partial/withheld/refused batch falls back to the normal path (no leak, no
  permanent gap, no disconnect). In-flight tracking is by literal hash, so a
  reorg cannot orphan range entries.

Hardened against the issues found in two adversarial review passes (drain vs
timeout, partial reconciliation, ownership-guarded frees, one-shot header,
reorg-proof helpers, cs_main hold). Validated end-to-end between two local
v1.0.3 nodes (128/128 and partial serves; height advanced; no errors).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-29 21:22:54 -05:00
parent 78ea2aac5b
commit 1f2b109d95
5 changed files with 269 additions and 4 deletions

View File

@@ -102,6 +102,22 @@ static const int DEFAULT_SCRIPTCHECK_THREADS = 0;
* ceiling at negligible bandwidth cost. */
static const int DEFAULT_MAX_BLOCKS_IN_TRANSIT_PER_PEER = 16;
extern int MAX_BLOCKS_IN_TRANSIT_PER_PEER;
/** Opt-in bulk block streaming (DragonX, -bulkblocksync). A single GETBLOCKSTREAM request makes a
* peer stream a contiguous range of old blocks as back-to-back BLOCK messages, amortizing the
* per-block round-trip over the whole range instead of the MAX_BLOCKS_IN_TRANSIT_PER_PEER window.
* OFF by default; negotiated via NODE_BULKBLOCKS; only used during IBD for blocks more than
* BULK_TIP_MARGIN below the active tip; never alters the default getdata path. */
static const bool DEFAULT_BULKBLOCKSYNC = false;
extern bool fBulkBlockSync;
/** Only bulk-stream blocks at least this far below the active tip (near-tip uses the normal path). */
static const int BULK_TIP_MARGIN = 5000;
/** Hard DoS cap: max blocks a single GETBLOCKSTREAM may request/serve. */
static const uint16_t BULK_MAX_BLOCKS_PER_REQUEST = 128;
/** Hard DoS cap: max total bytes streamed in response to one GETBLOCKSTREAM. */
static const size_t BULK_MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
/** Requester fallback: if a promised bulk range doesn't fully arrive within this many microseconds,
* free the in-flight range so the normal per-block path re-fetches it. */
static const int64_t BULK_RESPONSE_TIMEOUT_US = 90 * 1000000LL;
/** Timeout in seconds during which a peer must stall block download progress before being disconnected. */
static const unsigned int BLOCK_STALLING_TIMEOUT = 2;
/** Number of headers sent in one getheaders result. We rely on the assumption that if a peer sends