Skip to main content

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:

  1. Type check. The transaction must be EIP-2718 type 0x7A.
  2. Scheme check. Primary scheme must be on the CryptoSwitchboard's approved-primary list. Cosigner scheme, if present, must be valid.
  3. Bootstrap shape check. If the transaction is a bootstrapKey() call, init_primary_pubkey must be present and init_cosigner_pubkey must be absent. If it is not a bootstrap call, init_primary_pubkey must be absent.
  4. KeyVault read. For non-bootstrap transactions, the sender's KeyVault is loaded and the referenced key_id must exist.
  5. Signature pre-check. Primary signature is verified against the stored primary key. Cosigner signature is verified if the KeyEntry has a cosigner.
  6. Nonce check. If nonce_key == 0, the standard account nonce; otherwise NonceManager.
  7. Fee payer check. If fee_payer is present, the fee-payer KeyVault is loaded and the fee-payer composite signature is verified over the same tx_intent_hash with the QuantumFeePayer domain separator.

Only transactions that pass all of this enter the mempool.

Proposal — leader path

  1. Simplex elects a leader. Only the leader builds.
  2. The leader is invoked with the current consensus context (height, view, parent).
  3. The leader canonicalizes the parent — the execution layer confirms it has the parent block as head before proceeding.
  4. The block payload is assembled by pulling transactions from the mempool subject to gas and pool ordering.
  5. The freshly built block is verified locally against the execution layer.
  6. The block is stored and returned to Simplex for dissemination.

Verification — validator path

  1. Simplex broadcasts the proposed block.
  2. Each validator receives the block and begins verification.
  3. The block payload is decoded.
  4. Canonicalize the parent (fire-and-forget — the validator does not block on the execution layer).
  5. 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:

  1. 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.
  2. EVM execution invokes the addKey(...) ABI call on 0x1000.
  3. The KeyVault precompile reads the transient slot, compares it against the call arguments, and only proceeds if they match.
  4. 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 Syncing during 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 Invalid or Error — 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).