Skip to content

fix(payment): widen paid-quote issuer admission to close group + margin#139

Merged
jacderida merged 1 commit into
WithAutonomi:rc-2026.6.2from
jacderida:fix/widen-issuer-admission-margin
Jun 12, 2026
Merged

fix(payment): widen paid-quote issuer admission to close group + margin#139
jacderida merged 1 commit into
WithAutonomi:rc-2026.6.2from
jacderida:fix/widen-issuer-admission-margin

Conversation

@jacderida

Copy link
Copy Markdown
Collaborator

Problem

The paid-quote issuer close-group check in PaymentVerifier::validate_paid_quote_issuer_close_group compares the client's network-lookup-derived quote set against this node's local routing-table view (find_closest_nodes_local_with_self). On a young / large / NAT-heavy network the local routing table does not perfectly reflect the global K-closest, so a peer the client legitimately quoted routinely ranks just outside the bare close_group_size window. The honest payment is then rejected:

Paid quote issuer <peer> is not among this node's local 7 closest peers for <chunk>

Because one un-storable chunk fails the whole file, the upload failure rate scales multiplicatively with file size.

Observed

Seen on a staging testnet (ant-node 0.12.1-rc.1, 900 nodes, ~30% NAT-simulated). Failure rate by file size: ~1000 MB ≈ 100%, ~100 MB ≈ 55%, ~20 MB ≈ 10%; every failed upload reported N-1/N chunks stored, 1 failed, dominated by the "issuer is not among this node's local 7 closest peers" rejection.

Cause

fix(payment): enforce local admission before proof verification narrowed the issuer check from the wide K-closest window down to the bare configured close group (close_group_size, 7). That is the same local-view divergence that fix(replication): widen local storage admission range addressed for the sibling receiver admission check by adding STORAGE_ADMISSION_MARGIN (→ storage_admission_width, 9). The issuer check never received that margin and so over-rejects.

Fix

Apply the same margin to the issuer check: widen its window from close_group_size (7) to storage_admission_width(close_group_size) (9), mirroring the receiver admission check. Single-file change; no change to the receiver check or any other behaviour.

Notes

  • This keeps the design from enforce local admission before proof verification (issuer ∈ local close group) and simply reuses the tolerance margin already established for the receiver path — it does not revert either prior change.
  • The margin size (+2 → width 9) is the conservative, symmetry-preserving choice. If real-world local-RT divergence proves larger than 2 ranks, the width may need to grow further; trying 9 first.

🤖 Generated with Claude Code

The paid-quote issuer close-group check compares the client's
network-lookup-derived quote set against this node's *local* routing-table
view. On a young / large / NAT-heavy network the local routing table does
not perfectly reflect the global K-closest, so a peer the client
legitimately quoted routinely ranks just outside the bare close-group
window. The honest payment is then rejected with "Paid quote issuer ... is
not among this node's local 7 closest peers", which aborts the whole upload
(one un-storable chunk fails the file) — a failure rate that scales
multiplicatively with file size.

This is the same local-view divergence the sibling receiver admission check
already absorbs via `storage_admission_width` (close_group_size +
STORAGE_ADMISSION_MARGIN). Apply the same margin to the issuer check for
symmetry, widening its window from `close_group_size` (7) to
`storage_admission_width(close_group_size)` (9). No change to the receiver
admission check or to any other behaviour.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dirvine

dirvine commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Hermes Agent review

Thanks Chris — I reviewed the diff locally.

Verdict: looks reasonable to me; I would wait for the remaining CI test jobs to finish before merging.

What I checked

  • Diff scope: single-file change in src/payment/verifier.rs.
  • The paid-quote issuer locality check now uses storage_admission_width(self.config.close_group_size) instead of the bare close-group width.
  • This mirrors the direct ClientPut receiver admission path in src/storage/handler.rs, which already checks storage admission with close_group_size + STORAGE_ADMISSION_MARGIN before payment verification.
  • The surrounding payment verification path still checks the important bindings: quote content/address, peer/pubkey binding, signature, local price floor, and on-chain payment amount.

Local check run

cargo test -p ant-node paid --lib --no-fail-fast

Result: 56 passed; 0 failed; 484 filtered out.

CI observed at review time

Passing: builds, clippy, docs, format, security audit, no-logging test.

Pending at the time I checked: Test (ubuntu-latest), Test (macos-latest), Test (windows-latest).

Minor non-blocking note

The VerificationContext docs still say the verifier checks the paid quote issuer is in the “configured close group”. After this change, that is now effectively “configured close group plus storage-admission margin” for the legacy paid-quote issuer check. I’d update that wording either here or in a follow-up to avoid future invariant confusion around strict close-group vs storage-admission width.

No blocking code issue found from my review.

@jacderida jacderida merged commit ff1195e into WithAutonomi:rc-2026.6.2 Jun 12, 2026
10 of 11 checks passed
jacderida added a commit that referenced this pull request Jun 12, 2026
Pin saorsa-core 0.25.0 and ant-protocol 2.1.3 to their now-published
crates.io versions (they were released earlier this cycle), collapsing
the dual saorsa-core lineage that broke the ant-devnet build. Includes
the PR #139 payment-admission fix.
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.

2 participants