Skip to main content

StateSnapshot

Struct StateSnapshot 

Source
pub struct StateSnapshot {
    pub version: u8,
    pub entity_id: EntityId,
    pub through_seq: u64,
    pub chain_link: CausalLink,
    pub state: Bytes,
    pub horizon: ObservedHorizon,
    pub created_at: u64,
    pub bindings_bytes: Vec<u8>,
    pub identity_envelope: Option<IdentityEnvelope>,
    pub head_payload: Option<Bytes>,
}
Expand description

A serializable state snapshot at a point in the causal chain.

Fields§

§version: u8

Wire version this snapshot was produced under. Writers always stamp SNAPSHOT_VERSION; readers accept v0 bytes by surfacing them with the v1 defaults populated.

§entity_id: EntityId

Entity this snapshot belongs to.

§through_seq: u64

Sequence number this snapshot is valid through.

§chain_link: CausalLink

CausalLink at the snapshot point (for chain verification).

§state: Bytes

Serialized daemon state (opaque bytes).

§horizon: ObservedHorizon

The entity’s observed horizon at snapshot time.

§created_at: u64

Timestamp when snapshot was taken (unix nanos).

§bindings_bytes: Vec<u8>

Opaque wire slot for channel-re-bind metadata populated by DAEMON_CHANNEL_REBIND_PLAN.md. Stage 1 of the identity-migration plan lands this as an always-empty Vec so the wire format is forward-compatible with the channel-re-bind work even though the typed DaemonBindings decoder isn’t yet present. Plan #1 will decode these bytes into its own struct at restore time.

§identity_envelope: Option<IdentityEnvelope>

Encrypted ed25519 seed + attestation for cross-node identity transport. Populated by DAEMON_IDENTITY_MIGRATION_PLAN.md Stage 3; Stage 1 always emits None. A None envelope on restore means “public-identity migration” — the target gets a read-only keypair that can still serve entity_id / origin_hash queries but refuses to sign anything new.

§head_payload: Option<Bytes>

Runtime-only: payload bytes of the event at chain_link.sequence. Required by super::log::EntityLog::from_snapshot to validate the next event’s parent_hash after restore (the chain validator computes xxh3(prev_link_bytes ++ prev_payload)).

Not serializedto_bytes / from_bytes skip this field, so the wire format is unchanged. Callers reconstructing a snapshot from the log have the head event in hand and populate this via with_head_payload before passing the snapshot to restore. Cross-node migration carries the head event through the migration message itself, paired with the snapshot bytes.

Option<Bytes> so the “no head_payload context attached” case is structurally distinct from “head event genuinely had an empty payload.” An empty-Bytes sentinel would conflate both: assess_continuity would reject legitimate non-genesis snapshots whose head event happens to carry an empty payload as if they were missing-context. With the Option, Some(Bytes::new()) is “head event payload is empty” (legitimate) and None is “caller hasn’t populated this field” (verification can’t proceed).

Default for snapshots deserialized from wire bytes is None; callers populate it from the head event via with_head_payload before EntityLog::from_snapshot can validate subsequent events.

Implementations§

Source§

impl StateSnapshot

Source

pub const HEADER_SIZE: usize

Compact header size (excluding state payload). Historical constant from the v0 compat path — kept for any external caller that may still reference it for sizing math.

Source

pub fn new( entity_id: EntityId, chain_link: CausalLink, state: Bytes, horizon: ObservedHorizon, ) -> StateSnapshot

Create a new snapshot stamped with the current wire version and empty v1 extension fields.

Source

pub fn with_head_payload(self, head_payload: Bytes) -> StateSnapshot

Attach the head event’s payload bytes — needed by EntityLog::from_snapshot to validate the next event’s chain link after restore. Genesis snapshots (chain_link.sequence == 0) carry empty bytes; subsequent snapshots carry the payload of the event at chain_link.sequence.

Source

pub fn with_identity_envelope( self, source_kp: &EntityKeypair, target_static_pub: [u8; 32], ) -> Result<StateSnapshot, EnvelopeError>

Attach an identity envelope sealed to target_static_pub, returning self by value so the call chains cleanly off Self::new / the source’s snapshot-build path.

Fails with EnvelopeError::SourceReadOnly when source_kp is public-only — a public-only caller can’t produce the attestation signature the target needs to verify. The attestation transcript binds to self.chain_link, so the resulting envelope is non-replayable at a different migration point.

Source

pub fn open_identity_envelope( &self, target_x25519_priv: &StaticSecret, ) -> Result<Option<EntityKeypair>, EnvelopeError>

Open the attached identity envelope (if any) using the target’s X25519 static private key. Returns the daemon’s fully-keyed EntityKeypair, which the target-side restore path uses instead of the caller-supplied fallback.

Returns Ok(None) when the snapshot has no envelope — callers interpret this as “public-identity migration, target gets a read-only keypair.” Returns Err if the envelope is present but fails to verify / unseal; callers must treat that as a terminal error, not a fallback trigger, or an attacker could downgrade identity transport by tampering.

Source

pub fn to_bytes(&self) -> Vec<u8>

Serialize to bytes for transfer.

§v1 wire format
magic:             4 bytes (b"CDS1")
version:           1 byte  (SNAPSHOT_VERSION)
entity_id:        32 bytes
through_seq:       8 bytes
chain_link:       CAUSAL_LINK_SIZE bytes (28)
created_at:        8 bytes
state_len:         4 bytes (u32)
state:             state_len bytes
bindings_len:      4 bytes (u32)
bindings:          bindings_len bytes (opaque; see `bindings_bytes`)
envelope_flag:     1 byte (0 = none, 1 = present)
[envelope:       208 bytes]  (if envelope_flag == 1)

Horizon and head_payload are not serialized in the compact format — head_payload is a runtime-only field populated by the caller from the head event before invoking restore (see the field’s doc).

Source

pub fn try_to_bytes(&self) -> Result<Vec<u8>, SnapshotError>

Fallible serialization to bytes.

Returns SnapshotError::ExceedsWireFormat { .. } when state.len() or bindings_bytes.len() exceeds the u32::MAX wire-format cap (4 GiB). The wire format encodes each as a u32 length prefix; a payload that overflows would be permanently un-decodable.

to_bytes is on the migration / snapshot-send path, where a panic on u32 length-prefix overflow would crash the dispatch task without releasing locks. state is opaque caller-supplied bytes (compute orchestrator, FFI clients) and bindings_bytes is opaque externally-controlled migration metadata, so the >4 GiB case is reachable from outside-controlled inputs. Production callers should use try_to_bytes and surface the error; the legacy to_bytes wrapper is kept for well-known-bounded test callers.

Source

pub fn from_bytes(data: &[u8]) -> Option<StateSnapshot>

Deserialize from bytes.

Accepts only v2 (post-audit-#102 wire bump) layouts. v1 and pre-magic v0 bytes are rejected — see the project release notes for the migration cliff. The audit-#102 bump changed IDENTITY_ENVELOPE_SIZE (208 → 209), shifting every offset in the v1 trailer; a v1 reader cannot consume v2 bytes correctly, and conversely. v1 bytes reach this function with the current magic but a stale version byte; the version-byte check inside from_bytes_v2 rejects them.

head_payload is runtime-only and always defaults to empty after deserialize; callers must populate it from the head event before passing the snapshot to super::log::EntityLog::from_snapshot.

Source

pub fn age_secs(&self) -> u64

Age of this snapshot in seconds.

Trait Implementations§

Source§

impl Clone for StateSnapshot

Source§

fn clone(&self) -> StateSnapshot

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for StateSnapshot

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DynClone for T
where T: Clone,

Source§

fn __clone_box(&self, _: Private) -> *mut ()

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more