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
2 changes: 1 addition & 1 deletion ci/admitted_predecessor_readers_fenced.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ declare -a EXEMPT=(
"$core/dsm_sdk/src/sdk/economic_admission_flow.rs|sources the predecessor root rather than checking a supplied one; also names a (position, root) pair for a foreign verifier, and re-derives nothing"
"$core/dsm/src/economic/peer_lineage.rs|builds a SingleRoot from this verifier's own settled memo, never from the admitted store"
"$core/dsm/src/economic/lineage.rs|DEFINES the reader; the only production mention is its own doc"
"$core/dsm_sdk/src/sdk/sofi_evidence.rs|gathers SetupValid evidence: the claim this lineage accepted at a setup's position (SoFi Amendment S9); it creates no position, and an unresolved position yields no claim because the rehydration refuses it"
"$core/dsm_sdk/src/sdk/sofi_reads.rs|answers the verifier's read of the claim this lineage accepted at a setup's position (SoFi Amendment S9, SetupValid evidence); it creates no position, and an unresolved position yields no claim because the rehydration refuses it"
)

[[ -d "$core" ]] || { echo "[FAIL] $core not found"; exit 1; }
Expand Down
26 changes: 16 additions & 10 deletions ci/sofi_validated_root_constructors.sh
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ known_rehydrate_callers=(
"$core/dsm_sdk/src/sdk/core_sdk.rs" # the head's validated root from the admitted store
"$core/dsm_sdk/src/sdk/economic_admission_flow.rs" # the validated predecessor of a pending admission
"$core/dsm_sdk/src/sdk/sofi_advance.rs" # the pending SoFi position's validated predecessor
"$core/dsm_sdk/src/sdk/sofi_evidence.rs" # accepted_claim_at: the accepted claim at a setup's position
"$core/dsm_sdk/src/sdk/sofi_reads.rs" # accepted_claim_at: the accepted claim at a setup's position, answered to the verifier
)
rehydrate_callers=$(grep -rl 'rehydrate_from_admitted_store' "$core/dsm/src" "$core/dsm_sdk/src" dsm_storage_node/src 2>/dev/null \
| grep -v '_tests\.rs$' | grep -v '/test_support/' | sort)
Expand Down Expand Up @@ -278,25 +278,31 @@ if ! grep -q 'pub(crate) fn resolve_position' "$resolution"; then
fi
echo " ✓ advance_resolved runs the ladder over the established facts and takes no verdict"

# Production callers only: the facts tests state a chain through the memo
# constructor for a fixture vault, which is test text (ci/production_text.py).
# The memo constructor has exactly one production caller: the verifier's
# chain walk (`dsm/src/sofi/resolve.rs`), which anchors the recorded rows at
# the genesis it accepted and checks their links before it stands on them.
# Test text (ci/production_text.py) may state a chain for a fixture.
memo_callers=""
while IFS= read -r f; do
[[ -z "$f" ]] && continue
prod=$(python3 ci/production_text.py "$f")
grep -q 'from_recorded_generations' <<<"$prod" && memo_callers="$memo_callers$f"$'\n'
done < <(grep -rln 'from_recorded_generations' "$core/dsm/src" "$core/dsm_sdk/src" dsm_storage_node/src 2>/dev/null \
grep -q 'from_recorded(' <<<"$prod" && memo_callers="$memo_callers$f"$'\n'
done < <(grep -rln 'from_recorded(' "$core/dsm/src" "$core/dsm_sdk/src" dsm_storage_node/src 2>/dev/null \
| grep -v "sofi/resolution.rs" | sort)
memo_callers=${memo_callers%$'\n'}
expected_memo="$core/dsm_sdk/src/sdk/sofi_chain.rs"
expected_memo="$core/dsm/src/sofi/resolve.rs"
if [[ "$memo_callers" != "$expected_memo" ]]; then
echo "[FAIL] VaultChain::from_recorded_generations must be called only from $expected_memo"
echo "[FAIL] VaultChain::from_recorded must be called only from $expected_memo"
echo " production callers found: ${memo_callers:-none}"
exit 1
fi
count=$(python3 ci/production_text.py "$expected_memo" | grep -c 'from_recorded_generations')
count=$(python3 ci/production_text.py "$expected_memo" | grep -c 'from_recorded(')
if [[ "$count" -ne 1 ]]; then
echo "[FAIL] $expected_memo references from_recorded_generations $count times; the chain's start is one call"
echo "[FAIL] $expected_memo references from_recorded $count times; the chain's start is one call"
exit 1
fi
if grep -rn 'from_recorded_generations' "$core/dsm/src" "$core/dsm_sdk/src" >/dev/null 2>&1; then
echo "[FAIL] the unchecked memo constructor from_recorded_generations is back"
exit 1
fi
literals=$(grep -rn 'EstablishedFacts {' "$core/dsm/src" "$core/dsm_sdk/src" dsm_storage_node/src 2>/dev/null \
Expand All @@ -311,6 +317,6 @@ while IFS= read -r hit; do
exit 1
fi
done <<<"$literals"
echo " ✓ one caller of the chain memo, at the walk's start; facts are built by establish only"
echo " ✓ one caller of the anchored, linked chain memo, at the walk's start; facts are built by establish only"

echo "✓ raw envelope -> verified claim -> registered root: every arrow is opaque"
Original file line number Diff line number Diff line change
Expand Up @@ -672,7 +672,7 @@ mod tests {
};
let mut roots: Vec<D32> = (0..generation).map(|g| [0xF0 ^ (g as u8); 32]).collect();
roots.push(root);
VaultChain::from_recorded_generations(roots)
VaultChain::of_roots_for_test(roots)
}

/// The validated predecessor the fixture's `P` was built on.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1807,7 +1807,7 @@ mod tests {

#[cfg(test)]
#[allow(clippy::disallowed_methods)] // test asserts; a failure here is the signal
mod genesis_acceptance {
pub(crate) mod genesis_acceptance {
//! SoFi §19.8 `GenesisAccepted` over a creation signed with the owner's
//! key and policy bytes that re-hash to their commits. Each test changes
//! one thing the owner's validated creation or `V_0` holds and names the
Expand Down Expand Up @@ -1836,7 +1836,7 @@ mod genesis_acceptance {
}

/// A creation as the owner signed it, with what a verifier fetched.
struct Creation {
pub(crate) struct Creation {
preimage_bytes: Vec<u8>,
operation: Operation,
token_policies: BTreeMap<D32, Vec<u8>>,
Expand Down Expand Up @@ -1900,7 +1900,7 @@ mod genesis_acceptance {
}
}

fn valid() -> Creation {
pub(crate) fn valid() -> Creation {
let pair = (tokens()[0].0, tokens()[1].0);
let (state, market_bytes) = genesis_state(pair);
creation_of(state, market_bytes, pair)
Expand All @@ -1920,7 +1920,7 @@ mod genesis_acceptance {
)
}

fn accept(c: &Creation) -> Result<AcceptedVaultGenesis, GenesisRefusal> {
pub(crate) fn accept(c: &Creation) -> Result<AcceptedVaultGenesis, GenesisRefusal> {
accept_at(c, P_CREATE)
}

Expand Down
1 change: 1 addition & 0 deletions dsm_client/deterministic_state_machine/dsm/src/sofi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub mod lineage;
pub mod publication;
pub mod registration;
pub mod resolution;
pub mod resolve;
pub mod signature;
pub mod smt;
pub mod storage;
Expand Down
206 changes: 196 additions & 10 deletions dsm_client/deterministic_state_machine/dsm/src/sofi/resolution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,15 +160,42 @@ pub fn parent_status(established: Option<[u8; 32]>, claimed: &[u8; 32]) -> Paren
/// A chain is established, never assembled: it starts at an accepted genesis
/// ([`VaultChain::from_genesis`]) and grows by one Core-recomputed
/// consumption at a time ([`VaultChain::extend`]). The one other way in is
/// this verifier's own memo of generations it established before
/// ([`VaultChain::from_recorded_generations`]), which the CI gate
/// this verifier's own memo of generations it established before, anchored
/// at the genesis it accepts now and linked row to row before it is stood on
/// ([`VaultChain::from_recorded`]), which the CI gate
/// `ci/sofi_validated_root_constructors.sh` pins to its one caller. Nothing
/// read off the network becomes a root here.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct VaultChain {
roots: Vec<[u8; 32]>,
}

/// One generation as this device recorded it (`VaultChain::from_recorded`):
/// the root, and — past genesis — the root it was built on and the operation
/// that consumed that root.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RecordedGeneration {
pub generation: u64,
pub root: [u8; 32],
pub pre_root: Option<[u8; 32]>,
pub consumed_by: Option<[u8; 32]>,
}

/// This device's own record of a chain contradicts itself, or the genesis it
/// is anchored at. Not a network status: the local store is incoherent, and
/// nothing is stood on it.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MemoBroken {
pub generation: u64,
pub why: &'static str,
}

impl core::fmt::Display for MemoBroken {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "recorded generation {}: {}", self.generation, self.why)
}
}

/// Why a post state does not extend a chain: it was not built on the chain's
/// head. A chain grows one realized consumption at a time, from its head.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -218,21 +245,80 @@ impl VaultChain {
Ok(())
}

/// THE MEMO PUNCTURE: the generations this verifier itself established
/// earlier, as it recorded them — contiguous from generation zero, a
/// root per generation. A memo proves nothing by existing: what it holds
/// is this device's own earlier conclusion, read back, and a caller that
/// hands it anything else has fabricated a chain. That is why the CI gate
/// pins this constructor to its one caller, the chain walker's start.
pub fn from_recorded_generations(roots: Vec<[u8; 32]>) -> Self {
Self { roots }
/// THE MEMO: the generations this verifier itself established earlier,
/// as it recorded them, anchored at the genesis it accepts NOW and
/// linked one to the next before any of it is stood on. Row zero is the
/// accepted genesis root; every later row was built on the root before
/// it and names the operation that consumed it. A memo proves nothing by
/// existing — what it holds is this device's own earlier conclusion, read
/// back — so what can be checked is checked here, and a record that does
/// not anchor or does not link is a contradiction, never a chain. The CI
/// gate pins this constructor to its one caller, the chain walk's start.
pub fn from_recorded(
genesis: &super::lineage::AcceptedVaultGenesis,
rows: &[RecordedGeneration],
) -> Result<Self, MemoBroken> {
let mut chain = Self::from_genesis(genesis);
for (index, row) in rows.iter().enumerate() {
let generation = u64::try_from(index).map_err(|_| MemoBroken {
generation: row.generation,
why: "generation overflow",
})?;
if row.generation != generation {
return Err(MemoBroken {
generation: row.generation,
why: "the rows are not contiguous from generation zero",
});
}
if generation == 0 {
if row.root != *genesis.genesis_root() {
return Err(MemoBroken {
generation: 0,
why: "the recorded genesis is not the accepted genesis",
});
}
if row.pre_root.is_some() || row.consumed_by.is_some() {
return Err(MemoBroken {
generation: 0,
why: "the genesis generation records a consumption",
});
}
continue;
}
let Some((.., head)) = chain.head() else {
return Err(MemoBroken {
generation,
why: "no head to link to",
});
};
if row.pre_root != Some(head) {
return Err(MemoBroken {
generation,
why: "the generation was not built on the one before it",
});
}
if row.consumed_by.is_none() {
return Err(MemoBroken {
generation,
why: "no operation is recorded as consuming the generation before it",
});
}
chain.roots.push(row.root);
}
Ok(chain)
}

/// `R*_g` for every generation established, in generation order.
pub fn roots(&self) -> &[[u8; 32]] {
&self.roots
}

/// A chain stated for a test of what reads it; in-crate only.
#[cfg(test)]
pub(crate) fn of_roots_for_test(roots: Vec<[u8; 32]>) -> Self {
Self { roots }
}

/// The status of a parent asked about at `generation` — [`parent_status`]
/// over what this chain established there.
pub fn status_of(&self, generation: u64, claimed: &[u8; 32]) -> ParentStatus {
Expand Down Expand Up @@ -992,6 +1078,106 @@ mod tests {
assert_eq!(resolve_position(&realized(&refuted)), Ok(Resolution::Void));
}

/// The genesis the memo tests anchor at, accepted as the fixture owner's
/// creation.
fn accepted_genesis() -> crate::sofi::lineage::AcceptedVaultGenesis {
use crate::sofi::lineage::genesis_acceptance::{accept, valid};
accept(&valid()).expect("the fixture's genesis is accepted")
}

/// Rows as this device records them: the genesis at zero, then each
/// root built on the one before it and consumed by a distinct operation.
fn recorded(genesis: &[u8; 32], roots: &[[u8; 32]]) -> Vec<RecordedGeneration> {
let mut rows = vec![RecordedGeneration {
generation: 0,
root: *genesis,
pre_root: None,
consumed_by: None,
}];
let mut previous = *genesis;
for (i, root) in roots.iter().enumerate() {
rows.push(RecordedGeneration {
generation: i as u64 + 1,
root: *root,
pre_root: Some(previous),
consumed_by: Some([0xC0 | i as u8; 32]),
});
previous = *root;
}
rows
}

/// THE MEMO IS ANCHORED AND LINKED, OR IT IS NOTHING. The rows this
/// device recorded become a chain only from the genesis it accepts now,
/// each generation built on the one before it and consumed by a named
/// operation; a record that does not anchor, does not link, names no
/// consumption or skips a generation is a contradiction and no chain.
/// MUTATION CONTROL: a constructor that takes the rows as they are turns
/// this red.
#[test]
fn the_memo_becomes_a_chain_only_anchored_at_the_genesis_and_linked_row_to_row() {
let genesis = accepted_genesis();
let g = *genesis.genesis_root();
let (r1, r2) = ([0xA1; 32], [0xA2; 32]);

assert_eq!(
VaultChain::from_recorded(&genesis, &[]).unwrap().roots(),
&[g][..],
"no rows: the chain is the genesis alone"
);
let rows = recorded(&g, &[r1, r2]);
assert_eq!(
VaultChain::from_recorded(&genesis, &rows).unwrap().roots(),
&[g, r1, r2][..]
);

// Not anchored: row zero is not the genesis accepted now.
let mut unanchored = rows.clone();
unanchored[0].root = OTHER_ROOT;
assert_eq!(
VaultChain::from_recorded(&genesis, &unanchored)
.unwrap_err()
.generation,
0
);
// The genesis row records a consumption.
let mut consumed_genesis = rows.clone();
consumed_genesis[0].pre_root = Some(OTHER_ROOT);
assert_eq!(
VaultChain::from_recorded(&genesis, &consumed_genesis)
.unwrap_err()
.generation,
0
);
// Not linked: generation 2 was not built on generation 1.
let mut unlinked = rows.clone();
unlinked[2].pre_root = Some(OTHER_ROOT);
assert_eq!(
VaultChain::from_recorded(&genesis, &unlinked)
.unwrap_err()
.generation,
2
);
// No consumption named.
let mut unnamed = rows.clone();
unnamed[1].consumed_by = None;
assert_eq!(
VaultChain::from_recorded(&genesis, &unnamed)
.unwrap_err()
.generation,
1
);
// A generation skipped.
let mut gapped = rows.clone();
gapped.remove(1);
assert_eq!(
VaultChain::from_recorded(&genesis, &gapped)
.unwrap_err()
.generation,
2
);
}

/// A leg that consumed its canonical parent on this operation's E.
fn good_leg() -> LegFacts {
LegFacts {
Expand Down
Loading
Loading