From 0aa97e5d9ec039030b3892a4181da2ce8a466d35 Mon Sep 17 00:00:00 2001 From: Michael Taylor Date: Sun, 27 Sep 2026 05:13:11 -0700 Subject: [PATCH 1/7] build(deps): bump dig-rpc-protocol cascade to 0.14.0 Moves dig-rpc-protocol 0.12->0.14, dig-peer 0.15->0.16, dig-download 0.24->0.25, dig-peer-selector 0.13->0.14 across crates/dig-node-core and crates/dig-node-service Cargo.toml, with the resolved Cargo.lock. dig-rewards-coin stays pinned at 0.8 (not part of this cascade). Refs #3329 Co-Authored-By: Claude Sonnet 5 --- Cargo.lock | 16 ++++++++-------- crates/dig-node-core/Cargo.toml | 10 +++++----- crates/dig-node-service/Cargo.toml | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7f57fc7f..cf278e7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2689,9 +2689,9 @@ dependencies = [ [[package]] name = "dig-download" -version = "0.24.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d705dda562d63c03a1a481087c3b9f632b90982676ea8bb4e55f2cd04c4465fc" +checksum = "2ca48cd305747310b4f760fb260b309b4a2ebddf6cb2a2933679fc3153ea48d1" dependencies = [ "async-trait", "dig-constants 0.11.2", @@ -3151,9 +3151,9 @@ dependencies = [ [[package]] name = "dig-peer" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01657d5ef42a4ebf038d53b3b398997057417558cd6c059f4f58716e68676f34" +checksum = "779734f3bb8e29d4c94fbbe85b33602e228a51d843033c1104d5ced23f1c2743" dependencies = [ "chia-protocol 0.36.1", "chia-traits 0.36.1", @@ -3191,9 +3191,9 @@ dependencies = [ [[package]] name = "dig-peer-selector" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3be02a4acba35b9580f3655c95cf4e127031e100e684069a17fb2829bb4e68b" +checksum = "ac7ca508a390233dffd654ef241dc4743cd273f690147c4ebeea175777c05b51" dependencies = [ "dig-dht", "dig-nat", @@ -3237,9 +3237,9 @@ dependencies = [ [[package]] name = "dig-rpc-protocol" -version = "0.12.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb22a4741239303c9558b00d20c0cb6d9afad965382656bece3d4bbe9641f24" +checksum = "5ef77b4f6f4d6be8f8d479202867ce4e8e33b0f20c34c3e46a0a2d532e727997" dependencies = [ "serde", "serde_json", diff --git a/crates/dig-node-core/Cargo.toml b/crates/dig-node-core/Cargo.toml index 656af50a..b75df00c 100644 --- a/crates/dig-node-core/Cargo.toml +++ b/crates/dig-node-core/Cargo.toml @@ -198,7 +198,7 @@ serde_json = "1" # (0.15.0), `dig-download` (0.24.0) and `dig-peer-selector` (0.13.0) below all moved onto this line # in the same batch, so exactly one `dig-rpc-protocol` still resolves (asserted by # `crates/dig-node-core/tests/dependency_tree.rs`). -dig-rpc-protocol = "0.12" +dig-rpc-protocol = "0.14" # The directed-message base protocol (epic #793/#796): the e2e seal/open pipeline + the typed envelope # the chat subsystem seals into. dig-node is the TRANSPORT — it seals an app-supplied opaque DIGCHAT1 # envelope to the recipient's 0x0010 BLS identity key and dig-gossip directed-sends the sealed bytes. @@ -471,7 +471,7 @@ dig-pex = "0.1.1" # # Moved to 0.24 (dig_ecosystem#3269, final leg): 0.24.0 is on `dig-rpc-protocol` 0.12, matching this # crate's own move to 0.12 above. -dig-download = "0.24" +dig-download = "0.25" # -- The shared peer client (#1283/#1576) ------------------------------------------------------------- # `DigPeer` — the ONE DIG Network peer client: peer_id-pinned mTLS over the full NAT ladder plus typed # RPC. Depended on DIRECTLY (not only transitively through dig-download) because dig-node supplies the @@ -489,7 +489,7 @@ dig-download = "0.24" # # Moved to 0.15 (dig_ecosystem#3269, final leg): 0.15.0 is on `dig-rpc-protocol` 0.12, matching this # crate's own move to 0.12 above. -dig-peer = "0.15" +dig-peer = "0.16" # -- Self-optimizing peer selection (#178) ------------------------------------------------------------ # The decision + learning layer between dig-dht discovery and dig-download execution: it ranks the # providers `find_providers` returns (learning throughput/rtt/reliability + a per-class saturation @@ -525,7 +525,7 @@ dig-peer = "0.15" # # Moved to 0.13 (dig_ecosystem#3269, final leg): 0.13.0 is on `dig-peer ^0.15`, matching this crate's # own move to `dig-peer = "0.15"` above and closing the cascade at `dig-rpc-protocol` 0.12. -dig-peer-selector = "0.13" +dig-peer-selector = "0.14" # The canonical DIG mTLS certificate crate (L00, crates.io). The node's PERSISTENT machine identity # is a CA-signed `dig_tls::NodeCert` minted from the node's own BLS identity key and persisted 0600 in # the data dir (#908 identity boundary: this is the MACHINE key, never a user key). Replaces the @@ -609,7 +609,7 @@ rcgen = "0.13" # # Pinned by the `the_fail_open_anchor_verifier_is_not_reachable_from_a_production_build` test, which # fails if `testkit` ever appears on the production entry. -dig-download = { version = "0.24", features = ["testkit"] } +dig-download = { version = "0.25", features = ["testkit"] } # Captures the peer-facing serve's real emitted tracing records into an in-memory buffer, so the # serve-observability tests (#1595) assert what an operator would actually see in the node log — # and that no payload byte or proof ever reaches it. diff --git a/crates/dig-node-service/Cargo.toml b/crates/dig-node-service/Cargo.toml index 5674b4ea..0148bd17 100644 --- a/crates/dig-node-service/Cargo.toml +++ b/crates/dig-node-service/Cargo.toml @@ -194,7 +194,7 @@ getrandom = "0.2" # Moved to 0.12 (dig_ecosystem#3269), matching `dig-node-core`'s move to 0.12.0 — 0.12 renamed # `RewardSubject` -> `PayeeSubject` and replaced `HalfObservation` with `Half`, and this line # staying at 0.11 would duplicate the wire types the paragraph above warns against. -dig-rpc-protocol = "0.12" +dig-rpc-protocol = "0.14" # The Sage-parity wallet engine (crate `dig_wallet`) — the node-custodied wallet DB + dual-transport # dispatch + seed custody. This shell WIRES it into bring-up (#368): it builds one live @@ -353,7 +353,7 @@ windows-sys = { version = "0.61", features = [ # crate name-for-name. Already a normal dependency above; restated here only so the # integration-test crate can name it, and pinned to the SAME "0.12" line so the guard can # never compare against a different catalogue than the shell compiles against. -dig-rpc-protocol = "0.12" +dig-rpc-protocol = "0.14" # The `never_log` battery (#277) drives the real seed bootstrap against a temp layout so its # sentinels are the ACTUAL minted phrase and device key rather than invented strings. Already a # normal dependency above; restated here only so the integration-test crate can name it. From 4f495e766f4b9e11b3eb304dd849fcd717d5c7e6 Mon Sep 17 00:00:00 2001 From: Michael Taylor Date: Sun, 27 Sep 2026 05:22:48 -0700 Subject: [PATCH 2/7] test(deps): update dependency_tree.rs to the 0.14 cascade literal The exactly-one/version-floor assertion in the_workspace_carries_exactly_one_module_wire_crate stays exactly as strong -- only the expected literal moves from "0.12." to "0.14." to match the resolved dig-rpc-protocol cascade. Refs #3329 Co-Authored-By: Claude Sonnet 5 --- crates/dig-node-core/tests/dependency_tree.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/dig-node-core/tests/dependency_tree.rs b/crates/dig-node-core/tests/dependency_tree.rs index 2665d69f..8576f8d3 100644 --- a/crates/dig-node-core/tests/dependency_tree.rs +++ b/crates/dig-node-core/tests/dependency_tree.rs @@ -96,7 +96,7 @@ fn locked_versions(crate_name: &str) -> Vec<&str> { .collect() } -/// **Proves:** exactly ONE `dig-rpc-protocol` resolves in the workspace, and it is the 0.12 line that +/// **Proves:** exactly ONE `dig-rpc-protocol` resolves in the workspace, and it is the 0.14 line that /// defines the module wire (`ModuleInfo` / `GetModuleInfoParams` / `FetchModuleRangeParams`), the /// recursive-ask contract this node adopted (`GetAvailabilityParams::budget_ms` / `::ask_id`, /// `AvailabilityAnswer::absence_established`, `ErrorCode::ContentMissInconclusive`), AND (#3269) the @@ -110,10 +110,10 @@ fn locked_versions(crate_name: &str) -> Vec<&str> { /// is the point: a consumer's own lock can pin an old patch even when every caret dep and every /// higher-layer bump looks correct. /// -/// **Cascade closed (#3269, final leg):** `dig-node-core` depends on 0.12 directly; `dig-peer` -/// (0.15.0), `dig-download` (0.24.0) and `dig-peer-selector` (0.13.0) all now resolve -/// `dig-rpc-protocol` 0.12 too, so `cargo metadata` resolves exactly one line. This assertion is -/// deliberately left at exactly-one/0.12 (never widened to accept a set — see #836/#1576); if a +/// **Cascade closed (#3329, final leg):** `dig-node-core` depends on 0.14 directly; `dig-peer` +/// (0.16.0), `dig-download` (0.25.0) and `dig-peer-selector` (0.14.0) all now resolve +/// `dig-rpc-protocol` 0.14 too, so `cargo metadata` resolves exactly one line. This assertion is +/// deliberately left at exactly-one/0.14 (never widened to accept a set — see #836/#1576/#3269); if a /// future dependency bump reopens the split, this test goes red again on purpose. #[test] fn the_workspace_carries_exactly_one_module_wire_crate() { @@ -125,9 +125,9 @@ fn the_workspace_carries_exactly_one_module_wire_crate() { majors means two `ModuleInfo` shapes across the module pull's trust boundary" ); assert!( - versions[0].starts_with("0.12."), + versions[0].starts_with("0.14."), "the availability contract plus the #3269 reward RPC surface this node adopted ship in \ - dig-rpc-protocol 0.12; the workspace resolved {} — on an earlier line the canonical items \ + dig-rpc-protocol 0.14; the workspace resolved {} — on an earlier line the canonical items \ simply do not exist and this node would be back to declaring its own", versions[0] ); From f57e46d1e2257a561a09bf3c45e99b3151ae213e Mon Sep 17 00:00:00 2001 From: Michael Taylor Date: Sun, 27 Sep 2026 05:34:29 -0700 Subject: [PATCH 3/7] feat(rewards): thread the dig-rpc-protocol 0.14 chain-view anchor Adds chain_peak_height/chain_peak_timestamp to DistributorReport (port.rs) and GetRewardDistributorResult's struct literal (dispatch.rs), with a new ChainPortError::ChainPeakUnavailable refusal path when a read can't anchor to a chain peak. chain_port.rs reads the peak in the SAME synchronous spawn_blocking body that reads the distributor snapshot, never a later independent call, so the anchor never names a height the rest of the report was not read against. UNVERIFIED: not yet compiled. A cargo check on dig-node-core + dig-node-service is running; this commit is pushed as a checkpoint before that check returns, per this epic's push-early rule. Refs #3329 Co-Authored-By: Claude Sonnet 5 --- crates/dig-node-core/src/lib.rs | 14 ++++++++ crates/dig-node-core/src/rewards/port.rs | 20 +++++++++++ .../src/seams/dig_rpc/dispatch.rs | 20 +++++++++++ .../src/rewards/chain_port.rs | 35 ++++++++++++++++++- 4 files changed, 88 insertions(+), 1 deletion(-) diff --git a/crates/dig-node-core/src/lib.rs b/crates/dig-node-core/src/lib.rs index 53728f35..9d07f4df 100644 --- a/crates/dig-node-core/src/lib.rs +++ b/crates/dig-node-core/src/lib.rs @@ -9906,6 +9906,10 @@ mod tests { ), commitments, observed_at, + // Distinct from `observed_at` (a wall clock) by construction, so a test asserting the + // two fields are threaded independently cannot pass by accident on equal values. + chain_peak_height: 9_000_000 + seed as u64, + chain_peak_timestamp: 1_700_190_000 + seed as u64, } } @@ -9983,6 +9987,8 @@ mod tests { "last_entry_write_at", "entry_set_stale", "observed_at", + "chain_peak_height", + "chain_peak_timestamp", ]), "the wire body's key SET must be exactly this — a struct assertion cannot see a wrong \ key name or an extra field" @@ -10016,6 +10022,14 @@ mod tests { ); assert_eq!(result["entry_set_stale"], json!(report.entry_set_stale)); assert_eq!(result["observed_at"], json!(report.observed_at)); + assert_eq!( + result["chain_peak_height"], + json!(report.chain_peak_height) + ); + assert_eq!( + result["chain_peak_timestamp"], + json!(report.chain_peak_timestamp) + ); } /// **Proves:** `dig.listRewardDistributorCommitments` answers with the port's real values diff --git a/crates/dig-node-core/src/rewards/port.rs b/crates/dig-node-core/src/rewards/port.rs index 1a98205e..eff33be0 100644 --- a/crates/dig-node-core/src/rewards/port.rs +++ b/crates/dig-node-core/src/rewards/port.rs @@ -176,6 +176,13 @@ pub enum ChainPortError { /// SPEC §3.7 clause 4 applies to every attacker-adjacent string, and a chain error is not /// exempt). Other(String), + /// dig-rpc-protocol 0.14 (dig_ecosystem#3262/#3329): the adapter completed its distributor + /// read but could not obtain a chain peak height/timestamp from the SAME read to fill + /// [`DistributorReport::chain_peak_height`]/[`DistributorReport::chain_peak_timestamp`]. Per + /// SPEC §4.5 both fields are required and never `0`-as-absence, so a responder that cannot + /// anchor its answer to a chain view MUST refuse the whole call rather than answer with an + /// invented, stale, or independently-read peak. + ChainPeakUnavailable, } /// One clawback commitment slot, as `dig.listRewardDistributorCommitments` (SPEC §7.4 clause 5) @@ -254,6 +261,19 @@ pub struct DistributorReport { pub commitments: Vec, /// Unix seconds this report was assembled. pub observed_at: u64, + /// The chain peak height the adapter's chain read was taken against — dig-rpc-protocol 0.14's + /// chain-view anchor (dig_ecosystem#3262/#3329, `GetRewardDistributorResult::chain_peak_height` + /// SPEC §4.5). MUST come from the SAME chain read that produced this report, not a later, + /// independent `peak_height()` call: a peak read separately from the snapshot names a height + /// the data did not come from, which is wrong in the most convincing possible way — a plausible + /// number beside stale data, with nothing erroring. Required, never `0`-as-absence: an adapter + /// that cannot obtain the peak alongside its read MUST refuse the whole call + /// (`ChainPortError::ChainPeakUnavailable`) instead of reporting one. + pub chain_peak_height: u64, + /// `block_timestamp(chain_peak_height)` from that SAME chain read — the chain clock + /// `entry_set_stale` is computed against, never the wall clock `observed_at` uses. Same + /// same-read requirement and refusal-not-zero rule as `chain_peak_height` above. + pub chain_peak_timestamp: u64, } /// Reads and the one write this engine needs from the reward-distributor chain state. Derived from diff --git a/crates/dig-node-core/src/seams/dig_rpc/dispatch.rs b/crates/dig-node-core/src/seams/dig_rpc/dispatch.rs index 4f1ecf55..5117f84a 100644 --- a/crates/dig-node-core/src/seams/dig_rpc/dispatch.rs +++ b/crates/dig-node-core/src/seams/dig_rpc/dispatch.rs @@ -74,6 +74,13 @@ const REWARD_INVALID_WITHDRAWAL_SHARE_MACHINE: &str = "REWARD_INVALID_WITHDRAWAL /// `REWARD_INVALID_WITHDRAWAL_SHARE_MACHINE`'s sibling shape. const REWARD_ZERO_IDENTITY_MACHINE: &str = "REWARD_ZERO_IDENTITY"; +/// dig_ecosystem#3262/#3329: the machine code for [`ChainPortError::ChainPeakUnavailable`] — the +/// adapter's distributor read succeeded but it could not anchor a `chain_peak_height`/ +/// `chain_peak_timestamp` from that SAME read, so the whole call is refused rather than answered +/// with an invented or independently-read peak (SPEC §4.5). Sibling shape to the other +/// reward-distributor refusals above. +const REWARD_CHAIN_PEAK_UNAVAILABLE_MACHINE: &str = "REWARD_CHAIN_PEAK_UNAVAILABLE"; + /// Maps a [`ChainPortError`] to the JSON-RPC error response for both reward-distributor read /// methods (dig_ecosystem#3269 unit 2) — one mapping so `dig.getRewardDistributor` and /// `dig.listRewardDistributorCommitments` can never disagree about how a given port failure reads @@ -105,6 +112,12 @@ fn reward_chain_port_error_response(id: &Value, error: &ChainPortError) -> Value "message": format!("reward-distributor chain read failed: {msg}"), "data": { "code": "CONTROL_ERROR", "origin": "control" } }}), + ChainPortError::ChainPeakUnavailable => json!({"jsonrpc":"2.0","id":id,"error":{ + "code": CONTROL_ERROR, + "message": "distributor read succeeded but no chain peak height/timestamp from that \ + same read was available to anchor the result", + "data": { "code": REWARD_CHAIN_PEAK_UNAVAILABLE_MACHINE, "origin": "control" } + }}), } } @@ -1064,6 +1077,13 @@ impl RpcDispatch for Node { last_entry_write_at: report.last_entry_write_at, entry_set_stale: report.entry_set_stale, observed_at: report.observed_at, + // dig-rpc-protocol 0.14 chain-view anchor (dig_ecosystem#3262/#3329, SPEC §4.5): + // both come straight from `report`, i.e. the SAME chain read + // `RewardsChainPort::distributor_report` performed — never a fresh + // `peak_height()` call at this seam, which would anchor the answer to a height + // the rest of the data was never read against. + chain_peak_height: report.chain_peak_height, + chain_peak_timestamp: report.chain_peak_timestamp, }; return json!({"jsonrpc":"2.0","id":id,"result": result}); } diff --git a/crates/dig-node-service/src/rewards/chain_port.rs b/crates/dig-node-service/src/rewards/chain_port.rs index e1f6b49d..ff768927 100644 --- a/crates/dig-node-service/src/rewards/chain_port.rs +++ b/crates/dig-node-service/src/rewards/chain_port.rs @@ -174,7 +174,36 @@ where })?; let first_epoch_start = first_epoch_state.round_time_info.last_update; - report_from_snapshot(&snapshot, launcher_id, comment, first_epoch_start) + // dig-rpc-protocol 0.14 chain-view anchor (dig_ecosystem#3262/#3329, SPEC §4.5): read the peak + // HERE, in the same synchronous `spawn_blocking` body that produced `snapshot` above, never + // later at the RPC seam. A peak read independently of the snapshot would anchor the answer to + // a height the rest of the report was never read against -- a plausible number beside + // possibly-stale data, with nothing erroring. Both reads MUST succeed or the whole call + // refuses; see `ChainPortError::ChainPeakUnavailable`'s doc for why `0` is never a stand-in. + let chain_peak_height = read_chain_peak(source)?; + + report_from_snapshot( + &snapshot, + launcher_id, + comment, + first_epoch_start, + chain_peak_height, + ) +} + +/// Reads the chain peak height and its block timestamp as one pair, refusing rather than +/// substituting `0` if either leg of the read fails -- SPEC §4.5 forbids a zeroed anchor, and `0` +/// height is a claim about genesis, not an absence. +fn read_chain_peak(source: &S) -> Result<(u64, u64), ChainPortError> { + let height = source + .peak_height() + .map_err(|e| ChainPortError::Other(format!("peak height read failed: {e}")))? + .ok_or(ChainPortError::ChainPeakUnavailable)?; + let timestamp = source + .block_timestamp(height) + .map_err(|e| ChainPortError::Other(format!("peak timestamp read failed: {e}")))? + .ok_or(ChainPortError::ChainPeakUnavailable)?; + Ok((u64::from(height), timestamp)) } /// Maps a [`DistributorSnapshot`] plus the launch comment onto the port's [`DistributorReport`]. @@ -185,7 +214,9 @@ fn report_from_snapshot( launcher_id: chia_protocol::Bytes32, comment: dig_rewards_coin::comment::LaunchComment, first_epoch_start: u64, + chain_peak: (u64, u64), ) -> Result { + let (chain_peak_height, chain_peak_timestamp) = chain_peak; let distributor = snapshot.distributor(); let constants = distributor.info.constants; @@ -251,6 +282,8 @@ fn report_from_snapshot( entry_set_stale: snapshot.entry_set_stale(), commitments, observed_at, + chain_peak_height, + chain_peak_timestamp, }) } From 8c37f158e30452887f024827f1ddb41c8a955366 Mon Sep 17 00:00:00 2001 From: Michael Taylor Date: Sun, 27 Sep 2026 09:23:06 -0700 Subject: [PATCH 4/7] feat(rewards): answer claim_loop as NotConsulted and anchor the second result UNVERIFIED CHECKPOINT. Salvaged from a lane killed by a rate limit before its cargo check returned. This has NOT compiled. Committed so a cap cannot take it again -- the previous lane on this task lost everything it had not pushed. Content, described from `git diff --cached` (the index, which is what a commit records) rather than from the working tree: - dispatch.rs: fills dig-rpc-protocol 0.14's chain-view anchor on the second result type, from `report` -- the SAME chain read that produced the rest of the result, never a fresh peak lookup, which would name a height the data did not come from. - dispatch.rs: answers 0.14's required `claim_loop` as `NotConsulted`, dated when the responder established it has nothing to read. This crate holds no claim loop, so a manufactured `Consulted` with invented counts would be the lie this epic keeps producing. - lib.rs: extends the exhaustive-key tripwire's doc comment for `claim_loop`. KNOWN DEFECT IN THIS DIFF, for whoever picks it up: the new comments attribute the claim loop to dig_ecosystem#3421. That is wrong. #3421 is the PROVER's RewardsChainPort. The claim loop lives in dig-node-service/src/rewards_claim/ and belongs to #3268 (wiring, landed) and #3432 (the 13.2 off-chain seam). A false ticket attribution in a doc comment is the exact defect #3292 was filed for; fix it before this merges. Refs #3329 Co-Authored-By: Claude Opus 5 (1M context) --- crates/dig-node-core/src/lib.rs | 5 +++-- .../src/seams/dig_rpc/dispatch.rs | 19 ++++++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/crates/dig-node-core/src/lib.rs b/crates/dig-node-core/src/lib.rs index 9d07f4df..c9fc2417 100644 --- a/crates/dig-node-core/src/lib.rs +++ b/crates/dig-node-core/src/lib.rs @@ -10283,8 +10283,9 @@ mod tests { /// **Proves:** `dig.getPayeeRewardClaimStatus` is CONTROL-tier, NOT peer-reachable, dispatched /// through the `Method` enum match, and its exact serialized JSON body: `subject` is the - /// literal `"payee"`, `claim_log` is `NotConsulted` (no claim log exists in this crate yet), - /// and there is never a monetary amount or payout puzzle hash anywhere in the body. + /// literal `"payee"`, `claim_log` and `claim_loop` are both `NotConsulted` (neither a claim + /// log nor a claim loop exists in this crate yet — the loop is dig_ecosystem#3421's), and + /// there is never a monetary amount or payout puzzle hash anywhere in the body. #[test] fn get_payee_reward_claim_status_answers_the_exact_wire_shape() { use dig_rpc_protocol::Method; diff --git a/crates/dig-node-core/src/seams/dig_rpc/dispatch.rs b/crates/dig-node-core/src/seams/dig_rpc/dispatch.rs index 5117f84a..3ee6e575 100644 --- a/crates/dig-node-core/src/seams/dig_rpc/dispatch.rs +++ b/crates/dig-node-core/src/seams/dig_rpc/dispatch.rs @@ -1128,6 +1128,11 @@ impl RpcDispatch for Node { epoch_seconds: report.epoch_seconds, commitments, observed_at: report.observed_at, + // dig-rpc-protocol 0.14 chain-view anchor, same rule as + // `GetRewardDistributorResult` above: straight from `report`, the SAME chain + // read that produced everything else in this result. + chain_peak_height: report.chain_peak_height, + chain_peak_timestamp: report.chain_peak_timestamp, }; return json!({"jsonrpc":"2.0","id":id,"result": result}); } @@ -1241,12 +1246,24 @@ impl RpcDispatch for Node { // // No monetary amount, ever, and no payout puzzle hash — see `PayeeClaimStatus`'s doc. // No params type: this call takes none. + // + // `claim_loop` (dig-rpc-protocol 0.13.0, required on the 0.14 wire this crate now + // targets, dig_ecosystem#3329): this crate holds no claim loop either -- it is + // #3421's, in `dig-node-service`'s `rewards_claim/**`, which this ticket's brief + // fences off. Same honesty rule as `claim_log` right above: the loop was never + // constructed from here, so the answer is `NotConsulted`, dated at the moment this + // responder established it has nothing to read -- never a manufactured `Consulted` + // with invented counts. Some(Method::GetPayeeRewardClaimStatus) => { use crate::rewards::state::Clock as _; + let now = crate::rewards::state::SystemClock.now_unix_seconds(); let result = dig_rpc_protocol::types::PayeeClaimStatus { subject: dig_rpc_protocol::types::PayeeSubject::Payee, claim_log: dig_rpc_protocol::types::ClaimLogObservation::NotConsulted { - observed_at: crate::rewards::state::SystemClock.now_unix_seconds(), + observed_at: now, + }, + claim_loop: dig_rpc_protocol::types::ClaimLoopObservation::NotConsulted { + observed_at: now, }, }; return json!({"jsonrpc":"2.0","id":id,"result": result}); From 559a1cef204c8c49afe7f165a5d4405c59bd4bb5 Mon Sep 17 00:00:00 2001 From: Michael Taylor Date: Sun, 27 Sep 2026 09:27:58 -0700 Subject: [PATCH 5/7] fix(rewards): correct claim-loop ticket attribution and close the claim_loop exhaustive-key gap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `8c37f158` was a salvage checkpoint pushed unverified. It compiles now (cargo check -p dig-node-core -p dig-node-service: clean, 1m30s). Two follow-ups landed with it: - The doc comments in lib.rs and dispatch.rs attributed the claim loop to dig_ecosystem#3421 (the prover's RewardsChainPort). Wrong: the claim loop lives in dig-node-service's src/rewards_claim/**, owned by #3268 (wiring, landed) and #3432 (the SPEC 13.2 off-chain seam). Corrected both. - get_payee_reward_claim_status_answers_the_exact_wire_shape asserted an exhaustive key set of exactly {subject, claim_log} — stale since 8c37f158 added claim_loop to the wire result. Extended the enumeration to include claim_loop and asserted its NotConsulted shape, same strength as before (no widening). Refs #3329 Co-Authored-By: Claude Sonnet 5 --- crates/dig-node-core/src/lib.rs | 12 ++++++++++-- crates/dig-node-core/src/seams/dig_rpc/dispatch.rs | 7 ++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/crates/dig-node-core/src/lib.rs b/crates/dig-node-core/src/lib.rs index c9fc2417..9df394c5 100644 --- a/crates/dig-node-core/src/lib.rs +++ b/crates/dig-node-core/src/lib.rs @@ -10284,7 +10284,10 @@ mod tests { /// **Proves:** `dig.getPayeeRewardClaimStatus` is CONTROL-tier, NOT peer-reachable, dispatched /// through the `Method` enum match, and its exact serialized JSON body: `subject` is the /// literal `"payee"`, `claim_log` and `claim_loop` are both `NotConsulted` (neither a claim - /// log nor a claim loop exists in this crate yet — the loop is dig_ecosystem#3421's), and + /// log nor a claim loop exists in this crate yet — the loop lives in `dig-node-service`'s + /// `src/rewards_claim/**`, dig_ecosystem#3268 (wiring, landed) / #3432 (the SPEC §13.2 + /// off-chain seam), never dig_ecosystem#3421 (that ticket is the prover's + /// `RewardsChainPort`) — and /// there is never a monetary amount or payout puzzle hash anywhere in the body. #[test] fn get_payee_reward_claim_status_answers_the_exact_wire_shape() { @@ -10317,7 +10320,7 @@ mod tests { .collect(); assert_eq!( keys, - std::collections::BTreeSet::from(["subject", "claim_log"]), + std::collections::BTreeSet::from(["subject", "claim_log", "claim_loop"]), "no monetary amount, no payout puzzle hash — ever: {resp}" ); assert_eq!(result["subject"], json!("payee")); @@ -10326,6 +10329,11 @@ mod tests { result["claim_log"].get("claims_submitted_count").is_none(), "claims_submitted_count must live INSIDE Consulted only, never beside NotConsulted: {resp}" ); + assert_eq!(result["claim_loop"]["outcome"], json!("not_consulted")); + assert!( + result["claim_loop"].get("claims_submitted_count").is_none(), + "claim_loop's count must live INSIDE Consulted only, never beside NotConsulted: {resp}" + ); } /// **Proves:** dig_ecosystem#3269 unit 5 — a chain-derived report with a ZEROED `launcher_id` diff --git a/crates/dig-node-core/src/seams/dig_rpc/dispatch.rs b/crates/dig-node-core/src/seams/dig_rpc/dispatch.rs index 3ee6e575..e3dc6741 100644 --- a/crates/dig-node-core/src/seams/dig_rpc/dispatch.rs +++ b/crates/dig-node-core/src/seams/dig_rpc/dispatch.rs @@ -1248,9 +1248,10 @@ impl RpcDispatch for Node { // No params type: this call takes none. // // `claim_loop` (dig-rpc-protocol 0.13.0, required on the 0.14 wire this crate now - // targets, dig_ecosystem#3329): this crate holds no claim loop either -- it is - // #3421's, in `dig-node-service`'s `rewards_claim/**`, which this ticket's brief - // fences off. Same honesty rule as `claim_log` right above: the loop was never + // targets, dig_ecosystem#3329): this crate holds no claim loop either -- it lives in + // `dig-node-service`'s `src/rewards_claim/**` (dig_ecosystem#3268 wired it, landed; + // #3432 is the SPEC §13.2 off-chain seam), which this ticket's brief fences off. Same + // honesty rule as `claim_log` right above: the loop was never // constructed from here, so the answer is `NotConsulted`, dated at the moment this // responder established it has nothing to read -- never a manufactured `Consulted` // with invented counts. From 56fbfc8c310013c26784891a72c62ebc181657ff Mon Sep 17 00:00:00 2001 From: Michael Taylor Date: Sun, 27 Sep 2026 09:31:41 -0700 Subject: [PATCH 6/7] style(rewards): fmt the chain-peak-height assertion the salvage commit left unformatted cargo fmt --all -- --check was red on one line in the f57e46d1/8c37f158 salvage; cargo fmt --all now passes clean. Refs #3329 Co-Authored-By: Claude Sonnet 5 --- crates/dig-node-core/src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/dig-node-core/src/lib.rs b/crates/dig-node-core/src/lib.rs index 9df394c5..8a67895f 100644 --- a/crates/dig-node-core/src/lib.rs +++ b/crates/dig-node-core/src/lib.rs @@ -10022,10 +10022,7 @@ mod tests { ); assert_eq!(result["entry_set_stale"], json!(report.entry_set_stale)); assert_eq!(result["observed_at"], json!(report.observed_at)); - assert_eq!( - result["chain_peak_height"], - json!(report.chain_peak_height) - ); + assert_eq!(result["chain_peak_height"], json!(report.chain_peak_height)); assert_eq!( result["chain_peak_timestamp"], json!(report.chain_peak_timestamp) From 1ccc2ede58463d94b6d0e1ae1e4e7deddef6a685 Mon Sep 17 00:00:00 2001 From: Michael Taylor Date: Sun, 27 Sep 2026 09:44:57 -0700 Subject: [PATCH 7/7] fix(rewards): the commitments dispatch test's exhaustive key set had gone stale under 0.14 cargo test -p dig-node-core --lib caught it: list_reward_distributor_commitments_answers_with_real_values_through_dispatch still asserted the pre-0.14 key set on GetListRewardDistributorCommitments's result, so it failed the moment chain_peak_height/chain_peak_timestamp landed on the wire. Extended the enumeration and added value assertions for both fields, same strength as the sibling GetRewardDistributorResult test above it. Full `cargo test -p dig-node-core --lib`: 1238 passed, 0 failed (was 1237 passed, 1 failed before this commit). Refs #3329 Co-Authored-By: Claude Sonnet 5 --- crates/dig-node-core/src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/dig-node-core/src/lib.rs b/crates/dig-node-core/src/lib.rs index 8a67895f..aadec070 100644 --- a/crates/dig-node-core/src/lib.rs +++ b/crates/dig-node-core/src/lib.rs @@ -10074,6 +10074,8 @@ mod tests { "epoch_seconds", "commitments", "observed_at", + "chain_peak_height", + "chain_peak_timestamp", ]) ); assert_eq!( @@ -10086,6 +10088,11 @@ mod tests { ); assert_eq!(result["epoch_seconds"], json!(report.epoch_seconds)); assert_eq!(result["observed_at"], json!(report.observed_at)); + assert_eq!(result["chain_peak_height"], json!(report.chain_peak_height)); + assert_eq!( + result["chain_peak_timestamp"], + json!(report.chain_peak_timestamp) + ); let commitments = result["commitments"].as_array().unwrap(); assert_eq!(commitments.len(), 1); let row_keys: std::collections::BTreeSet<&str> = commitments[0]