Skip to content

chore(release): promote rc-2026.5.5#126

Merged
jacderida merged 12 commits into
mainfrom
rc-2026.5.5
Jun 3, 2026
Merged

chore(release): promote rc-2026.5.5#126
jacderida merged 12 commits into
mainfrom
rc-2026.5.5

Conversation

@jacderida
Copy link
Copy Markdown
Collaborator

Promotes rc-2026.5.5 to release version(s): 0.11.6.

  • strips -rc.* from [package].version
  • rewrites internal git+branch deps to crates.io version pins
  • regenerates Cargo.lock

Once merged, the release tag will be pushed to fire the publish workflow.

jacderida and others added 12 commits May 29, 2026 13:57
The storage-delta freshness gate added in #120 / refined in 33db86a rejected
essentially every payment on an actively-replicating network with:

  payment error: Quote from peer ... stale by 29 records
  (quoted 8 vs current 37, tolerance 5)

Root cause: pricing and the freshness gate read two different record counts.

- Pricing (`QuoteGenerator::create_quote`) used the in-memory
  `QuotingMetricsTracker`, whose `record_store()` is incremented ONLY on the
  client-paid PUT path (`storage/handler.rs`). Replication stores
  (`replication/mod.rs` fresh fan-out + repair fetch) write to LMDB without
  touching it.
- The freshness gate (`validate_quote_freshness`) reads
  `LmdbStorage::current_chunks()` — the authoritative total, which DOES include
  replicated and repaired records.

So `current` counted all records while `quoted` (price-derived) counted only
direct paid PUTs. On any network with replication, current >> quoted, the delta
blew past `tolerance = max(5, 5% of quoted)`, and every payment was rejected.
33db86a made the verifier's read authoritative but left pricing on the side
counter, which is what introduced the divergence. STG-01 (~907 nodes, 30% NAT,
10 concurrent uploaders) hit ~100% upload failure on the first cycle; smaller
runs only passed because per-node record counts stayed under the 5-record floor.

Fix: price quotes from the same authoritative source the gate reads.
`QuoteGenerator` gains an attached `Arc<LmdbStorage>` (mirroring
`PaymentVerifier`) and prices from `current_chunks()`, falling back to the
in-memory counter only when no store is attached (unit tests / misconfigured
startup). `AntProtocol::new` attaches the store to the generator right beside
the verifier, so the invariant holds for every construction path.

`calculate_price` and `derive_records_stored_from_price` round-trip exactly, so
feeding larger current_chunks() counts through pricing is lossless and the
verifier recovers the exact count — leaving the freshness delta at ~genuine
in-flight growth. current_chunks() is also a more accurate measure of node
fullness for pricing than direct-paid-PUTs-only.

Adds a regression test that writes records straight to the store (as
replication would, without bumping the side counter) and asserts the quote
prices off the store count — the divergence the prior unit tests structurally
could not express.

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

fix(payment): price quotes from current_chunks() to match freshness gate
… delta

PR #122 made pricing and the freshness gate read the same record source, but
uploads on a fresh, rapidly-filling testnet (STG-01, 0.11.6-rc.2) still failed
~100%. The residual cause is the freshness gate itself.

The gate compared the price-derived quoted record count against the node's
current count with a fixed `max(5, 5%)` tolerance, symmetrically (abs_diff). Two
problems on an actively-replicating network:

1. Tolerance too tight for normal in-flight churn. Between a node quoting and
   verifying a payment (the client collects 7 quotes/chunk, pays on-chain via
   Arbitrum, then PUTs — several seconds) the node's record count drifts by a
   handful of records via replication. Sampled STG-01 rejects had a median
   delta of 8 — just over the floor of 5. At low/moderate fill the pricing
   curve is nearly flat, so that drift is a negligible price change, yet it was
   rejected.

2. Symmetric abs_diff rejected over-payments. ~36% of rejects had current <
   quoted: the node had FEWER records than when it quoted (prune/churn), so the
   client paid for a fuller, pricier node — an over-payment — and the node
   rejected it anyway. Rejecting money a node is owed is nonsensical.

Because an upload needs every chunk stored, even a ~40% per-chunk reject rate
drives upload success to ~0 (0.6^9 for a 9-chunk file; worse for large files).
So per-chunk rejection has to approach zero, not merely shrink.

Fix: make the gate price-based and one-directional. Compute the price the node
would charge now for its current fullness and reject only if the quote paid
less than that by more than QUOTE_PRICE_STALENESS_PCT_TOLERANCE (25%).

- Over-payment (paid >= current price) is always accepted (fixes #2).
- Comparing prices self-scales with the quadratic curve: benign churn on the
  flat part of the curve is ignored, while genuine staleness at high fill —
  where the curve is steep — is still caught (fixes #1).

Replaces the two record-delta tolerance constants with a single price
percentage. Reworks the freshness tests: a 10-record drift now passes (it was
"stale by 10" before), over-payment passes (regression test for #2), and a
quote underpricing by >25% is still rejected.

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

fix(payment): gate quote freshness on price under-payment, not record delta
…band

The exact-match 13/16 closeness gate rejects honest Merkle pools wholesale on
a young, high-churn, NAT-heavy network: two nodes' closest-set views to the
pool midpoint diverge by far more than the 3 peers 13/16 tolerated. This was
the dominant production failure (the "candidate pub_keys do not match"
rejection — 1,808 occurrences across 311 chunks in one PROD-UL-01 run), and
retrying can't fix a systematic divergence.

Two changes to the closeness check:

- Lower CANDIDATE_CLOSENESS_REQUIRED from 13/16 to a 9/16 majority.
- Count a candidate if it is EITHER an exact match in the storer's returned
  closest peers OR within the XOR band those peers occupy (distance to the
  midpoint <= the farthest returned peer). The address-space arm accepts a
  candidate that is genuinely in the midpoint's close group but simply absent
  from this storer's particular lookup, while still rejecting fabricated keys
  that land far from an un-ground midpoint.

Lookup width stays at 32 (wider measured too heavy). Security trade-off is
deliberate and being A/B-measured against the stricter RC: no theft of funds
(payment still binds on-chain to the rewards address), but the pay-yourself
grinding gets cheaper; the address-space arm and the planned pool-midpoint
consensus-anchor (D1) work are what keep that bounded.

Tests cover the address-space arm accepting honest divergence, the security
floor rejecting far-from-midpoint fabrications, and the majority boundary.
The exact-match 13/16 closeness gate rejects honest Merkle pools wholesale on
a young, high-churn, NAT-heavy network: two nodes' views of the closest peers
to the pool midpoint diverge by more than a near-unanimous threshold tolerates.
This was the dominant production failure (the "candidate pub_keys do not
match" rejection), and retrying can't fix a systematic divergence.

Lower CANDIDATE_CLOSENESS_REQUIRED from 13/16 to a 9/16 majority. A candidate
still counts only if its PeerId is among the peers the storer's own DHT lookup
returns as closest, so every counted candidate is a real, reachable, routed
peer — a fabricated off-network key cannot satisfy the check. The leniency is
purely the lower threshold; it does not relax what makes a candidate valid.

Security: a lower threshold lets the pay-yourself attacker clear the bar with
fewer real top-K Sybil nodes, but the floor stays "run N real closest nodes
AND grind the midpoint" (no fund theft is possible — payment binds on-chain to
the rewards address). Pairs with the planned pool-midpoint consensus-anchor
work, which removes the midpoint grinding freedom.

cargo test payment::verifier::tests: 58 passed. fmt + clippy clean.
fix(merkle): lower closeness threshold to 9/16 majority
Copilot AI review requested due to automatic review settings June 3, 2026 17:39
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Promotes the node to release 0.11.6 and updates dependency versions/lockfile, while also aligning payment quoting and verification behavior around the authoritative LMDB record count and adjusting payment-verification gating logic.

Changes:

  • Bump crate version to 0.11.6, update ant-protocol/saorsa-core, and regenerate Cargo.lock.
  • Wire QuoteGenerator pricing to LmdbStorage::current_chunks() (and attach storage during AntProtocol construction) to keep quote pricing consistent with verifier freshness checks.
  • Update PaymentVerifier quote freshness gating to a one-directional, price-based staleness check; lower merkle candidate closeness threshold to a 9/16 majority; add/adjust regression tests.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/storage/handler.rs Attaches LMDB storage to both PaymentVerifier and QuoteGenerator; clarifies the fallback counter behavior.
src/payment/verifier.rs Changes freshness gate semantics (price-staleness), adjusts closeness threshold, and updates tests/comments accordingly.
src/payment/quote.rs Adds attachable LMDB-backed pricing source with fallback to the in-memory counter; adds a regression test.
Cargo.toml Bumps ant-node version and updates dependency pins.
Cargo.lock Regenerated lockfile for the release/dependency updates.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/payment/quote.rs
Comment on lines +103 to +116
fn pricing_records_stored(&self) -> usize {
if let Some(storage) = self.storage.read().as_ref() {
match storage.current_chunks() {
Ok(n) => return usize::try_from(n).unwrap_or(usize::MAX),
Err(e) => {
debug!(
"QuoteGenerator: current_chunks() failed ({e}); \
falling back to metrics_tracker for pricing"
);
}
}
}
self.metrics_tracker.records_stored()
}
Comment thread Cargo.toml
Comment on lines 1 to 6
[package]
name = "ant-node"
version = "0.11.5"
version = "0.11.6"
edition = "2021"
authors = ["David Irvine <david.irvine@maidsafe.net>"]
description = "Pure quantum-proof network node for the Autonomi decentralized network"
@jacderida jacderida merged commit 5080d08 into main Jun 3, 2026
23 checks passed
@jacderida jacderida deleted the rc-2026.5.5 branch June 3, 2026 20:50
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.

3 participants