Canonical Ethereum history is the PCASH block producer. Each Ethereum block maps to one PCASH block through a deterministic ordered function, and an Ethereum reorg causes restore-and-replay rather than a PCASH fork-choice vote.
For youRead this when you want to know how nodes agree or what happens when Ethereum reorganizes.
Before you startNo node-operations knowledge required.
No party proposes or signs a PCASH block. PCASH block N corresponds to the Ethereum height fixed by the configured genesis anchor plus N. Nodes read that canonical Ethereum block, process relevant bytes in its exact order, compute issuance and normalized logs, and commit the resulting PCASH delta and roots.
The previous PCASH state is the other input. Each successful transaction changes the overlay seen by the next, so two transactions that compete for the same nullifier are resolved by Ethereum order: the first changes state; the later one is evaluated against that changed state and is included as a failed transaction.
- Ethereum blocks
- 1 canonical sequence
- PCASH proposers
- 0
- PCASH blocks per L1 block
- 1
- PCASH fork choice
- Inherited from Ethereum
The inbox selects bytes; it does not execute them
An Ethereum transaction addressed to the configured inbox may carry one candidate batch. With no blob hashes, its complete calldata is the candidate. With blob hashes, calldata must be empty and one to six authenticated blobs must decode canonically into the candidate. Mixed carriers are rejected.
The inbox address is a routing tag, not deployed PCASH code. Ethereum orders the transaction and makes its bytes available. PCASH nodes parse and verify those bytes under PCASH rules.
View the source behind this explanationnode/crates/l1/src/types.rs:59
Order is Ethereum order all the way down
- 01Ethereum blockCanonical chain order
- 02Transaction indexPosition inside the block
- 03Batch itemPosition inside the carrier
The ordering key is simple: Ethereum block, transaction index, then item position inside the candidate batch. A node does not reorder transactions by fee, arrival time, or preference. The same input history therefore produces the same sequence of checks, the same outcomes, and the same state root.
A failing transaction is still a deterministic result. If its proof fails, its recent anchor is stale, or one of its nullifiers has already been used, the node records the first failing check as that transaction's outcome, changes no state, and continues. Transactions around it keep their exact relative order. The block commits every outcome alongside the state root, so two nodes that disagree about why one transaction failed disagree about the block itself.
View the source behind this explanationnode/crates/derive/src/engine.rs:864
Failure is isolated at the smallest knowable scope
Not a transaction; exclude the item and continue with the next
Include it with a committed failure outcome; state unchanged
Ignore this carrier's whole candidate batch
Halt and retry; never invent a verdict
An Inbox item becomes a PCASH transaction only if its bytes decode completely as a transaction type active at that Ethereum timestamp and its envelope names this network's chain ID. Anything else—empty bytes, a truncated payload, trailing bytes, an unknown type, an oversized declared length, or another network's chain ID—is a non-admitted item. It produces no transaction, no outcome, and no transaction ID, only a gap in the batch's item numbering and a manifest row recording its position, its bytes' hash, and why it was excluded. Admission needs no state, signature, or verifier, so every node decides it identically before evaluating anything else.
Transaction failure means the node admitted an item and one deterministic check, in the fixed order for that transaction type, returned false: a stale recent anchor, an expired deadline, a spent nullifier, a rejected proof. The transaction is included with that check's outcome code and changes no state. Its exact bytes stay in canonical PCASH history, so a wallet or explorer can later explain the failure without refetching Ethereum blob data that may have expired.
Batch failure means the candidate framing itself cannot identify a trustworthy item list. An unknown batch version, a declared length that runs past the end, trailing bytes after the final item, and a carrier that exceeds the batch byte or item-count bounds all land here, and the whole candidate batch is ignored rather than partially framed. Node failure means the required canonical input or evaluation machinery is unavailable.
That last case must not be converted into any of the others. A missing authenticated blob or crashed verifier produces no protocol verdict. Treating “I could not check” as “failed” could make one node advance to a state its peers did not derive.
View the source behind this explanationnode/crates/derive/src/engine.rs:1258
Exact bytes and exact inclusions have different names
Before inclusion, the only handle for a transaction is its contentHash, the Keccak hash of its exact bytes. That is what a wallet saves, what a mempool stores, and what a relayer selects. It names bytes, not a payment: reproving the same intent against a fresher anchor produces different bytes and a different content hash.
The same bytes can be posted more than once, so each canonical inclusion also gets a transactionId, derived from the L1 block hash, L1 transaction index, batch item index, and content hash. Every block commits the content hash and outcome of each admitted transaction in its transaction-results root; the transaction ID is derived from those committed fields rather than committed again. An Ethereum reorg that moves the bytes to a different block therefore produces a different transaction ID, which is the intended behavior: the old inclusion no longer exists.
View the source behind this explanationsdk/src/protocol/block.ts:205
A recent block hash anchors private state
Transactions name one public recentPcashBlockHash. The node requires it to be canonical and no more than one hour old at the candidate block's Ethereum timestamp. Inside the proof, private typed openings recover whichever output, credit, account-state, history, or log roots the action needs.
That single anchor avoids publishing a collection of state roots. It also makes reorg behavior exact: if Ethereum removes the named block, the hash stops being canonical immediately, and a transaction that still names it is included as a failure until it is reproved. A later transaction deadline cannot keep an old or orphaned anchor alive.
View the source behind this explanationnode/crates/state/src/inner.rs:295
Reorg recovery is restore, then replay
ancestor27
orphan28
orphan
recovered state = state from a fresh derivation of the new canonical history
When the next Ethereum parent hash does not match the recorded tip, the node walks backward until recorded and canonical block hashes agree. That block is the common ancestor. The node restores its state there and derives forward over the replacement Ethereum blocks with the ordinary derivation function.
Recovery is not a second consensus rule and does not invert individual transactions. The required result is the state a fresh node would obtain by deriving the new canonical history from genesis. State roots, PCASH block hashes, transaction outcomes and identifiers, history accumulators, issuance, recent anchors, and successful effects all follow from that replay.
Finality bounds storage and recovery, not transaction validity. A reorg deeper than the retained finalized savepoint cannot be reconstructed safely, so the node halts rather than guessing.