Skip to content

Add ADR-0005 node-side audit tally for earned reward eligibility#169

Open
grumbach wants to merge 2 commits into
WithAutonomi:mainfrom
grumbach:adr-0005-earned-reward-eligibility
Open

Add ADR-0005 node-side audit tally for earned reward eligibility#169
grumbach wants to merge 2 commits into
WithAutonomi:mainfrom
grumbach:adr-0005-earned-reward-eligibility

Conversation

@grumbach

@grumbach grumbach commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

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's report_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.toml temporarily pins ant-protocol at 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-features with the panic/unwrap/expect denies clean, cargo fmt --check clean.

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).
Copilot AI review requested due to automatic review settings July 9, 2026 05:08

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Comment on lines +261 to +263
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);
Comment thread Cargo.toml Outdated
Comment on lines +200 to +206
# 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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 11 changed files in this pull request and generated 4 comments.

Comment on lines +175 to +190
/// `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) {
Comment on lines +220 to +228
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;
}
Comment thread src/payment/quote.rs
Comment on lines +170 to +172
let reporter_peer_id = source.reporter_peer_id();
let payload =
ant_protocol::payment::audit_report_signed_payload(&reporter_peer_id, &nonce, &rows);
Comment on lines +232 to +238
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;
}
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.

2 participants