Skip to content
Merged
16 changes: 8 additions & 8 deletions Cargo.lock

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

10 changes: 5 additions & 5 deletions crates/dig-node-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
33 changes: 30 additions & 3 deletions crates/dig-node-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -10016,6 +10022,11 @@ 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
Expand Down Expand Up @@ -10063,6 +10074,8 @@ mod tests {
"epoch_seconds",
"commitments",
"observed_at",
"chain_peak_height",
"chain_peak_timestamp",
])
);
assert_eq!(
Expand All @@ -10075,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]
Expand Down Expand Up @@ -10269,8 +10287,12 @@ 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 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() {
use dig_rpc_protocol::Method;
Expand Down Expand Up @@ -10302,7 +10324,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"));
Expand All @@ -10311,6 +10333,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`
Expand Down
20 changes: 20 additions & 0 deletions crates/dig-node-core/src/rewards/port.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -254,6 +261,19 @@ pub struct DistributorReport {
pub commitments: Vec<CommitmentSlot>,
/// 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
Expand Down
40 changes: 39 additions & 1 deletion crates/dig-node-core/src/seams/dig_rpc/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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" }
}}),
}
}

Expand Down Expand Up @@ -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});
}
Expand Down Expand Up @@ -1108,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});
}
Expand Down Expand Up @@ -1221,12 +1246,25 @@ 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 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.
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});
Expand Down
14 changes: 7 additions & 7 deletions crates/dig-node-core/tests/dependency_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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() {
Expand All @@ -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]
);
Expand Down
4 changes: 2 additions & 2 deletions crates/dig-node-service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>`, 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
Expand Down Expand Up @@ -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.
Expand Down
Loading
Loading