Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 19 additions & 0 deletions dsm_client/deterministic_state_machine/dsm/src/economic/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,25 @@ fn read_leaf_state(c: &mut Cursor<'_>) -> Result<EconomicLeafState, DecodeError>
},
))
}
// The fifth arm (2c-D, `0x0032`). Missing from #852, which added the
// encoder, the key, the value and the pre-state match arm but not
// this one — so every market settle's witness, inclusion proof and
// cached leaf became undecodable. Mirrors the encoder exactly: two
// `digest32` and no rejection, because the encoder refuses nothing
// and a decoder stricter than its encoder makes valid leaves
// unreadable.
class::ECONOMIC_BUNDLE_ACCEPTANCE_STATE => {
c.envelope(
crate::economic::state::EconomicBundleAcceptanceState::CLASS,
crate::economic::state::EconomicBundleAcceptanceState::SCHEMA,
)?;
Ok(EconomicLeafState::BundleAcceptance(
crate::economic::state::EconomicBundleAcceptanceState {
bundle: c.digest32()?,
economic_operation_id: c.digest32()?,
},
))
}
got => Err(DecodeError::WrongClass { got }),
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -442,4 +442,24 @@ mod tests {
decode_economic_proof_artifact(&wrong.encode_to_vec()).expect("shape is still valid");
assert!(decoded.verify_against(&G, &D, 7, &tree.root()).is_err());
}

/// #852 REGRESSION. A settle's inclusion proof carries the bundle-acceptance
/// leaf (it is citable: `TA_B` asks a stranger to verify it), so the
/// artifact must decode and verify with that leaf in it.
#[test]
fn an_artifact_proving_a_bundle_acceptance_leaf_decodes_and_verifies() {
let acceptance = EconomicLeafState::BundleAcceptance(
crate::economic::state::EconomicBundleAcceptanceState {
bundle: [0xB0; 32],
economic_operation_id: [0x50; 32],
},
);
let (tree, artifact) = published(&[reserve(0xA1, 10_000, 0), acceptance]);
let decoded = decode_economic_proof_artifact(&artifact.encode())
.expect("a settle's proof artifact decodes");
decoded
.verify_against(&G, &D, 7, &tree.root())
.expect("and verifies against the root the reader names");
assert_eq!(decoded.states().count(), 2);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ use dsm::economic::credit::{
use dsm::economic::decode::{decode_credit_source, decode_leaf_state, decode_transition_witness};
use dsm::economic::mutation::EconomicLeafMutation;
use dsm::economic::state::{
EconomicBalanceState, EconomicConsumedSourceState, EconomicLeafState,
EconomicSettlementReceiptState, EconomicVaultReserveState,
EconomicBalanceState, EconomicBundleAcceptanceState, EconomicConsumedSourceState,
EconomicLeafState, EconomicSettlementReceiptState, EconomicVaultReserveState,
};
use dsm::economic::tree::ECONOMIC_SMT_HEIGHT;
use dsm::economic::witness::EconomicTransitionWitness;
Expand Down Expand Up @@ -349,6 +349,55 @@ fn a_receipt_cannot_assert_a_receipt_id_its_contents_do_not_produce() {
}
}

/// EVERY leaf class the tree can hold decodes back to itself.
///
/// The `match` has no wildcard ON PURPOSE: a sixth `EconomicLeafState` arm
/// fails to COMPILE here until a sample of it is added — and the sample then
/// fails at runtime until the decoder has an arm for it. #852 added the fifth
/// arm's encoder, key, value and pre-state handling but not its decoder, and
/// nothing noticed, because no test decoded one. This is that test.
#[test]
fn every_leaf_class_round_trips_through_the_decoder() {
let samples = [
EconomicLeafState::Balance(EconomicBalanceState::new(ERA, 10).expect("balance")),
EconomicLeafState::VaultReserve(EconomicVaultReserveState {
vault_id: VAULT,
policy_commit: ERA,
amount: 5,
vault_sequence: 2,
}),
EconomicLeafState::SettlementReceipt(
EconomicSettlementReceiptState::new(VAULT, [7; 32], 4, 5, ERA, 10, SOFI, 9)
.expect("valid"),
),
EconomicLeafState::ConsumedSource(EconomicConsumedSourceState {
source_id: [1; 32],
consumer_economic_operation_id: [2; 32],
}),
EconomicLeafState::BundleAcceptance(EconomicBundleAcceptanceState {
bundle: [0xB0; 32],
economic_operation_id: [0x50; 32],
}),
];
let mut covered = [false; 5];
for state in &samples {
let slot = match state {
EconomicLeafState::Balance(_) => 0,
EconomicLeafState::VaultReserve(_) => 1,
EconomicLeafState::SettlementReceipt(_) => 2,
EconomicLeafState::ConsumedSource(_) => 3,
EconomicLeafState::BundleAcceptance(_) => 4,
};
covered[slot] = true;
let bytes = state.encode().expect("encodable");
assert_eq!(
&decode_leaf_state(&bytes).expect("every leaf the encoder emits must decode"),
state
);
}
assert!(covered.iter().all(|c| *c), "one sample per leaf class");
}

// ── The manifest's derived provenance index ────────────────────────────────

fn manifest(addrs: Vec<[u8; 32]>) -> EconomicAdmissionManifest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -409,3 +409,24 @@ fn the_publication_address_is_the_canonical_identity() {
"the namespace's inner digest must be ta_B itself"
);
}

/// #852 REGRESSION. The settle's own witness — what the resume path decodes
/// from frozen state and what every foreign lineage walk decodes from the
/// fleet — carries the bundle-acceptance leaf, and must decode to itself.
#[test]
fn a_settle_witness_carrying_the_acceptance_leaf_decodes_to_itself() {
let (witness, _) = settled();
assert!(
witness
.mutations
.iter()
.any(|m| matches!(m.post_state, Some(EconomicLeafState::BundleAcceptance(_)))),
"the fixture must actually carry the leaf, or this test proves nothing"
);
let bytes = witness.encode().expect("encodable");
assert_eq!(
dsm::economic::decode::decode_transition_witness(&bytes)
.expect("a market settle's witness decodes"),
witness
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8412,6 +8412,51 @@ mod funded_creation_tests {
);
}

/// #852 REGRESSION, END TO END: a trader that settled must still be able to
/// start its NEXT economic admission.
///
/// Every admission begins by rebuilding its pre-state from this device's
/// leaf cache, and after a market settle that cache holds the
/// bundle-acceptance leaf. With no decoder arm for it the rebuild failed
/// at the first cached leaf, so a trader who settled once could admit
/// nothing — no send, no second trade — ever again.
#[test]
#[serial]
fn a_trader_that_settled_can_still_build_its_next_pre_state() {
install_identity();
let (vault_id, (pc_a, pc_b), _owner_dev, traders) =
market_with_traders("sofi/spec/next-pre-state", &[("trader0", 0x51)]);
let trader_dev = &traders[0];
trader_dev.enter();
let trader = trader_dev.router();
let (res, _x) = trader_settles(
trader,
&trader_dev.ak_pk.clone(),
&trader_dev.device_id,
&vault_id,
&pc_a,
&pc_b,
0,
(10_000, 5_000),
1_000,
crate::sdk::routing_path_sdk::constant_product_output(1_000, 10_000, 5_000, 30)
.expect("curve output"),
0x71,
);
assert!(res.success, "the settle binds: {:?}", res.error_message);

let validated =
crate::sdk::economic_admission_flow::validated_root_or_activate(&trader.core_sdk)
.expect("the settle admitted a root");
let (_tree, pre) =
crate::sdk::economic_admission_flow::producer_tree_and_pre_state(&validated)
.expect("the next admission can rebuild its pre-state from the leaf cache");
assert!(
!pre.balances.is_empty(),
"and the rebuilt pre-state carries the trader's balances"
);
}

/// 2c-D PRODUCER ADOPTION, OVER THE LIVE ROUTE: a settled market publishes
/// the canonical `TA_B` for the bundle it accepted — and realizes nothing.
///
Expand Down
Loading