{
 "number": 36350,
 "input_hash": "56892fa662a153bc",
 "model": "ledger",
 "prompt_hash": null,
 "created": "2026-10-23T01:17:19+00:00",
 "result": {
  "summary": "Tracks progress across both the presync and redownload phases of anti-DoS headers sync to grant bounded chain sync timeout renewals to slow outbound peers. In `PeerManagerImpl::ConsiderEviction`, if an outbound peer has not yet updated its best-known block index entry but has made headers sync progress since the timeout was set, `CHAIN_SYNC_TIMEOUT` is renewed for another 20-minute period up to a maximum of six times (`MAX_HEADERS_SYNC_TIMEOUT_RENEWALS`).",
  "problem": "While syncing below `-minimumchainwork`, anti-DoS headers sync holds headers in memory during presync and inside a redownload buffer, leaving `pindexBestKnownBlock` null for that peer. Chain sync eviction treats the peer as having an old or missing chain, triggering a 20-minute timeout followed by a 2-minute disconnect deadline. On high-latency networks like Tor or I2P where the ~480 mainnet round trips take longer than 22 minutes, nodes repeatedly evict active sync peers and discard all headers sync progress.",
  "card": "Prevents premature eviction of outbound peers during anti-DoS headers presync and redownload. When syncing headers below minimum chain work, headers are held without updating the block index, causing chain sync eviction to disconnect active peers after 22 minutes. On slow links like Tor or I2P, the ~480 round trips can take longer than 22 minutes, discarding all sync progress and repeating with subsequent peers. The change inspects headers sync progress during eviction checks and grants bounded 20-minute timeout renewals (up to six) as long as progress is being made, verified by functional tests.",
  "evidence": [
   "PR description: 'On mainnet presync is roughly 480 headers round trips. If that takes more than ~22 minutes (about 2.5 s per round trip, e.g. over slow Tor or I2P), the sync peer gets evicted before presync can finish, and the same thing can happen with the next peer. The chain sync logic is from #11490, presync came later in #25717, and the two were never connected.'",
   "PR description test results: 'On master the peer is disconnected after 24 minutes. With the fix it stays connected and presynced_headers keeps going up.'"
  ],
  "scope_notes": "The core logic change is only ~30 lines in headerssync.h and net_processing.cpp; the vast majority of the patch (+161 lines) consists of new functional tests in test/functional/p2p_headers_sync_with_minchainwork.py.",
  "changed_since_previous": "Added commit d59713ea51 to test_headers_sync_renewals_bounded, verifying that an outbound peer stays connected after 7 periods and disconnects after 8 periods to strictly enforce MAX_HEADERS_SYNC_TIMEOUT_RENEWALS.",
  "thread": {
   "last_event_at": "2026-10-22T07:04:35Z",
   "events": 7,
   "head_sha": "d59713ea518655031e2160026d85316ae19900fe",
   "schema_floor": 2,
   "schema": 2
  },
  "waiting_on": [
   {
    "at": "2026-10-22",
    "id": "c:6334875534",
    "on": "nothing",
    "url": "https://github.com/bitcoin/bitcoin/pull/36350#issuecomment-6334875534",
    "what": "all items settled and PR is ACKed"
   }
  ],
  "risk": {
   "level": "high",
   "touches": [
    "p2p"
   ],
   "why": "Mistakes in outbound peer eviction logic could allow slow or malicious peers to monopolize outbound sync slots or inadvertently stall initial chain synchronization."
  },
  "discussion": {
   "author_status": "last statement processed 2026-10-22",
   "open_concerns": [],
   "resolved_concerns": [
    "cyb3ralbert: A stalled peer that stops responding to getheaders remains connected indefinitely, holding the outbound slot. (fixed by 0xShadowX 2026-10-14)",
    "cyb3ralbert: Whether the Tor/I2P latency scenario is expected to apply when there is only a single outbound peer. (answered by 0xShadowX 2026-10-14)",
    "cyb3ralbert: Whether the eviction was observed on a real node or inferred from expected round-trip times. (answered by 0xShadowX 2026-10-14)",
    "cyb3ralbert: During the REDOWNLOAD phase, presync work stops growing and timeout renewals cease, risking eviction of slow peers before the redownload buffer releases headers. (fixed by 0xShadowX 2026-10-15)",
    "cyb3ralbert: Timeout renewals are unbounded, allowing a peer with slow but regular progress to hold an outbound slot indefinitely. (fixed by 0xShadowX 2026-10-15)",
    "cyb3ralbert: Call sync_with_ping() on each step or allow extra minutes to make test_presync_peer_stalled_is_evicted less timing-sensitive. (fixed by 0xShadowX 2026-10-15)",
    "cyb3ralbert: Pin the number of renewals in test_headers_sync_renewals_bounded so that the test fails if MAX_HEADERS_SYNC_TIMEOUT_RENEWALS is changed. (accepted by cyb3ralbert 2026-10-22)"
   ]
  },
  "reviewability": {
   "state": "Ready",
   "label": "CI failing",
   "reason": "CI is red; check whether the failure is the PR's before a deep review."
  },
  "agreement": {
   "state": "Strong",
   "summary": "Strong: substantive support, no open objection (cyb3ralbert)",
   "reason": "cyb3ralbert ACKed after testing d59713ea51 and confirming that test_headers_sync_renewals_bounded properly pins the bound, accepting the final suggestion.",
   "evidence": [
    "cyb3ralbert: ACK \u2014 The new commit only touches the functional test; verified test_headers_sync_renewals_bounded fails if the renewal bound is changed to 5 or 7 and passes with 6, pinning the bound from both sides"
   ],
   "objections": [
    {
     "reviewer": "cyb3ralbert",
     "association": "CONTRIBUTOR",
     "type": "objection",
     "kind": "correctness",
     "harm": "A stalled peer that stops responding to getheaders remains connected indefinitely, holding the outbound slot.",
     "request": "",
     "question": "",
     "text": "A stalled peer that stops responding to getheaders remains connected indefinitely, holding the outbound slot.",
     "clears_with": "change",
     "blocking": true,
     "status": "fixed",
     "status_note": "author confirmed and pushed bf56f4fb83 renewing the timeout only if presync work increased",
     "cleared": true,
     "author_replied": true,
     "others_replied": false,
     "fix_pushed": true,
     "replies": [
      "c:6163775056"
     ],
     "evidence": "2026-10-14: 'With a single outbound peer, a peer that sends one headers message and then stops responding to `getheaders` remains connected for 24 hours under mocktime, whereas master disconnects it after 24 minut'",
     "url": "https://github.com/bitcoin/bitcoin/pull/36350#issuecomment-6161235415",
     "id": "c:6161235415",
     "at": "2026-10-14",
     "status_at": "2026-10-14",
     "status_by": "0xShadowX",
     "resolution_evidence": "2026-10-14: 0xShadowX: 'Pushed bf56f4fb83. The presync work is now recorded when the timeout is set, and when it expires the timeout is only ren'",
     "pin": null,
     "from_schema": 2
    },
    {
     "reviewer": "cyb3ralbert",
     "association": "CONTRIBUTOR",
     "type": "question",
     "kind": "scope",
     "harm": "",
     "request": "",
     "question": "Whether the Tor/I2P latency scenario is expected to apply when there is only a single outbound peer.",
     "text": "Whether the Tor/I2P latency scenario is expected to apply when there is only a single outbound peer.",
     "clears_with": "answer",
     "blocking": false,
     "status": "answered",
     "status_note": "author explained that logic is independent of peer count",
     "cleared": true,
     "author_replied": true,
     "others_replied": false,
     "fix_pushed": false,
     "replies": [
      "c:6163775056"
     ],
     "evidence": "2026-10-14: 'Is the Tor/I2P latency scenario expected to apply when this is the only outbound peer?'",
     "url": "https://github.com/bitcoin/bitcoin/pull/36350#issuecomment-6161235415",
     "id": "c:6161235415#2",
     "at": "2026-10-14",
     "status_at": "2026-10-14",
     "status_by": "0xShadowX",
     "resolution_evidence": "2026-10-14: 0xShadowX: 'Single outbound peer: nothing in the logic depends on how many outbound peers there are, so a slow Tor/I2P link can hit '",
     "pin": null,
     "from_schema": 2
    },
    {
     "reviewer": "cyb3ralbert",
     "association": "CONTRIBUTOR",
     "type": "question",
     "kind": "usefulness",
     "harm": "",
     "request": "",
     "question": "Whether the eviction was observed on a real node or inferred from expected round-trip times.",
     "text": "Whether the eviction was observed on a real node or inferred from expected round-trip times.",
     "clears_with": "answer",
     "blocking": false,
     "status": "answered",
     "status_note": "author confirmed it was inferred from mainnet round trips and reproduced in mocktime tests",
     "cleared": true,
     "author_replied": true,
     "others_replied": false,
     "fix_pushed": false,
     "replies": [
      "c:6163775056"
     ],
     "evidence": "2026-10-14: 'Was the eviction observed on a real node, or inferred from expected round-trip times?'",
     "url": "https://github.com/bitcoin/bitcoin/pull/36350#issuecomment-6161235415",
     "id": "c:6161235415#3",
     "at": "2026-10-14",
     "status_at": "2026-10-14",
     "status_by": "0xShadowX",
     "resolution_evidence": "2026-10-14: 0xShadowX: 'Real node: no, the eviction was not observed on a real node. It is inferred from the ~480 presync round trips on mainnet'",
     "pin": null,
     "from_schema": 2
    },
    {
     "reviewer": "cyb3ralbert",
     "association": "CONTRIBUTOR",
     "type": "objection",
     "kind": "correctness",
     "harm": "During the REDOWNLOAD phase, presync work stops growing and timeout renewals cease, risking eviction of slow peers before the redownload buffer releases headers.",
     "request": "",
     "question": "",
     "text": "During the REDOWNLOAD phase, presync work stops growing and timeout renewals cease, risking eviction of slow peers before the redownload buffer releases headers.",
     "clears_with": "change",
     "blocking": true,
     "status": "fixed",
     "status_note": "author implemented GetSyncProgress() to cover both presync and redownload in 69e0e7188",
     "cleared": true,
     "author_replied": true,
     "others_replied": false,
     "fix_pushed": true,
     "replies": [
      "c:6187005712"
     ],
     "evidence": "2026-10-15: 'A peer that needs timeout renewals during presync therefore has only 22\u201342 minutes after the transition to deliver those headers before being disconnected.'",
     "url": "https://github.com/bitcoin/bitcoin/pull/36350#issuecomment-6185199965",
     "id": "c:6185199965",
     "at": "2026-10-15",
     "status_at": "2026-10-15",
     "status_by": "0xShadowX",
     "resolution_evidence": "2026-10-15: 0xShadowX: 'The renewal now uses a progress counter that covers both phases (`HeadersSyncState::GetSyncProgress()`, presync height +'",
     "pin": null,
     "from_schema": 2
    },
    {
     "reviewer": "cyb3ralbert",
     "association": "CONTRIBUTOR",
     "type": "objection",
     "kind": "safety",
     "harm": "Timeout renewals are unbounded, allowing a peer with slow but regular progress to hold an outbound slot indefinitely.",
     "request": "",
     "question": "",
     "text": "Timeout renewals are unbounded, allowing a peer with slow but regular progress to hold an outbound slot indefinitely.",
     "clears_with": "change",
     "blocking": true,
     "status": "fixed",
     "status_note": "author bounded renewals to MAX_HEADERS_SYNC_TIMEOUT_RENEWALS = 6 in 69e0e7188",
     "cleared": true,
     "author_replied": true,
     "others_replied": false,
     "fix_pushed": true,
     "replies": [
      "c:6187005712"
     ],
     "evidence": "2026-10-15: 'There does not appear to be a bound on the number of renewals, so a peer making very slow but regular progress can hold the outbound slot for an extended period.'",
     "url": "https://github.com/bitcoin/bitcoin/pull/36350#issuecomment-6185199965",
     "id": "c:6185199965#2",
     "at": "2026-10-15",
     "status_at": "2026-10-15",
     "status_by": "0xShadowX",
     "resolution_evidence": "2026-10-15: 0xShadowX: 'Renewals: now bounded by `MAX_HEADERS_SYNC_TIMEOUT_RENEWALS = 6`, so a peer can hold the slot for at most ~2h22m through'",
     "pin": null,
     "from_schema": 2
    },
    {
     "reviewer": "cyb3ralbert",
     "association": "CONTRIBUTOR",
     "type": "suggestion",
     "kind": "maintenance",
     "harm": "",
     "request": "Call sync_with_ping() on each step or allow extra minutes to make test_presync_peer_stalled_is_evicted less timing-sensitive.",
     "question": "",
     "text": "Call sync_with_ping() on each step or allow extra minutes to make test_presync_peer_stalled_is_evicted less timing-sensitive.",
     "clears_with": "answer",
     "blocking": false,
     "status": "fixed",
     "status_note": "author updated advance_minutes to sync with ping after each mocktime step and increased loop to 50 minutes",
     "cleared": true,
     "author_replied": true,
     "others_replied": false,
     "fix_pushed": true,
     "replies": [
      "c:6187005712"
     ],
     "evidence": "2026-10-15: 'Calling `sync_with_ping()` on each step, or allowing a few extra minutes, would make the test less timing-sensitive.'",
     "url": "https://github.com/bitcoin/bitcoin/pull/36350#issuecomment-6185199965",
     "id": "c:6185199965#3",
     "at": "2026-10-15",
     "status_at": "2026-10-15",
     "status_by": "0xShadowX",
     "resolution_evidence": "2026-10-15: 0xShadowX: '`advance_minutes` now calls `sync_with_ping()` after every mocktime step and the loop runs to 50 minutes.'",
     "pin": null,
     "from_schema": 2
    },
    {
     "reviewer": "cyb3ralbert",
     "association": "CONTRIBUTOR",
     "type": "suggestion",
     "kind": "maintenance",
     "harm": "",
     "request": "Pin the number of renewals in test_headers_sync_renewals_bounded so that the test fails if MAX_HEADERS_SYNC_TIMEOUT_RENEWALS is changed.",
     "question": "",
     "text": "Pin the number of renewals in test_headers_sync_renewals_bounded so that the test fails if MAX_HEADERS_SYNC_TIMEOUT_RENEWALS is changed.",
     "clears_with": "answer",
     "blocking": false,
     "status": "accepted",
     "status_note": "cyb3ralbert verified that the test fails with bound at 5 or 7 and passes with 6",
     "cleared": true,
     "author_replied": true,
     "others_replied": false,
     "fix_pushed": true,
     "replies": [
      "c:6311936322"
     ],
     "evidence": "2026-10-21: 'One gap in the new bound test: `test_headers_sync_renewals_bounded` doesn't pin the number of renewals.'",
     "url": "https://github.com/bitcoin/bitcoin/pull/36350#issuecomment-6310806124",
     "id": "c:6310806124",
     "at": "2026-10-21",
     "status_at": "2026-10-22",
     "status_by": "cyb3ralbert",
     "resolution_evidence": "2026-10-22: cyb3ralbert: 'with 6 it passes, so the bound is now pinned from both sides.'",
     "pin": null,
     "from_schema": 2
    }
   ],
   "support": [
    {
     "reviewer": "cyb3ralbert",
     "reason": "The new commit only touches the functional test; verified test_headers_sync_renewals_bounded fails if the renewal bound is changed to 5 or 7 and passes with 6, pinning the bound from both sides",
     "substantive": true,
     "verdict": "ACK",
     "id": "c:6334875534",
     "url": "https://github.com/bitcoin/bitcoin/pull/36350#issuecomment-6334875534",
     "at": "2026-10-22",
     "association": "CONTRIBUTOR",
     "evidence": "both",
     "areas": [
      "p2p_headers_sync_with_minchainwork.py"
     ]
    }
   ],
   "participants": [
    {
     "login": "cyb3ralbert",
     "stance": "support",
     "note": "ACKed after verifying the renewal bound test; previously raised REDOWNLOAD eviction, unbounded renewals, and test coverage concerns (all settled)",
     "association": "CONTRIBUTOR",
     "comments": 4,
     "first": "2026-10-14",
     "last": "2026-10-22"
    }
   ],
   "derivation": "substantive support, no open objection (cyb3ralbert)",
   "model_state": null,
   "corrections": [],
   "notes": "cyb3ralbert ACKed after testing d59713ea51 and confirming that test_headers_sync_renewals_bounded properly pins the bound, accepting the final suggestion."
  },
  "dependencies": {
   "depends_on": [],
   "enables": []
  },
  "categories": [
   {
    "name": "mempool",
    "member": false,
    "evidence": "Matches the 'eviction' keyword, but modifies peer chain sync eviction in net_processing rather than mempool transaction eviction.",
    "band": "Unranked",
    "reason_tag": "cleanup",
    "score": 0.0,
    "factors": {
     "bug_severity": 2,
     "leverage": 0,
     "performance": 0,
     "security_stability": 1,
     "user_value": 2
    },
    "rationale": "Does not touch mempool data structures or policy."
   },
   {
    "name": "p2p",
    "member": true,
    "evidence": "Matches the P2P label and modifies net_processing.cpp and headerssync.h to prevent chain sync eviction during headers presync.",
    "band": "P2",
    "reason_tag": "bug fix",
    "score": 0.7,
    "factors": {
     "bug_severity": 2,
     "leverage": 0,
     "performance": 0,
     "security_stability": 1,
     "user_value": 2
    },
    "rationale": "Fixes a sync liveness bug where nodes on high-latency networks like Tor or I2P repeatedly evict active sync peers. Chain sync eviction previously failed to account for anti-DoS presync headers progress, disconnecting peers after ~22 minutes when ~480 round trips take longer than 2.5s each."
   },
   {
    "name": "utils",
    "member": false,
    "evidence": "Matched on 'sync' and 'lock' keywords, but touches peer management in net_processing rather than shared utilities or threading primitives.",
    "band": "Unranked",
    "reason_tag": "cleanup",
    "score": 0.0,
    "factors": {
     "bug_severity": 2,
     "leverage": 0,
     "performance": 0,
     "security_stability": 1,
     "user_value": 2
    },
    "rationale": "Does not modify shared utilities or libraries."
   }
  ],
  "confidence": "high",
  "uncertainties": [],
  "needs": []
 },
 "cost_usd": 0.0,
 "source": {
  "record": "2026-10-23T01:17:19+00:00",
  "code": "2026-10-23T01:20:30+00:00",
  "judgments": 3
 }
}