Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
bb906cc
Merge pull request #162 from WithAutonomi/rc-2026.6.3
jacderida Jun 27, 2026
698b451
chore(release): cut rc-2026.6.4
jacderida Jun 29, 2026
15fc053
Merge remote-tracking branch 'upstream/rc-2026.6.4' into feat/full-no…
jacderida Jun 29, 2026
6be9d7d
docs(replication): drop redundant explicit doc link target
jacderida Jun 29, 2026
07ff59b
Merge pull request #161 from WithAutonomi/feat/full-node-shunning-node
jacderida Jun 29, 2026
eabe038
chore(release): cut rc-2026.6.4
jacderida Jun 30, 2026
91f84b6
chore(release): promote rc-2026.6.4 to 0.14.2
jacderida Jun 30, 2026
df82eb2
Merge pull request #163 from WithAutonomi/rc-2026.6.4
jacderida Jul 1, 2026
9e1cb67
feat: commitment-bound quote pricing (ADR-0003)
grumbach Jun 17, 2026
495b138
docs: renumber ADR-0003 → ADR-0004 (avoid collision with full-node-sh…
grumbach Jun 30, 2026
5e09318
chore: TEMP [patch.crates-io] evmlib+ant-protocol PR branches for CI …
grumbach Jun 30, 2026
1970812
fix(verifier): drop ADR-retired/superseded validators left dead by th…
grumbach Jun 30, 2026
a829eeb
docs(adr): move implementation-slices out of the ADR-NNNN namespace
grumbach Jun 30, 2026
b01f304
fix(test): make commitment-TTL boundary test robust on Windows
grumbach Jun 30, 2026
5f5a845
chore: bump evmlib→0.9.0 / ant-protocol→2.3.0 pins for ADR-0004
grumbach Jul 1, 2026
b52f457
fix(verifier): retire ADR-0004-superseded local price-floor gate
grumbach Jul 1, 2026
21841a1
docs(adr-0004): amendment 1 — answerability must outlive quote validi…
grumbach Jul 2, 2026
314c83e
feat(replication): ADR-0004 Amendment 1 core — paid-pin ledger + prov…
grumbach Jul 2, 2026
b7bed4d
fix(paid-pin): harden — panic-free, Result persistence, deterministic…
grumbach Jul 2, 2026
ef8c218
test(paid-pin): fail-closed admission + capstone pay-then-shed-across…
grumbach Jul 2, 2026
e3f32b7
feat(commitment): restart-durable responder retention (ADR-0004 A1 st…
grumbach Jul 2, 2026
12f6061
fix(commitment): harden restart-durable retention
grumbach Jul 2, 2026
48d1dfe
fix(commitment): close step-1 review-2 gaps — restart grace + format …
grumbach Jul 2, 2026
c42ca44
fix(commitment): apply restart grace BEFORE expiry check (step-1 revi…
grumbach Jul 2, 2026
d705130
feat(audit): remove grace — responsive UnknownCommitment is a confirm…
grumbach Jul 2, 2026
17f177e
refactor(audit): extract pure grade_reject + test the grace-removal flip
grumbach Jul 2, 2026
660b488
feat(audit): in-window screen on the monetized first-audit path (step 4)
grumbach Jul 2, 2026
5c43450
fix(audit): fail-closed both-ends monetized in-window screen (step-4 …
grumbach Jul 2, 2026
1f8b426
docs+cleanup: rewrite ADR-0004 Amendment 1 (grace removed) + retire p…
grumbach Jul 2, 2026
785918b
test(audit): extract + test the monetized in-window screen (step 7)
grumbach Jul 2, 2026
0982cff
Merge branch 'main' into adr-0003-commitment-bound-pricing
grumbach Jul 2, 2026
4613ca0
docs(adr-0004): fix stale grace/retention comments, drop dead code, h…
grumbach Jul 3, 2026
25d14a2
fix(node): re-derive MAX_PAYMENT_PROOF_SIZE_BYTES for ADR-0004 proofs
grumbach Jul 7, 2026
2331185
chore(deps): bump crossbeam-epoch 0.9.18 -> 0.9.20 (RUSTSEC-2026-0204)
grumbach Jul 7, 2026
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
14 changes: 6 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 17 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ mimalloc = "0.1"
# Until then, the git pin tracks the matching saorsa-core lineage
# (the rc-2026.4.2 branch) so Cargo can unify the wire types here
# with ant-protocol's re-exports.
ant-protocol = "2.2.2"
ant-protocol = "2.3.0"

# Core (provides EVERYTHING: networking, DHT, security, trust, storage)
saorsa-core = "0.26.2"
saorsa-pqc = "0.5"

# Payment verification - autonomi network lookup + EVM payment
evmlib = "0.8.1"
evmlib = "0.9.0"
xor_name = "5"

# Caching - LRU cache for verified XorNames
Expand Down Expand Up @@ -196,3 +196,18 @@ unused_async = "allow"
cognitive_complexity = "allow"
# Allow non-const functions during initial development (may need runtime features later)
missing_const_for_fn = "allow"

# ─── TEMPORARY: STRIP BEFORE MERGE — ADR-0004 coordinated cutover ───
# The ADR-0004 versions of evmlib (0.9.0) and ant-protocol (2.3.0) — carrying
# the signed quote fields `committed_key_count` / `commitment_pin` — are NOT yet
# published to crates.io, so this crate cannot build against crates.io. These git
# patches point both deps at their PR branches, whose package versions are
# exactly the 0.9.0 / 2.3.0 already pinned in [dependencies] above, so CI can
# build the coordinated change end-to-end.
#
# AT RELEASE (once evmlib 0.9.0 then ant-protocol 2.3.0 are published, in that
# order): delete this entire [patch.crates-io] block. The [dependencies] pins are
# already at the published versions, so nothing else changes.
[patch.crates-io]
evmlib = { git = "https://github.com/WithAutonomi/evmlib", branch = "adr-0003-signed-quote-fields" }
ant-protocol = { git = "https://github.com/WithAutonomi/ant-protocol", branch = "adr-0003-commitment-bound-pricing" }
Comment on lines +211 to +213
Comment on lines +211 to +213
336 changes: 336 additions & 0 deletions docs/adr/ADR-0004-commitment-bound-quote-pricing.md

Large diffs are not rendered by default.

109 changes: 109 additions & 0 deletions docs/adr/implementation-slices-adr-0004.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# ADR-0004 implementation slicing

This file tracks the slicing strategy used to ship ADR-0004 incrementally inside
ant-node alone, while the multi-repo evmlib breaking change ripens. The ADR
itself (`ADR-0004-commitment-bound-quote-pricing.md`) describes the end state;
this document describes the order in which the end state lands.

The constraint that drives the slicing: `PaymentQuote`, `ProofOfPayment`,
`bytes_for_signing`, and `quote.hash()` live in evmlib (crates.io `0.8.1`) and
flow into the on-chain `payForQuotes` interface. Adding signed fields to
`PaymentQuote` is therefore a coordinated four-repo release
(`evmlib` → `ant-protocol` → `ant-client` → `ant-node`). Until that lands,
every part of ADR-0004 that does NOT require new signed quote fields can —
and should — ship behind the rollout const the ADR's "Rollout" section
already specifies.

## Slice 1 — arithmetic re-check (shipped)

**What:** every storer re-runs `price == calculate_price(n)` for some
non-negative integer `n`, by exact recomputation, on every quote in every
payment bundle (all 7 single-node quotes and all 16 merkle candidates), in
every `VerificationContext`. Reject-only when enforced; no trust evidence.
Rollout-gated by `QUOTE_ARITHMETIC_RECHECK_ENABLED` (defaults to `false` —
observe-only). Telemetry runs only after ML-DSA-65 signature verification has
passed, so unauthenticated peers cannot poison rollout logs.

**Why first:** needs no evmlib change, no new state, no new wire types, no
new gossip; it is the ADR's "every storer re-runs the
price-equals-formula-of-count check on every quote in the bundle" rule in
its purest form. The price already encodes the count, so canonicality testing
the price alone catches every off-curve lie (a strictly weaker attack than
on-curve count inflation, which Slice 2 addresses).

**Files touched:** `src/payment/verifier.rs` (new functions
`validate_quote_arithmetic`, `validate_merkle_candidate_arithmetic`,
`log_off_curve_single_node`, `log_off_curve_merkle`,
`price_off_curve_diagnostics`, `candidate_count_to_usize`,
`quote_price_is_on_curve`), `src/replication/config.rs` (new const
`QUOTE_ARITHMETIC_RECHECK_ENABLED`).

**Scope it does NOT cover:** an on-curve quote for a fake `n`. That requires
the signed `claimed_key_count` and `commitment_pin` fields that only Slice 3
can add.

## Slice 2 — commitment-binding sidecar (no evmlib change)

**What:** carry the issuing node's current signed `StorageCommitment` as a
sidecar inside the existing payment-proof envelope. Wire the storer-side
cross-check (claimed count from the quote vs. pinned commitment's
`key_count`) using the sidecar where present, the gossiped cache where the
sidecar pin matches, or a `GetCommitmentByPin` fetch otherwise. Adds the
`FailureEvidence::QuoteCommitmentMismatch` variant. Adds the
deterministic-first-audit queue keyed on monetized pins.

**Why second:** this is the ADR's "peers cross-check the original and route
monetized commitments into audit" paragraph. It lands the full audit funnel
end-to-end against real signed commitments without changing evmlib. The
sidecar's `claimed_count` is not yet covered by the on-chain quote hash, so
the binding is enforced at the gossip/audit layer rather than at the chain
layer — exactly the residual the ADR's rollout phase already names.

**Files touched (planned):** `src/payment/proof.rs` (sidecar serialization
envelope), `src/payment/verifier.rs` (cross-check rule),
`src/replication/protocol.rs` (`GetCommitmentByPin` request/response),
`src/replication/commitment_state.rs` (quote-issuance answerability refresh),
`src/replication/mod.rs` (first-audit queue alongside
`last_commitment_by_peer`), `src/replication/types.rs`
(`FailureEvidence::QuoteCommitmentMismatch`), `src/payment/quote.rs` (read
current pin from commitment state).

## Slice 3 — signed quote fields (multi-repo, breaking cutover) — LANDED

**What:** signed `committed_key_count: u32` and `commitment_pin:
Option<[u8; 32]>` added to `PaymentQuote` and `MerklePaymentCandidateNode`
in evmlib, included in `bytes_for_signing` (single-node) and `bytes_to_sign`
(merkle), with the quote types' fields placed at the struct **tail** so an
old-format value still rmp-decodes (as `(0, None)`). `ant-protocol` is
patched in lockstep to verify the 5-field merkle message, so the merkle
binding is genuine same-key-signed evidence too. Both `evmlib` and
`ant-protocol` are brought in via `[patch.crates-io]` against local
checkouts; the eventual upstream path is published `evmlib` →
`ant-protocol` → `ant-client` → `ant-node` releases.

**This is a HARD CUTOVER, not a mixed-fleet observe-only.** Appending the
fields to the signed payload changes every quote's signature and
`quote.hash()`, so an old quote fails signature verification on a new node
regardless of any flag — there is no version in which old and new nodes
interoperate on the quote wire. The whole fleet and the clients upgrade
together. The earlier "Slice 3 deferred behind observe-only" framing was
wrong on this point (a round-2 review finding): only the **arithmetic
enforcement** (`QUOTE_ARITHMETIC_RECHECK_ENABLED`, reject vs log) is a
rollout dial; the signed-fields format is a one-shot breaking change. With
the fields signed, Slice 1's arithmetic gate strengthens from curve
canonicality to the exact `price == calculate_price(committed_key_count)`
binding rule (`binding_violation`), and pricing moves off the on-disk count
entirely (no-commitment → baseline).

## Rollout coupling

The ADR's "Rollout" section says full enforcement requires the fleet
upgraded **and** the ADR-0002 timeout-eviction gate enabled.
`QUOTE_ARITHMETIC_RECHECK_ENABLED` (reject vs observe-only-log) is
independent of timeout eviction: the arithmetic/binding gate is reject-only
on a confirmed off-curve or mis-shaped quote, with no silence lane. The
own-quote price-staleness gate is retired for commitment-bound quotes (it
compared against the on-disk count, which the committed responsible count
legitimately differs from). The Slice-2 cross-check's silence lane (an
unanswerable quoted pin) is what couples to timeout eviction, exactly as the
ADR specifies.
71 changes: 48 additions & 23 deletions src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,31 +143,56 @@ impl NodeBuilder {
}

// Initialize replication engine (if storage is enabled)
let replication_engine =
if let (Some(ref protocol), Some(fresh_rx)) = (&ant_protocol, fresh_write_rx) {
let storage_arc = protocol.storage();
let payment_verifier_arc = protocol.payment_verifier_arc();
match ReplicationEngine::new(
repl_config,
Arc::clone(&p2p_arc),
storage_arc,
payment_verifier_arc,
Arc::clone(&identity),
&self.config.root_dir,
fresh_rx,
shutdown.clone(),
)
.await
{
Ok(engine) => Some(engine),
Err(e) => {
warn!("Failed to initialize replication engine: {e}");
None
let replication_engine = if let (Some(ref protocol), Some(fresh_rx)) =
(&ant_protocol, fresh_write_rx)
{
let storage_arc = protocol.storage();
let payment_verifier_arc = protocol.payment_verifier_arc();
match ReplicationEngine::new(
repl_config,
Arc::clone(&p2p_arc),
storage_arc,
payment_verifier_arc,
Arc::clone(&identity),
&self.config.root_dir,
fresh_rx,
shutdown.clone(),
)
.await
{
Ok(engine) => {
// ADR-0004: wire the engine's commitment state as the
// quote generator's commitment source so quotes force
// their price from the live storage commitment. Done
// here because the engine owns the commitment state and
// is built after the protocol.
if let Some(ref protocol) = ant_protocol {
let concrete = Arc::clone(engine.commitment_state());
let source: Arc<dyn crate::payment::quote::CommitmentSource> = concrete;
protocol.attach_commitment_source(source);
// ADR-0004: share the engine's gossip commitment
// cache with the verifier so the cross-check can
// resolve quote pins against neighbours' commitments.
protocol
.payment_verifier_arc()
.attach_commitment_cache(Arc::clone(engine.last_commitment_by_peer()));
// ADR-0004: give the verifier the monetized-pin sender so
// commitments that back a payment get a deterministic
// first audit from the engine's drainer.
protocol
.payment_verifier_arc()
.attach_monetized_pin_sender(engine.monetized_pin_sender());
}
Some(engine)
}
} else {
None
};
Err(e) => {
warn!("Failed to initialize replication engine: {e}");
None
}
}
} else {
None
};

let node = RunningNode {
config: self.config,
Expand Down
Loading
Loading