Skip to content

fix(payment): widen receiver storage admission to uploader over-query window#148

Closed
jacderida wants to merge 1 commit into
rc-2026.6.2from
fix/widen-receiver-storage-admission-rc-2026.6.2
Closed

fix(payment): widen receiver storage admission to uploader over-query window#148
jacderida wants to merge 1 commit into
rc-2026.6.2from
fix/widen-receiver-storage-admission-rc-2026.6.2

Conversation

@jacderida

Copy link
Copy Markdown
Collaborator

Problem

On NAT-simulated testnets (STG-01, rc-2026.6.2), ~3–6% of single-node uploads fail. Each failed upload reports N-1/N stored, 1 failed, and the storer-side rejection is:

ClientPut receiver <peer> is not among this node's local 9 closest peers (close group plus storage margin)

Because the single-node payment path fails the whole file on one un-stored chunk, the per-chunk rejection rate compounds into per-file failures.

Regression

The previous staging run on the same fault-injection environment (30% NAT-simulated nodes, ±2h clock-skewed clients, +3h-skewed node) running v0.12.0-rc.1 achieved 1,724/1,724 uploads (100%) with 2 retried chunks of 69,403. v0.12.0-rc.1 had no single-node receiver/issuer close-group admission gate at all.

The gate was introduced after that run by #136 (feat(payment): accept flexible single-node proof bundles) — validate_store_membership (receiver) and the paid-quote-issuer check. It has since been tuned by #137 (margin), #140 (XOR-only — correct, kept), and #147 (issuer width → K_BUCKET_SIZE). #140 fixed the reachability re-rank; the receiver width was left at close_group_size + STORAGE_ADMISSION_MARGIN (= 9), which is the remaining binding constraint.

Cause

On a network with unreachable (NAT'd, relay-only) peers, the client cannot quote the genuinely-closest nodes, so it legitimately pays a peer that ranks just past close_group_size in a storer's full local routing-table view — that view still contains the closer peers the client could not reach. A close_group_size + 2 admission window is narrower than that divergence, so the storer rejects an honest payment for a chunk it is genuinely XOR-close to.

The merkle path is largely spared because its closeness check already uses a much wider window (2 * CANDIDATES_PER_POOL = 32, 9-of-16 majority) for exactly this reason.

Fix

Size the storage-admission window to the uploader's quote over-query window: close_group_size * STORAGE_ADMISSION_OVERQUERY_MULTIPLIER, where the multiplier (2) mirrors ant-client get_store_quotes (CLOSE_GROUP_SIZE * FAULT_TOLERANT_QUOTE_QUERY_MULTIPLIER). A storer now admits a chunk it is XOR-close to within the same window the uploader selected its close group from. Production close group of 7 → admission window 14 (was 9).

If 14 proves insufficient under heavier NAT clustering, the multiplier is a one-line bump.

Verification

  • cargo check -p ant-node
  • cargo test -p ant-node --lib storage_admission_width ✅ (storage_admission_width_is_overquery_window)
  • Full validation is a redeploy of STG-01 on this build, confirming the not among local 9 closest rejections disappear — the receiver gate's live behaviour needs a real routing table (the handler only exposes a boolean test-override seam), so it isn't unit-testable in isolation.

🤖 Generated with Claude Code

… window

Direct client PUT receiver admission (`validate_store_membership`) rejected
honest uploads on NAT-simulated testnets: the storer decided it was "not among
this node's local 9 closest peers" for a chunk it is XOR-close to.

Root cause: on a network with unreachable (NAT'd, relay-only) peers the client
cannot quote the genuinely-closest nodes, so it legitimately pays a peer that
ranks just past `close_group_size` in a storer's full local routing-table view
(which still contains those closer, client-unreachable peers). The admission
window was `close_group_size + STORAGE_ADMISSION_MARGIN` (= 9), narrower than the
divergence, so the receiver rejected the paid PUT. On the single-node payment
path — which fails the whole file on one un-stored chunk — this produced
multiplicative per-file upload failures. v0.12.0-rc.1 (which had no admission
gate) achieved 100% uploads on the same fault-injection environment; the gate
was introduced in #136 and is the regression.

Fix: size the storage-admission window to the uploader's quote over-query window
(`close_group_size * STORAGE_ADMISSION_OVERQUERY_MULTIPLIER`, mirroring ant-client
`CLOSE_GROUP_SIZE * FAULT_TOLERANT_QUOTE_QUERY_MULTIPLIER`), so a storer admits a
chunk it is XOR-close to within the same window the uploader selected from. The
check stays XOR-only (#140). Admission and stored-record retention stay coupled
(a node that accepts a chunk also retains it); audit, quorum, and paid-list
target sets are unchanged at `close_group_size`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jacderida jacderida closed this Jun 16, 2026
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