Skip to content

test: unit coverage and release notes for #266 - #272

Merged
kiwidream merged 1 commit into
djh58/headers-message-sizefrom
kiwidream/rg-258-tests-for-266
Oct 30, 2026
Merged

kiwidream merged 1 commit into
djh58/headers-message-sizefrom
kiwidream/rg-258-tests-for-266

Conversation

@kiwidream

@kiwidream kiwidream commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

Summary

This PR adds unit tests and a v1.0.1 release-notes fragment for #266 ("net: bound the size of headers messages we send"), which addresses #258. It makes no implementation changes. Every test runs #266's code at f18fb5069a through PeerManager or HeadersSyncState.

Refs #266
Refs #258

#266 keeps its rule private: MayHaveMoreHeaders(), THRESHOLD_HEADERS_MESSAGE_SIZE and HeadersMessageEntrySize() are local to net_processing.cpp. To build test input, src/test/util/net.{h,cpp} mirrors them under the same names, and SplitIntoHeadersMessages() mirrors how the sender fills a reply. The rule itself is tested through the real entry point instead: a test calls ProcessMessage with a headers message and checks whether the node then sends getheaders. If #266 exported the rule from net_processing.h, the mirrors could be removed.

src/test/peerman_tests.cpp

Test What it covers
headers_message_count_or_size_decides_whether_to_ask_for_more The boundary values: 1,999 ordinary headers do not make the node ask for more, and 2,000 do. A message of two AuxPoW headers totalling THRESHOLD_HEADERS_MESSAGE_SIZE - 1 bytes does not, and one of exactly THRESHOLD_HEADERS_MESSAGE_SIZE bytes does.
getheaders_reply_with_small_headers_is_unchanged With 2,001 small blocks, the node sends one reply of 2,000 headers, byte for byte the count-only reply. The last header then comes in a reply the receiver treats as final.
getheaders_replies_with_large_auxpow_headers_stay_within_receive_limit The active chain is 3 small blocks, 5 blocks with 1.9 MB AuxPoW headers, then 3 small blocks. Every reply is at most 4,000,000 bytes. The replies hold 5, 2 and 4 headers, and each one cut short is at least the threshold. Together they are contiguous, reach the tip and keep their payloads.
getheaders_reply_with_oversized_index_auxpow_stays_within_receive_limit A header first arrives alone with a 3,999,995-byte entry. The block index keeps that payload after the block connects with its own. The reply carries that header alone, within the limit, and the rest follows on the next request.
headers_sync_requests_more_after_byte_limited_message In the normal path, after each message cut short by size (far fewer than 2,000 headers), the node asks for headers starting from that message's last header. After the final message it does not. Mixed versions: a v1.0.0 sender's last message (two large headers, 3.8 MB) makes the node ask once more, and the empty reply ends the sync without a disconnect.
low_work_headers_sync_continues_through_byte_limited_messages With minimum chain work set to the chain's work, PRESYNC follows the cut-short messages and then restarts from the chain start. REDOWNLOAD follows them again and accepts every header.
headers_announcement_too_large_for_one_message_reverts_to_inv Two small blocks are still announced with a headers message. Three blocks with 1.9 MB AuxPoW headers are announced with an inv for the tip and no headers message.

src/test/headers_sync_chainwork_tests.cpp

Test What it covers
headers_sync_continues_through_byte_limited_messages HeadersSyncState on its own. Judged by header count alone, PRESYNC gives up after the first message cut short by size. With #266's rule, the sync goes from PRESYNC to REDOWNLOAD to FINAL across three messages and releases all 8 headers with their payloads.

doc/release-notes-266.md describes the change and records the known limitations deferred to later releases:

  • No consensus rule caps AuxPoW payload size yet. Adding one needs an activated soft fork.
  • The block index keeps every AuxPoW payload in memory.
  • v1.0.0 nodes still stall or abort headers sync through ranges with large AuxPoW headers.
  • The low-work redownload still has its 36 MB per-peer bound on retained AuxPoW bytes.

I did not add a functional test, because #266's p2p_auxpow_headers_size.py already covers the regtest scenario.

Findings on #266

None of the tests above fails against #266.

  1. A stored oversized AuxPoW can make a cut-short reply look final.
    • The block index keeps the AuxPoW from the first copy of a header it receives; AcceptBlockHeader does not replace it when the block arrives. If that copy is larger than any block could carry, a reply that stops before that header can be smaller than THRESHOLD_HEADERS_MESSAGE_SIZE, and the receiver takes it as the peer's tip.
    • net: bound the size of headers messages we send #266 notes this limitation, but its effect is worse than "continues at the next block announcement":
      • During IBD below minimum chain work, UpdatePeerStateForReceivedHeaders disconnects that outbound peer.
      • A low-work PRESYNC aborts with "incomplete headers message". A restarted sync makes the same request, so it can abort at the same height every time.
    • Planting such a payload needs a second parent proof of work for that header.
    • The fixes I can see are outside net_processing.cpp: replace the stored payload with the block's when the block is accepted, or refuse header copies whose entry is larger than the block size bound.
  2. The THRESHOLD_HEADERS_MESSAGE_SIZE comment overstates. It says that a single header never reaches the threshold. That holds for a block's own AuxPoW but not for a stored oversized one: getheaders_reply_with_oversized_index_auxpow_stays_within_receive_limit sends a single header in a 3,999,996-byte message. MayHaveMoreHeaders() treats that message as possibly cut short, so sync continues there, which is the right outcome.
  3. Comments still describe the count-only rule:
    • MAX_HEADERS_RESULTS in net_processing.h ("We rely on the assumption that if a peer sends less than this number, we reached its tip").
    • full_headers_message in headerssync.h.
    • The MAX_REDOWNLOAD_AUXPOW_SERIALIZED_SIZE rationale in headerssync.cpp. It is derived from count-full messages, but is now only a per-peer memory bound.
  4. Two padded headers can stand in for 2,000. A peer can start or continue a low-work headers sync with two headers padded to 2,000,000 bytes instead of 2,000 headers. The proof of work per header is unchanged, so I see no amplification, but each round's cost moves from proof of work to bandwidth.
  5. No release-notes fragment. net: bound the size of headers messages we send #266 has none; this PR adds doc/release-notes-266.md.

Testing

  • Built locally: cmake -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_GUI=OFF -DBUILD_TESTS=ON -DBUILD_BENCH=ON -DENABLE_WALLET=ON -DWITH_ZMQ=OFF -DWITH_CCACHE=ON, then cmake --build build -j12.
  • Ran focused unit or functional tests for the changed area.
  • Ran lint or formatting checks relevant to this change.
  • Not run. Reason:

Results at f98842ebde (#266's f18fb5069a plus this commit):

  • New tests: the 8 cases above pass.
  • Mutation check: changing >= to > in net: bound the size of headers messages we send #266's MayHaveMoreHeaders() makes headers_message_count_or_size_decides_whether_to_ask_for_more fail. I reverted the change afterwards. Before porting, the same tests ran against an equivalent implementation. Removing the sender's size cap, the receiver's size rule or the inv fallback each made the matching tests fail.
  • Full unit suite: build/bin/test_qbit ran 1,302 cases with no errors.
  • Functional tests: build/test/functional/test_runner.py --jobs=4 passed 15 of 15 runs:
    • p2p_auxpow_headers_size, feature_auxpow, feature_auxpow_commitment_activation, feature_minchainwork, feature_minchainwork_requested_block
    • p2p_block_sync (v1, v2), p2p_compactblocks, p2p_compactblocks_blocksonly, p2p_compactblocks_hb (v1, v2)
    • p2p_headers_sync_with_minchainwork, p2p_initial_headers_sync, p2p_sendheaders, p2p_unrequested_blocks
  • Lint: only git diff --check, which is clean. I did not run the lint suite.
  • CI: this PR is stacked on net: bound the size of headers messages we send #266's branch, so core-checks does not run until the PR is retargeted.

Target Branch

  • This PR targets main or a maintainer-requested release branch such as 0.1.x.

This PR targets djh58/headers-message-size, the branch of #266, as the coordinator requested. #266 targets 1.x.x for v1.0.1.

1.x.x is frozen for v1.0.1 (#181). A maintainer must lift the freeze before #266 merges, and the v1.0.1 release notes in #181 need this entry.

Risk / Review Notes

  • Consensus, script, crypto, wallet, P2P, release, CI, or security-sensitive behavior changed.
  • No consensus, script, crypto, wallet, P2P, release, CI, or security-sensitive behavior changed.

Notes:

  • This PR changes tests and release notes only. The P2P behavior under test is net: bound the size of headers messages we send #266's.
  • Engineering practices applied:
    • EP-VALIDATION: boundary and just-over-boundary values through the real entry point: 1,999 and 2,000 headers, the threshold minus one byte and the threshold, and a single header close to 4 MB.
    • EP-COMPAT: old and new producer/consumer pairs. Small-header replies are byte for byte the old ones. A v1.0.0 sender's last message is handled. v1.0.0 receivers are recorded as a limitation in the release notes.

Docs / Process Impact

  • I updated public docs because this PR changes user-visible behavior, integration guidance, release/process guidance, or expected validation: doc/release-notes-266.md.
  • No public docs update needed. Reason:

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

Add unit tests for #266, which bounds the size of headers messages:

- peerman_tests: the header count or message size decides whether a
  received headers message makes us ask for more, at MAX_HEADERS_RESULTS
  and at THRESHOLD_HEADERS_MESSAGE_SIZE. A reply of 2,000 small headers
  is unchanged byte for byte. Replies through large AuxPoW headers stay
  within MAX_PROTOCOL_MESSAGE_LENGTH and reach the tip. A header whose
  stored AuxPoW is close to 4 MB goes alone. The normal and low-work
  requesters keep asking after messages cut short by size. A v1.0.0
  sender's last message over the threshold costs one empty reply. An
  announcement too large for one message falls back to inv.
- headers_sync_chainwork_tests: HeadersSyncState continues through
  messages cut short by size in PRESYNC and REDOWNLOAD.

Also add the release notes for #266.

Refs #258

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kiwidream

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-30T15:38:57.488648Z f98842e Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: f98842ebde

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant