Transaction and block lifecycle
End-to-end view of how a Quantum transaction goes from a user's CLI invocation to a finalized block.
End-to-end diagram
flowchart TD
subgraph TX["Transaction Submission"]
A[User TX via JSON-RPC] --> B[TxPool Validation]
B -->|Valid| C[Mempool]
B -->|Invalid| D[Rejected]
end
subgraph PROPOSE["Block Proposal — Leader Path"]
C --> E[Simplex Leader Election]
E --> F[Leader selected]
F --> G[Canonicalize parent block]
G --> H[Build block payload from mempool]
H --> I[Verify payload locally]
I --> L[Return block to Simplex]
end
subgraph VERIFY["Block Verification — Validator Path"]
L --> M[Simplex broadcasts block]
M --> N[Each validator receives block]
N --> O[Decode block payload]
O --> P[Canonicalize parent block]
P --> Q[Verify payload against execution layer]
Q -->|Valid/Accepted| R[Block accepted]
Q -->|Still Syncing / Invalid| S[Reject]
end
subgraph FINALIZE["Notarization & Finalization"]
R --> T[Simplex Notarization\n≥ 2f+1 votes]
T --> V[Apply forkchoice update\nwith retry]
T --> W[Simplex Finalization]
W --> ZA[Apply forkchoice update\nwith finalized hash]
ZA --> AA[Block Finalized ✓]
end
Submission and pool validation
When a transaction arrives via JSON-RPC:
- Type check. The transaction must be EIP-2718 type
0x7A. - Scheme check. Primary scheme must be on the CryptoSwitchboard's approved-primary list. Cosigner scheme, if present, must be valid.
- Bootstrap shape check. If the transaction is a
bootstrapKey()call,init_primary_pubkeymust be present andinit_cosigner_pubkeymust be absent. If it is not a bootstrap call,init_primary_pubkeymust be absent. - KeyVault read. For non-bootstrap transactions, the sender's KeyVault is loaded and the referenced
key_idmust exist. - Signature pre-check. Primary signature is verified against the stored primary key. Cosigner signature is verified if the KeyEntry has a cosigner.
- Nonce check. If
nonce_key == 0, the standard account nonce; otherwise NonceManager. - Fee payer check. If
fee_payeris present, the fee-payer KeyVault is loaded and the fee-payer composite signature is verified over the sametx_intent_hashwith theQuantumFeePayerdomain separator.
Only transactions that pass all of this enter the mempool.
Proposal — leader path
- Simplex elects a leader. Only the leader builds.
- The leader is invoked with the current consensus context (height, view, parent).
- The leader canonicalizes the parent — the execution layer confirms it has the parent block as head before proceeding.
- The block payload is assembled by pulling transactions from the mempool subject to gas and pool ordering.
- The freshly built block is verified locally against the execution layer.
- The block is stored and returned to Simplex for dissemination.
Verification — validator path
- Simplex broadcasts the proposed block.
- Each validator receives the block and begins verification.
- The block payload is decoded.
- Canonicalize the parent (fire-and-forget — the validator does not block on the execution layer).
- The payload is verified against the execution layer.
Valid/Accepted→ block accepted, vote submitted to Simplex.Syncing→ bounded retry; if still syncing after retries, reject.Invalid/Error→ reject.
Notarization
Once ≥ 2f+1 validators vote yes, Simplex notarizes the block. A notarization event is fired at the execution layer — this is fire-and-forget and does not block Simplex's voter loop. The execution layer applies the forkchoice update with retry logic.
Finalization
Simplex finalizes (one more round after notarization). The execution layer applies the forkchoice update with the finalized block hash.
Payload state machine
stateDiagram-v2
[*] --> InMempool: TX submitted via RPC
InMempool --> Building: Leader elected
Building --> Built: Block assembled from mempool
Built --> ImportedToEL: Payload verified by execution layer
ImportedToEL --> HeadSet: Forkchoice updated to new head
HeadSet --> Broadcast: Block disseminated to validators
Broadcast --> Verified: Validators verify payload
Verified --> Notarized: ≥ 2f+1 votes collected
Notarized --> HeadCanonicalized: Execution layer canonicalizes head
HeadCanonicalized --> Finalized: Simplex finalization
Finalized --> [*]: Forkchoice updated with finalized hash
Built --> Rejected: Payload invalid or error
Broadcast --> Rejected: Validator verify fails after bounded retry
Rejected --> [*]
Account bootstrap and key-management lifecycle
KeyVault lifecycle operations are processed inside the same transaction lifecycle, with one extra step: transient lifecycle-intent context is written during pre-EVM validation. The KeyVault precompile reads this transient slot during EVM execution and rejects mutations whose intent doesn't match — this is what prevents contract-mediated lifecycle bypasses.
The transition for an addKey call:
- Pre-EVM validation verifies the outer transaction is signed by a FullAccess key, and writes
(sender, target_key_id, lifecycle_kind=AddKey)into the precompile's transient storage. - EVM execution invokes the
addKey(...)ABI call on0x1000. - The KeyVault precompile reads the transient slot, compares it against the call arguments, and only proceeds if they match.
- The new KeyEntry is written; storage is updated.
This is the lifecycle intent binding invariant — nested contract calls cannot trigger mutations.
Failure modes and retries
- EL returns
Syncingduring verification — bounded retry. If still syncing after retries, the validator rejects the block. The leader, on its own machine, should rarely hit this. - EL returns
InvalidorError— immediate reject. - Forkchoice update fails — the execution layer retries with exponential backoff. Notarization-driven and finalization-driven forkchoice updates are fire-and-forget, so the Simplex voter loop is never blocked.
- Marshal backfill kicks in when a validator falls behind — gaps are recovered by fetching missing blocks from peers via the Coding variant (
N ≥ 4).
Related
- Consensus (Simplex + Falcon) — consensus-side detail
- Native precompiles — how the precompiles read transient storage
- Indexer architecture — chain-deviations checklist for indexers