Repository navigation
fix(merkle): cap a payment's in-flight merkle candidate requests at 32 - #252
Merged
Merged
Conversation
A tree's candidate pools (up to 16) were queried concurrently, each asking up to 32 peers, so one tree sent about 500 requests at once and got about 500 responses back in one burst. saorsa-core queues inbound messages on 8 dispatch shards chosen by the sender's IP, 32 messages each, and drops a message when its shard is full (V2-1900). Under CPU load part of the burst is also lost below the dispatcher (V2-1901). Each lost response cost its request the versioned-probe timeout and a legacy retry, and pools came up short of 16 candidates. This is the Merkle E2E flake (V2-1893): every node of the in-process testnet is on 127.0.0.1. A semaphore shared by every pool of a payment now holds each request, legacy retry included, to 32 in flight. Measured against 16 slots and against 16 slots released after 2 s: at 2 cores on a local testnet, 32 lost 0 responses per run (main: 356 and 1094), and on mainnet it added about 0.6 s per tree, where 16 slots added about 6 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaidSafe-Hermes
approved these changes
Nov 6, 2026
MaidSafe-Hermes
left a comment
Contributor
There was a problem hiding this comment.
APPROVE. Both seats agree (two model families): 2/2 seats reviewed (sol-1 gpt-6.1-sol xhigh; glm-1 z-ai/glm-5.3). The shared semaphore limits all candidate request futures across the pools in one build_candidate_pools invocation; each owned permit spans the versioned attempt and any legacy retry and releases on completion/cancellation. No blocking correctness issue found. The scope is per invocation, not a client-wide aggregate cap, which matches the stated per-payment intent. The performance measurements are author-reported; CI passes the listed build/test checks. Security Audit remains a separate inherited RUSTSEC-2026-0387 merge gate (V2-1743), not a defect introduced by this PR.
Coverage: e818e8d.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
test_merkle_payment_across_batch_boundaryfails about 3 times in 100 CI runs with "Got 14/15 of 16 merkle candidates". The cause is the client, not the test.A merkle tree's candidate pools (up to 16) are queried at the same time, and each asks up to 32 peers. So one tree sends about 500 candidate requests at once and gets about 500 answers back in one burst. On a loaded host part of that burst is lost:
Each lost answer costs its request the versioned-probe timeout plus a legacy retry. Pools then come up short of 16 candidates, and the payment fails or retries.
Change: one semaphore, shared by every pool of a payment, holds candidate requests to 32 in flight. The legacy retry is included. That's 35 lines in
ant-core/src/data/client/merkle.rs, plus a changelog entry.Why 32
Before choosing, I measured four variants. The full report is in Linear on V2-1893.
Linear issue
Closes V2-1893
Risk tier
Proposed T1. Only the client's request pacing changes. Requests, answers and payments are the same messages as before.
Compatibility
Semver impact
Test evidence
Measurements, 2026-11-05 (dev2 plus mainnet, numbers in the table above): the runs used a build that set the cap and slot mode with environment variables, and its logic matches this commit.
test_merkle_payment_across_batch_boundary, 35 nodes. The 32-slot variant passed all 4 of its runs: 3 at 2 cores (2 with no silent nodes, 1 with 4 silent nodes) and 1 at 4 cores with 4 silent nodes (10 s quote timeout).prepare_merkle_batch_external(no wallet, nothing paid), 16/16 pools on every tree.This commit (26746e3), on dev2:
cargo +stable fmt --checkpasses.cargo +stable clippy --all-targets --all-features -D warningsis clean.ant-coremerkle unit tests: 75 passed.e2e_merkleat 4 cores: 8 of 8 passed (1917 s).e2e_merkleat 2 cores: 7 of 7 passed before my 45-min timeout cut the run off. The eighth,test_merkle_payment_across_batch_boundary(the flaky one), then passed on its own at 2 cores (426 s, no short attempts).CI on 26746e3: everything is green, including E2E and Merkle E2E on ubuntu and macOS, except Security Audit, which fails with RUSTSEC-2026-0387 as on main (V2-1743; this PR touches no Cargo files).
Main merged in (e818e8d, 2026-11-06), bringing in feat(core): type a wallet balance shortfall as Error::InsufficientFunds (#152) #224, ci(install-test): make the npm jobs test npm's ant, not Apache Ant's #228 and fix(core): don't mistake a stale daemon PID file for a running daemon (V2-1655) #235. The only conflict was
CHANGELOG.md, and both Fixed entries are kept. On dev2 the merged tree passes fmt, strict clippy and the 75 merkle unit tests. The fix itself is unchanged since 26746e3.New dependency
none (
tokio::sync::Semaphore, already a dependency)ADR
n/a
Mitigation / rollback
Revert this commit, or raise
MAX_IN_FLIGHT_MERKLE_CANDIDATE_REQUESTS. Either way, payments go back to today's uncapped behaviour.🤖 Generated with Claude Code