From 29ad6a5db96e00ceaaa10a5868c7fea3f67d5d08 Mon Sep 17 00:00:00 2001 From: Chris O'Neil Date: Fri, 12 Jun 2026 22:04:57 +0100 Subject: [PATCH] fix(payment): widen paid-quote issuer admission to close group + margin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- src/payment/verifier.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/payment/verifier.rs b/src/payment/verifier.rs index 4844d09a..3d0c9449 100644 --- a/src/payment/verifier.rs +++ b/src/payment/verifier.rs @@ -11,6 +11,7 @@ use crate::payment::pricing::{calculate_price, derive_records_stored_from_price} use crate::payment::proof::{ deserialize_merkle_proof, deserialize_proof, detect_proof_type, ProofType, }; +use crate::replication::config::storage_admission_width; use crate::storage::lmdb::LmdbStorage; use ant_protocol::payment::verify::{verify_quote_content, verify_quote_signature}; use evmlib::common::{Amount, QuoteHash}; @@ -919,17 +920,27 @@ impl PaymentVerifier { } }; - let close_group_size = self.config.close_group_size; + // Verify against the close group PLUS the storage-admission margin + // rather than the bare configured close-group width. The check compares + // the client's network-derived quote set against this node's *local* + // routing-table view, which on a real (young / large / NAT-heavy) + // network does not perfectly reflect the global K-closest: a peer the + // client legitimately quoted routinely ranks just outside this node's + // local close-group window. Bare `close_group_size` (no margin) falsely + // rejects those honest payments — the same divergence the sibling + // receiver admission check absorbs via `storage_admission_width` + // (PR #137); apply the same margin here for symmetry. + let admission_width = storage_admission_width(self.config.close_group_size); let closest = p2p_node .dht_manager() - .find_closest_nodes_local_with_self(xorname, close_group_size) + .find_closest_nodes_local_with_self(xorname, admission_width) .await; if closest.iter().any(|node| node.peer_id == *issuer_peer_id) { return Ok(()); } Err(Error::Payment(format!( - "Paid quote issuer {} is not among this node's local {close_group_size} closest peers for {}", + "Paid quote issuer {} is not among this node's local {admission_width} closest peers for {}", issuer_peer_id.to_hex(), hex::encode(xorname) )))