Repository navigation
net: kill the 2026-10-17 missed mutants - #1073
Conversation
There was a problem hiding this comment.
Summary
The comparison deletions are equivalent on real inputs: a compact short-id with any count other than one was already marked missing, protect_by_netgroup(0) and an empty candidate list still protect nobody, I2P base64 + matches | because the added digit is six bits sitting in a zero low half, and the permission literals are the same masks as the old ors. unproved_anchor is the same split as the old two-pass check (a covered checkpoint at or above the queue front, otherwise the highest checkpoint strictly below it). The Class A startup log removal is not justified by the claim that the walk always returns zero: body-queue rehydrate can insert tip+1 into the height map first, and the new smoke journey does exactly that. The new pins otherwise exercise the operators they name.
Issue counts by severity
- bugs: 0
- suggestions: 4
- nits: 0
b80caa8 to
97e1607
Compare
There was a problem hiding this comment.
Summary
The production deletions are behavior-preserving on real inputs. The compact len() > 1 arm was already the same missing.push as the existing wildcard, inbound eviction with k == 0 or an empty candidate list still protects nobody, I2P (acc << 6) + v matches | because every digit is six bits, and the permission literals are the old implied-bit masks (FORCE_RELAY 0b1100, NOBAN 0b0101_0000, ALL 0b1111_1110). unproved_anchor still treats a covered checkpoint at or above the queue front as proved and still anchors on the highest checkpoint strictly below; extend_tip and promote_challenger do not insert two different hashes at one height, so the new strict > versus max_by_key's last-wins tie does not change a reachable anchor. The Class A startup info line is only a duplicate of the callee's non-zero log, and the four earlier comment and cancel notes are fixed in the current tree. The one gap is a header-walk pin that returns before the operator it names.
Issue counts by severity
- bugs: 0
- suggestions: 1
- nits: 0
Delete compares that fold to the same result. Pin the rest on the header walk, compact blocks, I2P SAM, eviction, permissions, and two IBD journeys.
A body-queue row or an archived body above the tip can make that walk return a positive count. The callee already logs that fill, so this match only keeps the zero case.
The checkpoint under the queue front is the anchor. A covered checkpoint at or above the front means the queue is proved.
ForceRelay still implies Relay, and NoBan still implies Download. The masks stay the same literals.
A cancel during the initial dial returns before rehydrate and before the redial line. These two-block chains exit on catch-up, with a timeout only if that exit never comes.
A linking header with valid proof reaches that clause. && would extend the tip. A bad proof still returns at pow_linked.
97e1607 to
3c32996
Compare
There was a problem hiding this comment.
Summary
The production deletions are equivalent on real inputs. The compact short-id arm and the wildcard both mark the slot missing, eviction with k == 0 or an empty set still protects nobody, I2P (acc << 6) + v matches | because v is six bits, and the permission literals are the same masks as the old ors (FORCE_RELAY 0b1100, NOBAN 0b0101_0000, ALL 0b1111_1110). unproved_anchor keeps the same >= front boundary as the old filter; strict > versus max_by_key last-wins only differs when two different hashes share a height, and extend_tip / promote_challenger do not insert that. Class A Ok(_) drops a duplicate log: rehydrate_class_a_into_body_queue still logs n > 0 and still fills. The new pins, including both || arms in note_stored_candidate_ignores_a_dead_end_tip, observe the operators they name. Earlier review fixes hold. What remains is comments that narrate a mutant or describe a cancel path this tree no longer takes.
Issue counts by severity
- bugs: 0
- suggestions: 3
- nits: 0
The dead-end pin was narrating the flipped operator, and the IBD journeys still mentioned a dial cancel they do not set. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Summary
I checked this against the 2026-10-17 missed lines (run 39004835061, master 9d912f87), not only the current names of the tests.
The rewrites match the old results on inputs the node can actually store. A compact short-id with any count other than one was already pushed onto missing. protect_by_netgroup(0) and an empty candidate list still remove nobody, because the protect list stops before it records an id. I2P (acc << 6) + v matches the old | even after the u32 accumulator wraps: a left shift of 6 always leaves the low six bits clear, and v is only six bits. I checked that on a 512-byte destination, not only on the short round-trip vectors. The permission literals are the old masks (FORCE_RELAY 0b1100, NOBAN 0b0101_0000, ALL 0b1111_1110). retain_challenger is only called from rewind_to, after the tip has been set from the last checkpoint or the base, so dropping the tip-hash arm does not drop a fork that arm alone would have kept.
unproved_anchor still returns none when any queued height covers a checkpoint at or above the queue front, and otherwise returns the highest checkpoint strictly under the front. The Class A startup line was info!, and rehydrate_class_a_into_body_queue still logs info! when the count is non-zero, so the fill is not dropped to debug. The named pins I traced do reach the nightly operator: the dead-end header pins the dead-end arm, and the bad-proof header pins || !pow_linked (nightly column 53), which is the miss.
What is left is a new comparison the rewrite introduced, and a changelog line that still tells the mutant story.
Issue counts by severity
- bugs: 0
- suggestions: 2
- nits: 0
Two checkpoints under the front at one height must return the earlier hash. A later-wins tie would move the anchor. Co-authored-by: Cursor <cursoragent@cursor.com>
The mask, short-id, base64, and eviction edits keep the same bytes. The note should say the filled-N line is gone because the callee already logs a non-zero fill. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Summary
Reviewed 4de06f3bc against the 2026-10-17 misses (run 39004835061, 9d912f87) and against every open suggestion from the earlier reviews. Nothing new is wrong.
The production edits still match the old results on inputs this node stores. A compact short-id whose count is not one was already marked missing. Eviction with k == 0 or an empty set still protects nobody. I2P (acc << 6) + v matches | after the accumulator wraps, because a shift of 6 leaves the low six bits clear. The permission literals are the old masks. retain_challenger runs only after rewind_to has set the tip from the last checkpoint or the base, so the removed tip-hash arm does not drop a live fork. unproved_anchor still treats a covered checkpoint at or above the queue front as proved, and otherwise anchors on the highest checkpoint strictly below it. The Class A startup line was info!, and rehydrate_class_a_into_body_queue still logs info! when the count is non-zero.
The same-height pin holds. Two checkpoints at height 3 under a front at height 5 return the earlier hash. Replacing > with >= returned the later hash and failed that assert.
Disposition
| Suggestion | Result |
|---|---|
| Class A comment claimed the walk count is always zero | Fixed. The comment says a row or an archived body above the tip can make the count non-zero, and that this match only logs the zero fill because the callee already logs the rest. |
unproved_anchor comment narrated the deleted compare |
Fixed. It now says the checkpoint under the front is the anchor, and a covered checkpoint at or above the front means the queue is proved. |
| Permission docs explained xor and "union as a mask" | Fixed. ForceRelay still says it implies Relay, NoBan still says it implies Download, and the bit values are unchanged. |
| Both new IBD journeys cancelled at 400ms, racing the lines they assert | Fixed. They pass no cancel flag and wait up to 20s for catch-up on a one-block and a two-block chain. |
note_stored_candidate pin returned at pow_linked before the ` |
|
| Those two test comments narrated the flipped operator | Fixed. Both comments are gone. The asserts state the tip and the empty checkpoint list. |
| Smoke comments still described the dial-cancel path | Fixed. The redial comment only says the two-block chain exits on catch-up. The noted-line comment is gone. |
New > in unproved_anchor was unpinned, so the next nightly would miss >= |
Fixed in f00824d5c. The test plants two different hashes at height 3 and expects the earlier one. |
| Changelog told the xor, short-id, base64, and eviction story | Fixed in 4de06f3bc. The note says the misses are gone or pinned, and that the startup Class A "filled N" line is gone because that rehydrate already logs a non-zero fill. |
Issue counts by severity
- bugs: 0
- suggestions: 0
- nits: 0
Summary
Kills every
MISSEDoperator from the 2026-10-17 nightly (Actions run 39004835061): 109 lines, 10 already gone on this base, 19 deleted because the compare folds to the same result, 80 pinned by a named test. No#[mutants::skip].The startup Class A "filled N" line is removed. That walk sees only the confirmed tip, so the count is always zero, and
rehydrate_class_a_into_body_queuealready logs a non-zero fill. The in-RAM body-queue rehydrate arm stays and is pinned, along with the redial clock and the Class A seed count, byibd_smoke.Already gone on master (10)
Deleted (19)
Pinned by test (80)
retain_challenger_keeps_a_fork_that_is_still_on_the_candidate— crates/rbitcoin-net/src/ibd/header_walk.rs:1261:13: replace || with &&retain_challenger_keeps_a_fork_that_is_still_on_the_candidate— crates/rbitcoin-net/src/ibd/header_walk.rs:1259:34: replace == with !=retain_challenger_keeps_a_fork_that_is_still_on_the_candidate— crates/rbitcoin-net/src/ibd/header_walk.rs:1261:65: replace == with !=retain_challenger_keeps_a_fork_that_is_still_on_the_candidate— crates/rbitcoin-net/src/ibd/header_walk.rs:1263:8: delete !adopt_heavier_keeps_the_milestone_at_the_fork_height— crates/rbitcoin-net/src/ibd/header_walk.rs:1424:20: replace < with <=adopt_heavier_keeps_the_milestone_at_the_fork_height— crates/rbitcoin-net/src/ibd/header_walk.rs:1441:13: replace != with ==walk_ahead_of_stored_is_strictly_above_the_recorded_height— crates/rbitcoin-net/src/ibd/header_walk.rs:1492:58: replace > with >=classify_pins_an_unlinked_batch_a_refill_and_the_first_checkpoint— crates/rbitcoin-net/src/ibd/header_walk.rs:1522:27: replace || with &&classify_pins_an_unlinked_batch_a_refill_and_the_first_checkpoint— crates/rbitcoin-net/src/ibd/header_walk.rs:1529:37: replace == with !=classify_pins_an_unlinked_batch_a_refill_and_the_first_checkpoint— crates/rbitcoin-net/src/ibd/header_walk.rs:1544:9: replace || with &&headers_late_is_logged_only_for_a_late_extension— crates/rbitcoin-net/src/ibd/header_walk.rs:1602:17: replace && with ||ignore_below_floor_pins_the_floor_gate_and_the_queue_cap— crates/rbitcoin-net/src/ibd/header_walk.rs:1616:27: replace || with &&ignore_below_floor_pins_the_floor_gate_and_the_queue_cap— crates/rbitcoin-net/src/ibd/header_walk.rs:1630:25: replace < with ==ignore_below_floor_pins_the_floor_gate_and_the_queue_cap— crates/rbitcoin-net/src/ibd/header_walk.rs:1630:25: replace < with >ignore_below_floor_pins_the_floor_gate_and_the_queue_cap— crates/rbitcoin-net/src/ibd/header_walk.rs:1630:25: replace < with <=reject_refill_miss_pins_each_reason_it_returns— crates/rbitcoin-net/src/ibd/header_walk.rs:1646:9: replace || with &&reject_refill_miss_pins_each_reason_it_returns— crates/rbitcoin-net/src/ibd/header_walk.rs:1645:9: replace || with &&reject_refill_miss_pins_each_reason_it_returns— crates/rbitcoin-net/src/ibd/header_walk.rs:1644:9: replace || with &&reject_refill_miss_pins_each_reason_it_returns— crates/rbitcoin-net/src/ibd/header_walk.rs:1665:37: replace || with &&proved_above_floor_requires_both— crates/rbitcoin-net/src/ibd/header_walk.rs:1675:27: replace && with ||unproved_anchor_stops_at_a_met_checkpoint_and_keeps_the_one_below— crates/rbitcoin-net/src/ibd/header_walk.rs:1702:54: replace >= with <candidate_prev_accepts_base_tip_and_checkpoint— crates/rbitcoin-net/src/ibd/header_walk.rs:1766:9: replace || with &&candidate_prev_accepts_base_tip_and_checkpoint— crates/rbitcoin-net/src/ibd/header_walk.rs:1765:9: replace || with &&candidate_prev_accepts_base_tip_and_checkpoint— crates/rbitcoin-net/src/ibd/header_walk.rs:1764:9: replace || with &&candidate_prev_accepts_base_tip_and_checkpoint— crates/rbitcoin-net/src/ibd/header_walk.rs:1766:61: replace == with !=proven_header_prefix_writes_the_whole_reply_until_the_candidate_is_proved— crates/rbitcoin-net/src/ibd/header_walk.rs:1829:9: replace || with &&proven_header_prefix_writes_the_whole_reply_until_the_candidate_is_proved— crates/rbitcoin-net/src/ibd/header_walk.rs:1828:9: replace || with &&proven_header_prefix_writes_the_whole_reply_until_the_candidate_is_proved— crates/rbitcoin-net/src/ibd/header_walk.rs:1827:9: replace || with &&proven_header_prefix_writes_the_whole_reply_until_the_candidate_is_proved— crates/rbitcoin-net/src/ibd/header_walk.rs:1827:12: delete !proven_header_prefix_writes_the_whole_reply_until_the_candidate_is_proved— crates/rbitcoin-net/src/ibd/header_walk.rs:1829:12: delete !proven_header_prefix_writes_the_whole_reply_until_the_candidate_is_proved— crates/rbitcoin-net/src/ibd/header_walk.rs:1835:8: delete !refill_misses_checkpoint_only_above_the_queue_tail— crates/rbitcoin-net/src/ibd/header_walk.rs:1875:69: replace > with >=refill_misses_checkpoint_only_above_the_queue_tail— crates/rbitcoin-net/src/ibd/header_walk.rs:1889:20: replace += with *=note_empty_rewinds_only_when_every_taller_peer_is_empty— crates/rbitcoin-net/src/ibd/header_walk.rs:1912:46: replace > with >=note_stored_candidate_ignores_a_batch_that_does_not_link— crates/rbitcoin-net/src/ibd/header_walk.rs:1935:53: replace || with &&classify_v2_ping_and_empty_other— crates/rbitcoin-net/src/compact.rs:59:13: delete match arm NetworkMessage::GetBlockTxn(r)cmpct_reconstruct_stats_first_shot_all_mempool_fetched_zero— crates/rbitcoin-net/src/compact.rs:422:33: replace += with *=cmpct_reconstruct_stats_first_shot_all_mempool_fetched_zero— crates/rbitcoin-net/src/compact.rs:436:33: replace += with *=cmpct_reconstruct_stats_extra_disjoint_from_mempool— crates/rbitcoin-net/src/compact.rs:439:31: replace += with *=cmpct_reconstruct_stats_orphan_bytes_sum— crates/rbitcoin-net/src/compact.rs:441:28: replace += with -=cmpct_reconstruct_stats_orphan_bytes_sum— crates/rbitcoin-net/src/compact.rs:441:28: replace += with *=cmpct_reconstruct_stats_orphan_bytes_sum— crates/rbitcoin-net/src/compact.rs:442:32: replace += with -=cmpct_reconstruct_stats_orphan_bytes_sum— crates/rbitcoin-net/src/compact.rs:442:32: replace += with *=prefill_indexes_keeps_a_tx_that_fills_the_budget_exactly— crates/rbitcoin-net/src/compact.rs:472:36: replace > with >=protect_by_netgroup_keeps_one_peer_from_each_largest_group— crates/rbitcoin-net/src/eviction.rs:115:48: replace += with *=protect_by_netgroup_keeps_one_peer_from_each_largest_group— crates/rbitcoin-net/src/eviction.rs:122:30: replace >= with <protect_by_netgroup_keeps_one_peer_from_each_largest_group— crates/rbitcoin-net/src/eviction.rs:127:36: replace == with !=sam_errors_name_the_socket_and_the_reply_that_retry— crates/rbitcoin-net/src/i2p_sam.rs:87:9: delete match arm NetError::Io(_) | NetError::Disconnectedsam_errors_name_the_socket_and_the_reply_that_retry— crates/rbitcoin-net/src/i2p_sam.rs:92:17: replace || with &&sam_errors_name_the_socket_and_the_reply_that_retry— crates/rbitcoin-net/src/i2p_sam.rs:112:9: delete match arm NetError::Io(_) | NetError::Disconnectedsam_errors_name_the_socket_and_the_reply_that_retry— crates/rbitcoin-net/src/i2p_sam.rs:113:9: delete match arm NetError::Encode(s)sam_errors_name_the_socket_and_the_reply_that_retry— crates/rbitcoin-net/src/i2p_sam.rs:120:17: replace || with &&sam_errors_name_the_socket_and_the_reply_that_retry— crates/rbitcoin-net/src/i2p_sam.rs:119:17: replace || with &&sam_errors_name_the_socket_and_the_reply_that_retry— crates/rbitcoin-net/src/i2p_sam.rs:118:17: replace || with &&sam_errors_name_the_socket_and_the_reply_that_retry— crates/rbitcoin-net/src/i2p_sam.rs:117:17: replace || with &&sam_errors_name_the_socket_and_the_reply_that_retry— crates/rbitcoin-net/src/i2p_sam.rs:116:17: replace || with &&sam_errors_name_the_socket_and_the_reply_that_retry— crates/rbitcoin-net/src/i2p_sam.rs:128:9: delete match arm NetError::Io(_) | NetError::Disconnectedsam_errors_name_the_socket_and_the_reply_that_retry— crates/rbitcoin-net/src/i2p_sam.rs:137:17: replace || with &&sam_errors_name_the_socket_and_the_reply_that_retry— crates/rbitcoin-net/src/i2p_sam.rs:136:17: replace || with &&sam_errors_name_the_socket_and_the_reply_that_retry— crates/rbitcoin-net/src/i2p_sam.rs:135:17: replace || with &&sam_errors_name_the_socket_and_the_reply_that_retry— crates/rbitcoin-net/src/i2p_sam.rs:134:17: replace || with &&sam_errors_name_the_socket_and_the_reply_that_retry— crates/rbitcoin-net/src/i2p_sam.rs:133:17: replace || with &&stream_status_error_is_not_retried— crates/rbitcoin-net/src/i2p_sam.rs:275:27: replace match guard stream_retry(&e) with truestream_status_error_is_not_retried— crates/rbitcoin-net/src/i2p_sam.rs:313:27: replace match guard stream_retry(&e) with truei2p_b64_round_trips_digits_dash_and_tilde— crates/rbitcoin-net/src/i2p_sam.rs:416:13: delete match arm '-' | '+'i2p_b64_round_trips_digits_dash_and_tilde— crates/rbitcoin-net/src/i2p_sam.rs:417:13: delete match arm '~' | '/'i2p_b64_round_trips_digits_dash_and_tilde— crates/rbitcoin-net/src/i2p_sam.rs:415:35: replace - with +i2p_b64_round_trips_digits_dash_and_tilde— crates/rbitcoin-net/src/i2p_sam.rs:415:35: replace - with /hello_requires_the_reply_prefix_and_result_ok— crates/rbitcoin-net/src/i2p_sam.rs:502:39: replace || with &&read_line_stops_at_16kib— crates/rbitcoin-net/src/i2p_sam.rs:536:22: replace > with ==read_line_stops_at_16kib— crates/rbitcoin-net/src/i2p_sam.rs:536:22: replace > with >=read_line_stops_at_16kib— crates/rbitcoin-net/src/i2p_sam.rs:536:27: replace * with +ibd_redials_immediately_and_does_not_invent_a_rehydrate— crates/rbitcoin-net/src/ibd/mod.rs:400:42: replace - with +ibd_redials_immediately_and_does_not_invent_a_rehydrate— crates/rbitcoin-net/src/ibd/mod.rs:427:29: replace > with ==ibd_redials_immediately_and_does_not_invent_a_rehydrate— crates/rbitcoin-net/src/ibd/mod.rs:439:18: replace match guard n > 0 with trueibd_notes_a_body_queue_row_above_the_tip— crates/rbitcoin-net/src/ibd/mod.rs:439:18: replace match guard n > 0 with falseibd_notes_a_body_queue_row_above_the_tip— crates/rbitcoin-net/src/ibd/mod.rs:439:20: replace > with ==decode_permit_pools_are_wider_than_one_slot— crates/rbitcoin-net/src/msg_decode.rs:19:5: replace block_decode_permits -> usize with 1decode_permit_pools_are_wider_than_one_slot— crates/rbitcoin-net/src/msg_decode.rs:27:5: replace ctrl_decode_permits -> usize with 1permission_masks_keep_their_implied_bits— crates/rbitcoin-net/src/net_permissions.rs:38:9: replace NetPermissionFlags::clear_implicit with ()Test plan
cargo test -p rbitcoin-net --libfilters for the new pins, and the twoibd_smokejourneyscargo fmt --all -- --check,cargo clippy --workspace --all-targets -- -D warnings,cargo deny check,./scripts/ast-grep.shqc,test,windows,macos,coverage