Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
695443f
feat(rewards): peer claim loop skeleton -- discovery, cadence, config…
MichaelTaylor3d Sep 9, 2026
c6654d2
test(rewards): write all twelve acceptance tests for the peer claim loop
MichaelTaylor3d Sep 9, 2026
96d19a8
feat(rewards): wire the seven rewards_claim submodules into the crate
MichaelTaylor3d Sep 9, 2026
35577f4
style(rewards): cargo fmt the rewards_claim submodules
MichaelTaylor3d Sep 9, 2026
7d5194c
chore(deps): bump dig-node-control-interface 0.33->0.35, dig-rpc-prot…
MichaelTaylor3d Sep 9, 2026
51516e6
chore(deps): revert dig-node-control-interface and dig-rpc-protocol b…
MichaelTaylor3d Sep 9, 2026
1701759
fix(rewards-claim): fault laundering, permanent no-entry blacklist, f…
MichaelTaylor3d Sep 9, 2026
e9553f1
fix(rewards-claim): refuse a claim entry for the wrong payout puzzle …
MichaelTaylor3d Sep 9, 2026
e80e9a0
fix(rewards-claim): B1 -- ClaimableButNotClaiming is a magnitude comp…
MichaelTaylor3d Sep 9, 2026
c448272
fix(rewards-claim): B2/B3 -- value-ordered budget with rotation, per-…
MichaelTaylor3d Sep 9, 2026
3ef601f
fix(rewards-claim): R5 -- document not-yet-wired loop; persist B2 rot…
MichaelTaylor3d Sep 9, 2026
749c7a7
fix(rewards-claim): clippy collapsible-match + SPEC v0.1.3 wording re…
MichaelTaylor3d Sep 9, 2026
a64d148
fix(rewards-claim): add missing rotation_cursor field in cadence.rs t…
MichaelTaylor3d Sep 9, 2026
5cdf832
fix(rewards-claim): F1 -- ChainSourceUnavailable is per-cycle, never …
MichaelTaylor3d Sep 9, 2026
28c1b68
fix(rewards-claim): F3/F4/F5 -- fix stale counters, dedup candidates,…
MichaelTaylor3d Sep 9, 2026
5aaeae3
fix(rewards_claim): fold payout-hash mismatches into the shortfall pr…
MichaelTaylor3d Sep 9, 2026
a09205e
fix(rewards-claim): F1/F3 -- AtomicU32 in fake ports, not Mutex, keep…
MichaelTaylor3d Sep 9, 2026
a2aa956
fix(rewards-claim): F7 -- persist the fee window and cadence gate acr…
MichaelTaylor3d Sep 9, 2026
24b7b63
fix(rewards-claim): F7 -- update cadence.rs test literal for new pers…
MichaelTaylor3d Sep 9, 2026
f478516
fix(rewards-claim): F8/F14 -- atomic state write, fail closed on a co…
MichaelTaylor3d Sep 9, 2026
165c8f4
fix(rewards-claim): negate with ! rather than the unimported Not trait
MichaelTaylor3d Sep 9, 2026
1b104f9
fix(rewards-claim): F13 -- correct stale test literals to the folded …
MichaelTaylor3d Sep 9, 2026
730aa0b
feat(rewards-claim): add ClaimOutcome::Faulted variant
MichaelTaylor3d Sep 9, 2026
e3bd3dd
fix(rewards-claim): both fault arms now push ClaimOutcome::Faulted
MichaelTaylor3d Sep 9, 2026
5f729d1
test(rewards-claim): a failed submission produces a Faulted outcome
MichaelTaylor3d Sep 9, 2026
b93b1db
fix(rewards-claim): delete fee_window_poisoned, stop latching self-he…
MichaelTaylor3d Sep 10, 2026
2313411
fix(rewards-claim): satisfy clippy doc-list indent and rustfmt
MichaelTaylor3d Sep 10, 2026
99c278e
test: THROWAWAY revert-check -- remove 4 config.rs corrupt-load enfor…
MichaelTaylor3d Sep 10, 2026
28dbdd3
fix: THROWAWAY -- drop now-unused mut after removing the clamp mutation
MichaelTaylor3d Sep 10, 2026
77d06f1
test: THROWAWAY -- disable nextest fail-fast so all 5 revert-check te…
MichaelTaylor3d Sep 10, 2026
055a045
fix: THROWAWAY -- move fail-fast=false to [profile.default], CI passe…
MichaelTaylor3d Sep 10, 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
5 changes: 5 additions & 0 deletions .config/nextest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
# per-test override here rather than removing this backstop.
[profile.default]
slow-timeout = { period = "60s", terminate-after = 3 }
# THROWAWAY (Finding 3 revert-check diagnostic, never merged): CI invokes cargo nextest
# run WITHOUT --profile, so [profile.ci] settings never applied here -- fail-fast must
# live on [profile.default] to disable it, so a deliberately reverted enforcement does
# not cancel the rest of the suite before the other under-test config.rs tests run.
fail-fast = false

# CI runs `cargo llvm-cov nextest` with `--retries 2`; the same backstop must apply there so a hang
# on CI is terminated identically to a local run.
Expand Down
30 changes: 15 additions & 15 deletions Cargo.lock

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

6 changes: 6 additions & 0 deletions crates/dig-node-service/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ pub mod peers;
/// The passthrough relay guard (#1997): whether this node relays an unimplemented method to an
/// upstream, and the bring-up probe that proves an upstream is not this node itself. See [`relay`].
pub mod relay;
/// The node's PEER-SIDE reward claim loop (DIG-Network/dig_ecosystem#3251): discovers the
/// reward distributors covering the `(store_id, root)`s this node mirrors and submits
/// `InitiatePayout` on a jittered cadence, default 24h. The other half of the reward-distributor
/// lifecycle from `dig_node_core::rewards` (#3250, the funder-side prover, a sibling lane). See
/// [`rewards_claim`].
pub mod rewards_claim;
pub mod rpc;
/// The offline `wallet export-seed` rescue command: a local read of this node's
/// encrypted seed file. Adds no network surface, and is removed with node-side custody.
Expand Down
85 changes: 85 additions & 0 deletions crates/dig-node-service/src/rewards_claim/cadence.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//! Claim cadence + jitter (SPEC §8.6): the peer's own setting, never curried on the distributor,
//! and jittered so a network of peers on the default does not converge on one minute of the day.
//!
//! The jitter SOURCE is injected — never a global RNG or clock read directly — so the schedule is
//! deterministic under test.

/// SPEC §8.6: the minimum jitter spread every peer MUST apply.
pub const CLAIM_JITTER_SECONDS_DEFAULT: u64 = 3_600;

/// Supplies the jitter offset for one scheduling decision. A production implementation draws from
/// the OS CSPRNG; tests inject a fixed or sequenced value.
pub trait JitterSource: Send + Sync {
/// An offset in `0..=bound` seconds.
fn jitter_seconds(&self, bound: u64) -> u64;
}

/// A jitter source that always returns the same value — for deterministic tests.
pub struct FixedJitter(pub u64);

impl JitterSource for FixedJitter {
fn jitter_seconds(&self, bound: u64) -> u64 {
self.0.min(bound)
}
}

/// The next cadence interval, in seconds: `cadence_seconds + jitter`, where `jitter` is drawn from
/// `[0, jitter_seconds]` via the injected source (SPEC §8.6). `jitter_seconds` is a lower bound on
/// the SPREAD available to the source, not a fixed addition — a source that always returns `0`
/// still produces a schedule within the required bound, just at its floor.
#[must_use]
pub fn next_interval_seconds(
cadence_seconds: u64,
jitter_seconds: u64,
source: &dyn JitterSource,
) -> u64 {
let offset = source.jitter_seconds(jitter_seconds);
cadence_seconds.saturating_add(offset)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn interval_is_cadence_plus_a_bounded_jitter() {
let cadence = 86_400;
let jitter_bound = 3_600;

let at_floor = next_interval_seconds(cadence, jitter_bound, &FixedJitter(0));
assert_eq!(at_floor, cadence);

let at_ceiling = next_interval_seconds(cadence, jitter_bound, &FixedJitter(jitter_bound));
assert_eq!(at_ceiling, cadence + jitter_bound);

// A source that tries to exceed the bound is clamped by the source contract itself
// (FixedJitter here), and the composed interval never exceeds cadence + jitter_seconds.
let over = next_interval_seconds(cadence, jitter_bound, &FixedJitter(jitter_bound * 10));
assert!(over <= cadence + jitter_bound);
assert!(over >= cadence);
}

/// ACCEPTANCE 8 (part) — a config setting a cadence OTHER than the default is honoured by the
/// scheduling function, not silently overridden back to `CLAIM_CADENCE_SECONDS_DEFAULT`.
#[test]
fn a_non_default_configured_cadence_is_honoured() {
let cfg = super::super::config::RewardsClaimConfig {
enabled: true,
cadence_seconds: 12_000,
jitter_seconds: 500,
max_fee_mojos: 1,
max_cycle_fee_budget_mojos: 10,
rotation_cursor: None,
..super::super::config::RewardsClaimConfig::default()
};
let interval =
next_interval_seconds(cfg.cadence_seconds, cfg.jitter_seconds, &FixedJitter(0));
assert_eq!(
interval, 12_000,
"configured cadence, not the 86_400 default"
);
let interval_at_ceiling =
next_interval_seconds(cfg.cadence_seconds, cfg.jitter_seconds, &FixedJitter(500));
assert_eq!(interval_at_ceiling, 12_500);
}
}
Loading
Loading