Receiver-side price floor for single-node store admissions#176
Open
grumbach wants to merge 2 commits into
Open
Receiver-side price floor for single-node store admissions#176grumbach wants to merge 2 commits into
grumbach wants to merge 2 commits into
Conversation
…ions ADR-0004 binds every quote's price to its issuer's committed key count — a price CEILING. Removing the old local floor (b52f457) left no revenue floor at all: the node verifier accepts 1..=7 quote proofs and fully validates only the median-priced candidate, so a modified client can fetch quotes from the whole neighbourhood, settle only the cheapest valid one 3x, and every receiver accepts. The flexible-bundle change (WithAutonomi#136) was safe while the old floor stood; their combination is the hole, and the one-quote client (ant-client WithAutonomi#150) makes that path routine. Add the floor half, priced from the SAME live commitment the receiver's own QuoteGenerator uses (committed responsible key count via a new non-mutating CommitmentSource::current_binding_snapshot — never current_chunks(), whose divergence from commitment counts is what false-rejected honest quotes under the pre-ADR-0004 floor; and never current_binding_for_quote(), which would extend answerability). Requirement on single-node store admissions: settled >= 3 x tolerance% x calculate_price(local committed key count) alongside the existing settled >= 3 x median check, i.e. an honest client may overpay a cheap quote to clear stricter receivers. No live commitment (fresh node, retired, restart window, unwired) prices the floor at baseline — vacuous, never an outage mode. Shadow-first rollout: default is observe-only with one telemetry line per evaluated admission (target ant_node::payment::price_floor) recording paid price, settled amount, local count/price, and the would-reject verdict. Enforcement is per-node via ANT_PRICE_FLOOR_ENFORCE=1 for canary rollout (tolerance override: ANT_PRICE_FLOOR_TOLERANCE_PERCENT, default 50); unsetting it is the kill switch. Clients need 4-of-20 stores, so canary enforcement cannot fail honest uploads while telemetry calibrates the tolerance. A floor rejection is an economic admission decision only, never trust evidence. Scope: split VerificationContext::FreshReplication out of ClientPut — verified identically everywhere (store-strength cache, cross-check, first-audit) so the floor's telemetry can distinguish direct ingress from fan-out and the fan-out policy stays tunable without touching direct PUTs. The floor applies to both (one cheap accepting node must not fan a below-floor proof around everyone else's floor) and NEVER to paid-list admission, repair, or cache hits — no historical receipt is repriced. Merkle proofs are untouched. Tests: cheapest-of-K settlement rejected under enforcement (direct PUT and fresh replication), exactly-at-floor overpayment accepted, shadow mode never rejects, no-commitment receivers stay baseline-vacuous, paid-list admission never repriced.
There was a problem hiding this comment.
Pull request overview
Adds a receiver-side “price floor” policy to single-node payment verification so a node can reject (or shadow-log) underpaid store admissions even when a client settles only the cheapest valid quote from a bundle.
Changes:
- Introduces
PriceFloorConfig(shadow by default; env-controlled enforcement + tolerance) and applies it during single-node proof verification for store-admission contexts. - Splits
VerificationContextwith a newFreshReplicationvariant and unifies cache/admission semantics viais_store_admission(). - Adds
CommitmentSource::current_binding_snapshot()and wires the same commitment source into both quoting and the verifier’s price-floor computation; adds unit/e2e config wiring and tests.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/e2e/testnet.rs | Adds default price_floor config into e2e testnet verifier construction. |
| tests/e2e/data_types/chunk.rs | Wires PriceFloorConfig::default() into verifier config used by chunk e2e tests. |
| src/storage/handler.rs | Forwards the attached commitment source to the payment verifier (in addition to the quote generator). |
| src/replication/mod.rs | Uses VerificationContext::FreshReplication for fresh-offer payment verification and updates the associated test. |
| src/replication/commitment_state.rs | Implements current_binding_snapshot() for ResponderCommitmentState without mutating answerability. |
| src/payment/verifier.rs | Implements PriceFloorConfig, adds store-admission context predicate, enforces/logs receiver-side floor after settlement verification, and adds targeted tests. |
| src/payment/quote.rs | Extends CommitmentSource trait with current_binding_snapshot() and updates test implementation. |
| src/payment/mod.rs | Re-exports PriceFloorConfig and the env var names for external configuration. |
| src/node.rs | Initializes verifier price_floor from env and logs when enforcement is enabled. |
| src/devnet.rs | Sets default price_floor config for devnet verifier construction. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+1106
to
+1110
| let local_key_count = source | ||
| .and_then(|s| s.current_binding_snapshot()) | ||
| .map_or(0u32, |binding| binding.key_count); | ||
| let local_price = calculate_price(usize::try_from(local_key_count).unwrap_or(usize::MAX)); | ||
|
|
The floor is its own economic policy, not part of ADR-0004's ceiling: it amends "a node may always charge less" to "a receiver may refuse a payment that settles below its own commitment-priced floor". Records the pricing basis (committed responsible count via non-mutating snapshot), the scope (store admissions only, no historical repricing), the shadow-first rollout with per-node canary enforcement, and the validation gates.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Node-side companion to ant-client #150 (one-quote uploads). ADR-0004 gives every quote a price ceiling (price = f(committed key count)); removing the old local floor (
b52f4577) left no revenue floor. Since the verifier accepts 1..=7 quote proofs and fully validates only the median candidate, a modified client can fetch quotes from the whole neighbourhood and settle only the cheapest valid one. The flexible bundles from #136 were safe while the old floor stood; the combination is the hole, and one-quote clients make it routine.This PR restores the floor on the receiver, priced from the receiver's own live commitment:
alongside the existing
settled >= 3 x mediancheck. The floor compares the settled amount, so an honest client may overpay a cheap quote to clear stricter receivers.Design points
CommitmentSource::current_binding_snapshot()— the exact sourceQuoteGeneratorprices from, wired inAntProtocol::attach_commitment_source. Nevercurrent_chunks()(the unlike-counts comparison that false-rejected honest quotes under the pre-ADR-0004 floor), and nevercurrent_binding_for_quote()(which would extend commitment answerability as a side effect).calculate_price(0), making it vacuous rather than an outage mode.ant_node::payment::price_floor) with paid price, settled amount, local count/price, and the would-reject verdict. Enforcement is a per-node canary opt-in viaANT_PRICE_FLOOR_ENFORCE=1(tolerance overrideANT_PRICE_FLOOR_TOLERANCE_PERCENT, default 50%); unsetting it is the kill switch. Clients need 4-of-20 stores, so canary enforcement cannot fail honest uploads while telemetry calibrates the tolerance.VerificationContext::FreshReplicationsplits fan-out from direct PUT — verified identically everywhere (store-strength cache, cross-check, first-audit) so behaviour is unchanged; the floor applies to both (a single cheap accepting node must not fan a below-floor proof around everyone else's floor), and never to paid-list admission, repair, or cache hits. No historical receipt is repriced. Merkle proofs untouched.Tests
-D warnings), fmt, and docs clean.Rollout
price_floortelemetry for the settled/local-price ratio distribution.