Add ADR-0005 node-side audit tally for earned reward eligibility#169
Add ADR-0005 node-side audit tally for earned reward eligibility#169grumbach wants to merge 2 commits into
Conversation
Every node already audits its neighbours (ADR-0002/0004); this records the outcomes so a client can decide, at quote collection, whether a quoter has earned a clean audited week at the size it wants to charge for. - audit_tally: per-peer, per-day tally of subtree-audit passes and the size proven, with sticky convictions and a fence for unanswered monetized-pin challenges. Rows and the fence age out with the window; the on-disk snapshot carries a magic+version tag so a format change is rejected cleanly rather than mis-decoded, and the observer starts fresh. - quote: attach the signed audit report (bound to the client's report_nonce) to quote responses. - replication/storage: wire pass/conviction/fence recording into the audit paths and persist the tally with write-on-change atomic rename. - Cargo: temporarily pin ant-protocol to the PR branch carrying the wire types (revert to a published bump once released).
There was a problem hiding this comment.
Pull request overview
Implements the node-side portion of ADR-0005 “earned reward eligibility” by recording subtree-audit outcomes in a per-peer/per-day tally, persisting it across restarts, and attaching a signed audit report (bound to the client-provided nonce) to quote responses so clients can evaluate eligibility at quote-collection time.
Changes:
- Add a new replication audit tally (per peer/day, with conviction + fence semantics) plus snapshot load/persist support.
- Extend quote generation/handling to optionally include a signed ADR-0005 audit report on the wire.
- Wire tally recording into existing subtree-audit paths and connect the replication engine tally into the node’s quote generator during node assembly.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/storage/handler.rs | Adds quote-response audit_report attachment and handler-level wiring/tests for the new report source. |
| src/replication/storage_commitment_audit.rs | Threads audited commitment key-count into subtree pass results so the tally can record “passed at this size”. |
| src/replication/mod.rs | Adds audit-tally state, wiring from audit outcomes (pass/conviction/fence), and snapshot load/persist alongside retention. |
| src/replication/config.rs | Introduces ADR-0005 testnet env knobs to compress audit/neighbor-sync cadence for time-compressed networks. |
| src/replication/audit.rs | Extends AuditTickResult::Passed with optional commitment key-count (only present for subtree lane). |
| src/replication/audit_tally.rs | New module implementing the per-peer/per-day tally, snapshot tagging/serialization, and quote report adapter. |
| src/payment/quote.rs | Adds AuditReportSource and report building/signing logic bound to report_nonce, plus tests. |
| src/node.rs | Wires the replication engine’s tally into the quote generator as the node’s report source. |
| Cargo.toml | Temporarily patches ant-protocol to a git branch containing the new ADR-0005 wire types. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let mut rows: Vec<(&PeerId, &TallyRow)> = self.rows.iter().collect(); | ||
| rows.sort_by_key(|(_, row)| std::cmp::Reverse(row.last_touched_day)); | ||
| rows.truncate(MAX_REPORT_ROWS); |
| # ADR-0005 depends on the audit-report wire types added to ant-protocol in | ||
| # https://github.com/WithAutonomi/ant-protocol/pull/19, not yet on crates.io. | ||
| # Point the pin at that PR branch so this builds in CI. Revert to a published | ||
| # ant-protocol version bump once the wire types are released (evmlib is | ||
| # re-exported through ant-protocol, so no separate evmlib pin is needed). | ||
| [patch.crates-io] | ||
| ant-protocol = { git = "https://github.com/grumbach/ant-protocol.git", branch = "adr-0005-earned-reward-eligibility" } |
Delete every test/testnet-only ADR-0005 seam from the node so no release binary carries them: - the destructive fat-pin cheater and idle-honest seams (ADR5_TEST_CHEAT_DELETE_AFTER_SECS / ADR5_TEST_IDLE_AFTER_SECS), which could delete all stored chunks or refuse storage if an env var leaked; - the compressed-time knobs (ADR5_DAY_SECS and the ADR5_TEST_* cadence overrides), so an untrusted reporter can never redefine a "day" and the tally day is a fixed 86400s in production. The seam functions collapse to their production form and the storage admission paths always proceed, so production behaviour is unchanged; the CI tests direct-drive the tally, so nothing depended on the seams. Also harden persistence and the report producer: - flush the tally eagerly on a conviction or fence, not only on the 30s periodic loop, so a crash cannot lose negative evidence; - give the audit-tally persist/load path its own log labels instead of reusing the commitment-retention ones; - enforce the report row/day/byte caps in the producer before signing, so an over-cap report is never put on the wire. Add the ADR-0005 source document and pin ant-protocol to the immutable PR rev for a reproducible build.
| /// `now_secs` MUST be the observer's real-time clock at the moment the audit | ||
| /// outcome is processed — never a timestamp carried on the wire. All three | ||
| /// record paths pass [`unix_now_secs`], so events are stamped in processing | ||
| /// order and `now_secs` is monotonic non-decreasing. This is what makes | ||
| /// "a pass clears the fence" safe: a pass can only clear a fence that was set | ||
| /// at or before the same wall-clock instant, so an out-of-order/replayed | ||
| /// pass cannot retroactively lift a newer fence. Do not feed this a | ||
| /// caller-supplied or attacker-influenced timestamp. | ||
| pub fn record_pass(&mut self, peer: PeerId, commitment_key_count: u32, now_secs: u64) { | ||
| let today = now_secs / tally_day_secs(); | ||
| self.evict_if_full(&peer, today); | ||
| let row = self.rows.entry(peer).or_default(); | ||
| row.prune(today); | ||
| row.fenced_day = None; | ||
| row.last_touched_day = today; | ||
| match row.days.iter_mut().find(|(day, _)| *day == today) { |
| pub fn record_conviction(&mut self, peer: PeerId, now_secs: u64) { | ||
| let today = now_secs / tally_day_secs(); | ||
| self.evict_if_full(&peer, today); | ||
| let row = self.rows.entry(peer).or_default(); | ||
| row.days.clear(); | ||
| row.fenced_day = None; | ||
| row.convicted_day = Some(today); | ||
| row.last_touched_day = today; | ||
| } |
| let reporter_peer_id = source.reporter_peer_id(); | ||
| let payload = | ||
| ant_protocol::payment::audit_report_signed_payload(&reporter_peer_id, &nonce, &rows); |
| pub fn record_unanswered_monetized_challenge(&mut self, peer: PeerId, now_secs: u64) { | ||
| let today = now_secs / tally_day_secs(); | ||
| self.evict_if_full(&peer, today); | ||
| let row = self.rows.entry(peer).or_default(); | ||
| row.fenced_day = Some(today); | ||
| row.last_touched_day = today; | ||
| } |
|
Parking ADR-0005 (earned reward eligibility) for now: deprioritized while we focus on the ADR-0004 production fixes and capacity recovery. Closing to keep the review queue tidy, will reopen when we revisit. Branch stays intact. |
What
The node side of ADR-0005: earned reward eligibility. Every node already audits its neighbours (ADR-0002/0004); this PR just writes down the results, per peer per day, so a client can decide at quote collection whether a quoter has earned a clean audited week at the size it wants to charge for. Nothing here gates payment on its own; the client-side gate lands in the ant-client PR, and the shared wire types are in ant-protocol#19.
Changes
replication/audit_tally.rs(new) — per-peer, per-day tally of subtree-audit passes and the size proven. Sticky convictions (a caught cheater re-earns its whole week), and a fence that freezes an observer's vouch when a monetized-pin challenge goes unanswered until the peer next passes. Rows and an evidence-less fence age out with the window. The on-disk snapshot carries a magic + version tag so a layout change is rejected cleanly rather than mis-decoded positionally, and the observer safely starts fresh (a lost snapshot only costs one testimony window, re-earned through ordinary audits).payment/quote.rs— attach the node's signed audit report, bound to the client'sreport_nonce, to quote responses.replication/*+storage/handler.rs— wire pass / conviction / fence recording into the existing audit paths and persist the tally with the same write-on-change atomic-rename discipline as the ADR-0004 retention snapshot.Dependency
Builds against the ADR-0005 wire types in WithAutonomi/ant-protocol#19.
Cargo.tomltemporarily pinsant-protocolat that PR branch so CI can build; this reverts to a published version bump once the wire types are released (same release dance as ADR-0004).Testing
cargo test --lib(695 pass, including the tally, fence age-out, and snapshot-tag tests),cargo clippy --all-targets --all-featureswith the panic/unwrap/expect denies clean,cargo fmt --checkclean.