Skip to content
Merged
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ jobs:
anvil --version
forge --version
- name: Run E2E tests (serial)
run: cargo test -p ant-core --test e2e_chunk --test e2e_data --test e2e_file --test e2e_payment --test e2e_security --test e2e_cost_estimate -- --test-threads=1
run: cargo test -p ant-core --features test-utils --test e2e_chunk --test e2e_data --test e2e_file --test e2e_payment --test e2e_security --test e2e_cost_estimate -- --test-threads=1

test-merkle:
name: Merkle E2E (${{ matrix.os }})
Expand Down
3 changes: 3 additions & 0 deletions ant-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ default = []
# Enable `LocalDevnet` (ant-core/src/node/devnet.rs) which wraps
# `ant_node::devnet::Devnet` and an Anvil EVM testnet.
devnet = ["dep:ant-node"]
# Expose test-only client seams (e.g. forcing the ADR-0002 extended PUT
# fallback) used by the e2e/integration test suite.
test-utils = []

[dev-dependencies]
# Test infrastructure in tests/support/mod.rs spawns real ant-nodes
Expand Down
15 changes: 12 additions & 3 deletions ant-core/src/data/client/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ pub struct PreparedChunk {
pub content: Bytes,
/// Content address (BLAKE3 hash).
pub address: XorName,
/// Closest peers from quote collection — PUT targets for close-group replication.
/// Ordered PUT targets from quote planning.
///
/// Kept under the legacy `quoted_peers` name for API compatibility; the
/// list can include non-quoted fallback peers beyond the quoted close
/// group.
pub quoted_peers: Vec<(PeerId, Vec<MultiAddr>)>,
/// Payment structure (quotes sorted, median selected, not yet paid on-chain).
pub payment: SingleNodePayment,
Expand All @@ -58,7 +62,11 @@ pub struct PaidChunk {
pub content: Bytes,
/// Content address (BLAKE3 hash).
pub address: XorName,
/// Closest peers from quote collection — PUT targets for close-group replication.
/// Ordered PUT targets from quote planning.
///
/// Kept under the legacy `quoted_peers` name for API compatibility; the
/// list can include non-quoted fallback peers beyond the quoted close
/// group.
pub quoted_peers: Vec<(PeerId, Vec<MultiAddr>)>,
/// Serialized [`PaymentProof`] bytes.
pub proof_bytes: Vec<u8>,
Expand Down Expand Up @@ -256,7 +264,8 @@ impl Client {
};
let quotes_with_peers = quote_plan.quotes;

// Capture all quoted peers for close-group replication.
// Capture the ordered PUT target set for replication. This can be
// wider than the peers that supplied the paid quotes.
let quoted_peers = quote_plan.put_peers;

// Build peer_quotes for ProofOfPayment + quotes for SingleNodePayment.
Expand Down
285 changes: 228 additions & 57 deletions ant-core/src/data/client/chunk.rs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion ant-core/src/data/client/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2578,7 +2578,7 @@ impl Client {
hex::encode(addr)
))
})?;
let peers = self.close_group_peers(&addr).await?;
let peers = self.put_target_peers(&addr).await?;
observe_op(
&limiter,
|| async move { self.chunk_put_to_close_group(content, proof, &peers).await },
Expand Down
2 changes: 1 addition & 1 deletion ant-core/src/data/client/merkle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -854,7 +854,7 @@ impl Client {
hex::encode(addr)
))
})?;
let peers = self.close_group_peers(&addr).await?;
let peers = self.put_target_peers(&addr).await?;
observe_op(
&limiter,
|| async move { self.chunk_put_to_close_group(content, proof, &peers).await },
Expand Down
22 changes: 16 additions & 6 deletions ant-core/src/data/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use tracing::debug;

/// Width of the chunk PUT-target set (initial writes plus fallback): the
/// closest `PUT_TARGET_WIDTH` peers to the address.
///
/// Mirrors the node-side `K_BUCKET_SIZE` / `PAID_QUOTE_ISSUER_CLOSENESS_WIDTH`
/// (20): a node accepts a reused payment proof only when one of the proof's
/// closest-`CLOSE_GROUP_SIZE` quote issuers is within its own local 20-closest,
/// so trying peers past this width is pointless.
pub(crate) const PUT_TARGET_WIDTH: usize = 20;

/// Classify a `data::error::Error` into a controller `Outcome`.
///
/// Capacity signals (Timeout / NetworkError) drive the controller
Expand Down Expand Up @@ -508,16 +517,17 @@ impl Client {
self.next_request_id.fetch_add(1, Ordering::Relaxed)
}

/// Return all peers in the close group for a target address.
/// Return the chunk PUT-target set: the closest [`PUT_TARGET_WIDTH`] peers
/// to the address, each paired with its known network addresses.
///
/// Queries the DHT for the closest peers by XOR distance.
/// Returns each peer paired with its known network addresses.
pub(crate) async fn close_group_peers(
/// Used by the merkle store path, which — unlike single-node payment — has
/// no witnessed put-target list to forward, so it fetches the closest-K
/// neighbourhood locally.
pub(crate) async fn put_target_peers(
&self,
target: &XorName,
) -> Result<Vec<(PeerId, Vec<MultiAddr>)>> {
self.closest_peers(target, self.config().close_group_size)
.await
self.closest_peers(target, PUT_TARGET_WIDTH).await
}

/// Return the requested number of closest peers for a target address.
Expand Down
12 changes: 7 additions & 5 deletions ant-core/src/data/client/payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ impl Client {
/// Pay for storage and return the serialized payment proof bytes.
///
/// This orchestrates the full payment flow:
/// 1. Collect `CLOSE_GROUP_SIZE` quotes from the witnessed close group
/// 1. Collect `CLOSE_GROUP_SIZE` quotes plus ordered PUT targets
/// 2. Build `SingleNodePayment` using node-reported prices (median 3x, others 0)
/// 3. Pay on-chain via the wallet
/// 4. Serialize `PaymentProof` with transaction hashes
Expand All @@ -32,9 +32,10 @@ impl Client {
///
/// Returns an error if the wallet is not set, quotes cannot be collected,
/// on-chain payment fails, or serialization fails.
/// Returns `(proof_bytes, quoted_peers)`. `quoted_peers` are the
/// `CLOSE_GROUP_SIZE` peers that provided quotes — callers should store
/// the chunk to at least `CLOSE_GROUP_MAJORITY` of these peers.
/// Returns `(proof_bytes, put_targets)`. The peer list is the ordered PUT
/// target set from quote planning: it starts with peers expected to accept
/// the paid proof and can include non-quoted fallback peers beyond the
/// quoted close group.
pub async fn pay_for_storage(
&self,
address: &[u8; 32],
Expand All @@ -59,7 +60,8 @@ impl Client {
)
})?;

// Capture all quoted peers for replication by the caller.
// Capture the ordered PUT target set for replication by the caller.
// This can be wider than the peers that supplied the paid quotes.
let quoted_peers = quote_plan.put_peers;

// 2. Build peer_quotes for ProofOfPayment + quotes for SingleNodePayment.
Expand Down
Loading
Loading