pub trait SequentialChainState { }Expand description
Assumption (Sequential instance state). The transitions of one consensus instance are
mutually exclusive and each runs to completion: no two of them interleave their reads and
writes of the same ChainManager.
Exclusivity has two halves, and only the second is in this repository’s control.
Within a worker process, linera_core::worker::WorkerState reaches a chain only through a
per-chain tokio::sync::RwLock<ChainWorkerState> — chain_write for transitions, chain_read
for queries. A transition holds the write side for its whole duration, and the manager is
!Sync-by-construction behind the guard.
Across the processes of one validator, each chain belongs to exactly one worker, because
shard assignment is static: ValidatorInternalNetworkPreConfig::get_shard_id in linera-rpc
is a pure function of the validator’s public key, the chain id and shards.len(), with no
leases and no handoff. Senders route by that function, so a worker is only ever asked for the
chains of its own shard.
That partition excludes reads as much as writes. All shards share one backing store, so nothing physically prevents a worker from reading another shard’s keys; what makes it never happen is that a worker is never asked to. A chain’s mutable state is therefore touched in neither direction by any process but its owner.
Chains do still share storage, but only published artifacts: blobs, which are content
addressed, and events, which a reader reaches through OracleResponse::Event. Both are
immutable once written and belong to no chain’s view. So the boundary is not “no shared
storage” but “no reading another chain’s state”: a cross-chain dependency is either delivered
as a message or read from one of those two stores, never observed in the producing chain’s
ChainManager or inboxes.
IncomingBundlesMatchTheLocalInbox is the form that takes for the message case.
The specification relies on the composition whenever it reasons about “the state immediately
before” a vote — for instance in UnlockingJustification, where the guard evaluated by
ChainManager::check_proposed_block must still describe the state when
ChainManager::create_vote runs a few statements later.
Residual obligation. The second half holds only while shards.len() is stable and worker
processes do not overlap. Changing the shard count re-partitions every chain, and a rolling
restart that runs a replacement alongside its predecessor puts two processes on the same
chain; in both cases they compute the same owner and write the same shared keys. Nothing in
the code detects either, so this is a deployment obligation, not an enforced invariant.