Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 10 additions & 14 deletions src/payment/verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ 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};
Expand Down Expand Up @@ -920,27 +919,24 @@ impl PaymentVerifier {
}
};

// 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);
// Closeness *verification* must mirror the uploader's pure XOR-distance
// peer selection. `find_closest_nodes_local_with_self` reranks the local
// routing table by reachability (preferring directly-reachable peers,
// XOR only as a tiebreaker), which demotes an XOR-close relay-only /
// NAT'd peer out of the compared window and falsely rejects an honest
// payment that legitimately quoted that peer. Use the XOR-only sibling
// so this check matches how the client chose the quoted close group.
let close_group_size = self.config.close_group_size;
let closest = p2p_node
.dht_manager()
.find_closest_nodes_local_with_self(xorname, admission_width)
.find_closest_nodes_local_by_distance_with_self(xorname, close_group_size)
.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 {admission_width} closest peers for {}",
"Paid quote issuer {} is not among this node's local {close_group_size} closest peers for {}",
issuer_peer_id.to_hex(),
hex::encode(xorname)
)))
Expand Down
10 changes: 9 additions & 1 deletion src/storage/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,9 +413,17 @@ impl AntProtocol {

let self_id = *p2p_node.peer_id();
let admission_width = storage_admission_width(self.payment_verifier.close_group_size());
// Storage-responsibility *verification* must mirror the uploader's pure
// XOR-distance peer selection. `find_closest_nodes_local_with_self`
// reranks the local routing table by reachability (preferring
// directly-reachable peers, XOR only as a tiebreaker), which demotes
// this node out of the compared window when it is an XOR-close
// relay-only / NAT'd peer and falsely rejects an honest PUT it is
// legitimately responsible for. Use the XOR-only sibling so the local
// admission check matches how the client chose the close group.
let closest = p2p_node
.dht_manager()
.find_closest_nodes_local_with_self(address, admission_width)
.find_closest_nodes_local_by_distance_with_self(address, admission_width)
.await;
if closest.iter().any(|node| node.peer_id == self_id) {
return Ok(());
Expand Down
Loading