Repository navigation
feat(storage): answer get-or-closer requests (ADR-0020) - #249
mickvandijke wants to merge 11 commits into
Conversation
dirvine
left a comment
There was a problem hiding this comment.
Code approval at the reviewed head
No introduced blocking runtime defect found in this PR. Production responses use the size-capped encoder; authenticated requester identity is passed into the handler. Decoder use here is test-only.
Stack coordination remains required: saorsa-core#173 has a P2 decoder-size blocker. Refresh the core patch pin at Cargo.toml:240 after that fix and rerun integration tests; propagate the resulting node head into the client. This code approval is not approval to ship the current linked dependency set or a waiver of merge/release gates.
Reviewed commit: 79385bfddc04a26b5c0747b3355364c12b9a127b.
Review method: six substantive seats across the linked set, including a completed z-ai/glm-5.2 API review recovered from a worker that timed out after saving its response; coordinator source checks and local execution. Initial functional/security/test seats recommended approval. The coordinator probe, independent adjudicator, API seat and replacement concurrency/release seats corroborate the missing decoder bound. This is not unanimous approval. The API seat recommended client approval with concerns; the coordinator withholds it because the exact client pin still exposes the defect.
Local verification (macOS, rustc 1.96.0): core closer-peers 3 passed and DHT-manager regression 143 passed; protocol all-features 108 passed; node get-or-closer unit tests 3 passed and real-transport E2E 1 passed; client kad_read unit tests 10 passed and real-transport E2E 1 passed. Formatting passed in all four repositories. No production/testnet deployment or merge performed. Draft git pins were verified with cargo metadata --locked; this is not published dependency closure. ADR-0020 remains Proposed.
|
Thanks.
|
930f90e to
9775920
Compare
dirvine
left a comment
There was a problem hiding this comment.
Re-review — pin update confirmed, one coordination item
Fix verified
- saorsa-core pin now
fd377754(the bounded closer-peer decoder) in both Cargo.toml and Cargo.lock — the first-round coordination requirement is met. - ADR-0020 docs updated to match: oversized closer-peer payloads rejected before parsing; bad-content peers dropped.
Coordination item (not a code defect)
- The PR head is
97759205, but ant-client#221 pins ant-node atad886472— a commit inside this PR's branch, not the head. The client consumes the get-or-closer handler and the fixed core pin from that mid-branch commit. - Please either: (a) pin the head after squash/merge, or (b) confirm
ad886472is the intended node revision. Otherwise the client's node dependency tracks an unreferenced commit once this branch merges.
Local verification
- Handler + e2e tests at head
97759205: 3/3 and 1/1 ok (local run, macOS).
Verdict
APPROVE at head 97759205, subject to the node-revision pin being reconciled before the client ships. Panel: GLM 5.3 seat billing-failed (402) — 2/3 reviewed, provisional.
4ab018a to
8b29fc9
Compare
Kademlia's FIND_VALUE for chunks. A new get-or-closer request is answered with the chunk by a holder, and otherwise with the closer peers its FIND_NODE answer would hold, encoded and checked by saorsa-core. Native clients run their own iterative lookup with it. Peers that do not announce `get-or-closer/1` in their user agent get FIND_NODE plus a GET instead, so a new client reads at full speed from today's nodes. When the lookup finds nothing, the client falls back to today's read path, so the absence rule is unchanged. Measured on production: native p50 5.95 s to about 1 s, the whole file 3.7 times faster, at 2.5-3.3 chunk copies per read. Part of V2-1358. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ADR-0020. A node that holds the requested chunk answers `Found` with it. Otherwise it answers `Closer` with saorsa-core's encoding of the FIND_NODE answer it would give the authenticated requester, so clients check those peers exactly as they check FIND_NODE answers. A node with storage enabled announces `get-or-closer/1` in its user agent; one with storage off never answers chunk requests, so it does not. Browsers are not offered the request yet. Get-or-closer traffic gets its own rx and tx rows in the chunk traffic summary (group 4), and each answer is logged as `get_or_closer_rpc`. Part of V2-1358. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A client built from the design read a 612 MB production file in 56 s, against 242 s for the current client, through the per-peer fallback that today's nodes take. In-process networks of upgraded nodes are read with get-or-closer requests only. Part of V2-1358. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From review of the ADR-0020 stack: - decode_closer_peers rejects a payload over the 64 KiB FIND_NODE limit before parsing it (saorsa-core fd37775). The chunk message carrying it may be up to 5 MiB. - A peer whose content fails verification only drops out of the client's lookup (ant-client 7d9a248). Every queried peer is asked for the chunk, so ending the read on one bad answer would let any dishonest node on a lookup path fail reads. Today's read path, which runs when the lookup finds nothing, keeps its rule that bad content ends the read. - The trade-offs now list that every queried peer can send content. - Validation now covers both changes. Part of V2-1358. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Browsers keep today's read. The browser variant was built and A/B'd on a 60-node testnet, and was not faster: a warm browser already holds sessions with the chunk's close group there. The review trigger now names when to revisit it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ADR-0023 made the ordinary read choose its first GET uniformly among a chunk's close group, so a popular chunk no longer loads one node while the rest of its close group idles. The get-or-closer read asked the nearest likely holder first, which put that load back on one node for every native read. Record that it now chooses the first likely holder uniformly among those a lookup round hands over, and that the likely holders are asked one at a time and hedged after 500 ms. The user-agent token now follows `node/<version>` directly: ADR-0022 removed the migration token it used to follow. Part of V2-1358. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… branches saorsa-core c3c4c81 is saorsa-core#173 rebased onto 0.29.2, and ant-protocol 0c6ae27 is ant-protocol#44 rebased onto 5.0.1, the rc-2026.10.3 baseline this branch now sits on. Part of V2-1358. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Devnet nodes built their core config without the node's user agent, so they never announced the get-or-closer token. A client reading from a local devnet therefore took the legacy route for every peer, and ADR-0020's own path went unexercised there. Devnet nodes serve chunks, so they now announce it, as `NodeBuilder` does for a node with storage. Part of V2-1358. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A get-or-closer is a chunk read, but it logged only a bare `get_or_closer_rpc` line, at INFO for every answer, and none of the GET read event's fields: queue wait, storage read, encode and send time, and send outcome. - It now emits the same `get_rpc` event a GET does. A new `request` field tells the two apart (`get` or `get_or_closer`). A `Closer` answer has storage outcome `not_found`, as a GET for a chunk the node does not hold. - The separate `get_or_closer_rpc` line is gone, so each read logs one line, as before. - `of_get` gets its doc comment back from `of_get_or_closer`, and the new response keys are documented. Part of V2-1358. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The e2e harness answered chunk requests with its own router, which called `try_handle_request` without the sender. The get-or-closer test then checked that the answer left out the requester after decoding it on the requester, which drops itself from any answer it decodes, so the check could not fail. Decoded on a third node, the harness's answers named the requester. - `RunningNode::answer_chunk_request` (test-utils) answers a message as a running node's router does: with the authenticated sender, the GET telemetry and the traffic counters. The harness now routes every chunk message through it, so the e2e suite exercises the production path. - The test decodes on a third node and asks with the requester's own ID as the address, so an answer that kept the requester would name it first. As a control, the same node's answer to another peer must name the requester. The strengthened test failed against the old harness router and passes through the node's. Part of V2-1358. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Records the review fixes in ant-client#221 and this PR: - Every queried peer answers the lookup at once. Only the likely holder chosen to go next is asked for the chunk in its lookup step; other likely holders answer with FIND_NODE and wait for a GET, and far peers get FIND_NODE only. - Until the read knows two close groups of peers, every peer counts as a likely holder, so the first round is gated too. - At most two likely holders are asked at once. - The ordinary read starts from the peers the lookup ended on. - Node load stays as today's: routing never waits behind PUTs, and get-or-closer waits only as a GET does. - Validation lists the new driver tests, the legacy-node e2e test, the requester control, the read telemetry and devnet nodes' token. Part of V2-1358. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8b29fc9 to
6a14b53
Compare
ADR-0020: a chunk GET returns the chunk, or closer peers, in one request, as Kademlia's FIND_VALUE does. This PR adds the ADR and the node side.
Why. On production, a native chunk read spends 82% of its time finding a holder, because the closest-peers lookup runs to convergence before the GETs start. Asking each queried peer for the chunk as well as for closer peers cuts native p50 from 5.95 s to about 1 s; see the investigation.
Node.
GetOrCloserRequestwithFound.Closer, with saorsa-core's encoding of the FIND_NODE answer it would give the authenticated requester.get-or-closer/1in its user agent. One with storage off never answers chunk requests, so it does not announce it.get_rpcread event a GET does, with queue, storage, encode and send times, andrequest = get_or_closerto tell them apart. ACloseranswer has storage outcomenot_found.Compatibility by design.
Stack, merge in this order:
This PR pins saorsa-core (
c3c4c81) and ant-protocol (0c6ae27) at those branches by git rev, so it cannot be published as is. Before merging, release saorsa-core#173 and ant-protocol#44, and replace the[patch.crates-io]pins with version bumps.Review fixes (2026-10-27). Rebased onto main again, plus:
get-or-closer/1.Closernamed the requester there. The old test decoded on the requester, which drops itself, so it could not catch that. The harness now answers through the node's own router (RunningNode::answer_chunk_request, test-utils). The test decodes on a third node, with a control showing the same node names the requester to anyone else.Linear issue
Closes V2-1358
Risk tier
Compatibility
GetOrCloserRequest, and its user agent gains the tokenget-or-closer/1. Thenode/prefix is kept. Every existing message is unchanged.RunningNode::answer_chunk_request, behindtest-utils, answers a chunk message as a running node's router does.ChunkRequestContext::with_requesterandnode::user_agent(serves_chunks)are crate-private.get_rpcevent gains arequestfield (getorget_or_closer).Semver impact
Test evidence
cargo test --lib --features test-utils: 1,193 passed. New since the review: a get-or-closer carries the GET read telemetry.cargo test --test e2e --features test-utils -- --test-threads=1: 120 passed, 3 ignored, with every chunk message now answered through the node's own router.test_get_or_closer_returns_chunk_or_closer_peersdecodes on a third node, with a control.cargo clippy --all-targets --all-features -- -D warnings: clean, except lints that main also fails on this toolchain. Those are insrc/replication/pointer.rs,src/storage/chunk_store.rsandtests/e2e/pointer_replication.rs, which this PR does not touch.Live. A client from the ant-client PR downloaded a 612 MB production file in 56 s, against 242 s for the current client, with identical bytes. All 150 chunks came through the new lookup, on the route for nodes without the token.
Dev testnet comparison against main, 2026-10-27, after the review fixes. Two 60-node DigitalOcean fleets with the same layout:
Three runs:
ant-testnet/state/comparisons/v2-1358-review-fixes-{vs-main,swapped,swapped-matched}-60m-1027/README.md.New dependency
none
ADR
ADR-0020: Kademlia-style chunk GET: return the chunk or closer peers, added in this PR.
Mitigation / rollback
The node is passive until clients send the request. To stop clients using it, drop the user-agent token; they then fall back to FIND_NODE plus GET. Otherwise revert.
🤖 Generated with Claude Code