diff --git a/specs/assets/batch-deriv-chain.svg b/specs/assets/batch-deriv-chain.svg new file mode 100644 index 0000000000000..3a2f7c3b6a249 --- /dev/null +++ b/specs/assets/batch-deriv-chain.svg @@ -0,0 +1,3 @@ + + +
100
100
102
102
101
101
3
3
4
4
5
5
6
6
7
7
8
8
Compressed & encoded batch data
Compressed & encoded batch data
A0
A0
B0
B0
B1
B1
9
9
10
10
11
11
A1
A1
B2
B2
L1 Transactions,
~128 KB each
L1 Transactions,...
Channels,
with timeout
Channels,...
Batches,
1 batch = 1 L2 block tx list
Batches,...
L2 Blocks
(a.k.a. execution payloads)
L2 Blocks...
Channel A, Frame 0
Channel A, Frame 0
Channel A, Frame 1
Channel A, Frame 1
Channel B,
Frame 0
Channel B,...
Channel B, Frame 1
Channel B, Frame 1
Channel B, Frame 2
Channel B, Frame 2
Channel C, etc...
Channel C, etc...
L1 Blocks,
These may not be as
frequent/consistent
as L2 blocks.
L1 Blocks,...
Actual inclusion on L1:
channels are valid
within a timeout
Actual inclusion on L1:...
Channel B was seen first,
and will be decoded into batches first.
Channel B was seen first,...
Batches can be buffered
for up to a full sequencing window
worth of L1 blocks
to get the L2 ordering back.
Batches can be buffered...
Time
Time
older L2 data
older L2 data
B1
B1
B0
B0
A1
A1
B2
B2
A0
A0
100-0
100-0
100-1
100-1
100-2
100-2
100-3
100-3
100-2
100-2
101-0
101-0
99-5
99-5
99-4
99-4
99-3
99-3
99-2
99-2
99
99
Each L2 block has a tx with info
about the "origin" L1 block
Each L2 block has a tx with info...
The "sequence number"
helps differentiate between
L2 blocks with the same origin.
The "sequence number"...
deposit
deposit
deposit
deposit
Deposits are L1 log events,
parsed from EVM receipts
Deposits are L1 log events,...
deposit
deposit
deposit
deposit
Deposits get included
the first L2 block that
adopts the L1 origin the
deposits were made in.
Deposits get included...
Security types on L2:
- "unsafe": not submitted on L1
- "safe": is confirmed on L1
- "finalized": fully derived from finalized L1 data
Security types on L2:...
Security types on L1:
- "unsafe": very new
- "safe": decent attestation ratio
- "finalized": with FFG finality gadget
Security types on L1:...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/specs/assets/batch-deriv-pipeline.svg b/specs/assets/batch-deriv-pipeline.svg new file mode 100644 index 0000000000000..c2769ccde340a --- /dev/null +++ b/specs/assets/batch-deriv-pipeline.svg @@ -0,0 +1,3 @@ + + +
110
110
110
110
110
110
100
100
Batch Queue
Batch Queue
Engine Queue
Engine Queue
PayloadAttributes
Queue
PayloadAttributes...
Channel
Input-reader
Channel...
Channel
Bank
Channel...
L1 Traversal
L1 Traversal
100
100
110
110
L1 Deposits
L1 Deposits
130
130
110
110
110
110
110
110
100
100
100
100
130
130
130
130
130
130
130
130
130
130
130
130
1) find safe L2 block
with canonical L1 origin
1) find safe L2 block...
3) reset all stage origins,
traverse chain back
3) reset all stage orig...
4) dry-run pipeline
to heal stage buffers
4) dry-run pipeline...
5) new L1 data
can now stream into L2
5) new L1 data...
2) Stages with gaps will
reconstruct buffer data
2) Stages with gaps wi...
Numbers for illustration,
referring to L1 origin
block numbers
Numbers for illustrati...
"ResetStep":
reset stage with
distance from next
"ResetStep":...
"Step":
progress pipeline.
Start closest to L2,
visit previous stage
for more input data.
"Step":...
L1 block hash > Data TXs > Channel Frames > Batches > Ordered batches > PayloadAttributes > L2 payloads
L1 block hash > Data TXs > Channel Frames > Batches > Ordered batches > PayloadAttributes > L2 payl...
Resets
Channel
timeout
Resets...
Resets
Sequencing
window
ResetsSequenc...
L1 Retrieval
L1 Retrieval
100
100
100
100
130
130
L1 Data
L1 Data
L1 Chain
L1 Chain
L2 Exec
Engine
L2 Exec...
Reset:
Reset:
110
110
110
110
Text is not SVG - cannot display
\ No newline at end of file diff --git a/specs/derivation.md b/specs/derivation.md new file mode 100644 index 0000000000000..47083cdeeb7e7 --- /dev/null +++ b/specs/derivation.md @@ -0,0 +1,537 @@ +# L2 block derivation specification + + +[g-payload-attr]: glossary.md#payload-attributes +[g-block]: glossary.md#block +[g-exec-engine]: glossary.md#execution-engine +[g-reorg]: glossary.md#re-organization +[g-receipts]: glossary.md#receipt +[g-inception]: glossary.md#L2-chain-inception +[g-deposit-contract]: glossary.md#deposit-contract +[g-deposited]: glossary.md#deposited-transaction +[g-l1-attr-deposit]: glossary.md#l1-attributes-deposited-transaction +[g-user-deposited]: glossary.md#user-deposited-transaction +[g-deposits]: glossary.md#deposits +[g-l1-attr-predeploy]: glossary.md#l1-attributes-predeployed-contract +[g-depositing-call]: glossary.md#depositing-call +[g-depositing-transaction]: glossary.md#depositing-transaction +[g-sequencing-window]: glossary.md#sequencing-window +[g-sequencing]: glossary.md#sequencing +[g-sequencer-batch]: glossary.md#sequencer-batch + + + + +**Table of Contents** +TODO + + + +# L2 block derivation + +[Block derivation][g-derivation] is the process of building a chain of L2 [blocks][g-block] from a chain of L1 data. + +The L1 data can be thought of as a stream, and L2 rollup nodes are the ***readers***, transforming their state with the inputs. + +The L2 batcher nodes are the ***writers*** to the stream: they take L2 changes and submit the necessary data to L1. +Others can then reproduce the same L2 changes. + +Consuming the stream presents an inductive process: +given that we know the last L2 block derived from the previous [sequencing window][g-sequencing-window], as well as the +next [sequencing window][g-sequencing-window], then we can derive [payload attributes] of the next L2 blocks. + +To derive the whole L2 chain from scratch, we simply start with the L2 genesis block as the last L2 block, and the +block at height `L2_CHAIN_INCEPTION + 1` as the start of the next sequencing window. +Then we iteratively apply the derivation process, using the [derivation pipeline](#derivation-pipeline) process outlined below. + +> **TODO** specify genesis block + +## Rollup Stream format + +The stream is ***multiplexed*** and ***buffered***, to handle serve the required properties: + +- There may be different concurrent data submissions to L1 +- There may be different actors that submit the data, the system cannot rely on a single EOA nonce value. +- The fraud proof processes submitted data, which has to be bounded +- The submissions confirmed on L1 may be confirmed out of order +- The L1 may [reorganize][g-reorg], requiring the submissions to reset to a previous point +- The L2 grows, and does so in smaller intervals than L1 +- The L2 may have bursts that cannot be fully confirmed in a single piece of submitted data + +The multiplexed and buffered stream consists of: + +- L1 data transactions: any form of L1 data from which frames can be read, tagged with the L1 blockhash, number and timestamp. +- Channels: independent of each other, a bounded stream submitted by a single actor, encoding a range of L2 bock derivation inputs. +- Frames: numbered slices of channel data, which can be confirmed on L1 out of order and mixed with other channels. + +The types of data are illustrated in the diagram below. + +![](./assets/batch-deriv-chain.svg) + +### Frame format: + +```text +frame = channel_id | frame_number | frame_data_length | frame_data | is_last + +channel_id = bytes32 | uvarint # identifies the channel to add frame_data to, as well as the time (UNIX-epoch in seconds) the channel was created. +frame_number = uvarint # identifies the index of frame_data in the channel +frame_data_length = uvarint # frame_data_length == len(frame_data) +frame_data = bytes # data to add to the channel, after frame max(frame_number-1, 0) +is_last = bool # 1 byte, channel is closed if 1, stays open if 0, invalid otherwise +``` + +The channel identity uniqueness is limited both by the random `bytes32` in the ID, as well as the timestamp. +The timestamp is limited by L1 timestamps, to force-close a channel and avoid revival of the same channel ID after the `CHANNEL_TIMEOUT`. +This ensures at most a span of `CHANNEL_TIMEOUT` seconds of L1 data is required to reconstruct any channel-bank state in time. + +### L1 transaction data format: + +```text +transaction_data = version_byte | rollup_payload + +# version_byte == 0: +rollup_payload = frame ... # one or more frames, concatenated + +# 0 padding may be used to right-pad the rollup_payload. +# This padding will be interpreted as channel 0, to be ignored. + +# version_byte != 0: invalid, reserved for future use. +``` + +## Derivation Pipeline + +Derivation is abstracted as a pipeline of stages, that indicate if they either: +- Can be resumed +- Need data from the lower stage before continuing +- Error if they cannot be resumed (the pipeline should be completely reset) + +On a pipeline "step", stages are attempted in order from the last (Engine Queue) to the first (L1 Data Source): +if a stage returns `EOF` (End-Of-File, exhausted data) then the next stage is attempted, +until no `EOF` is returned by the stage anymore. + +This pipeline abstraction enables the node to iterate on the derivation process, and retry any steps that are recoverable. +E.g. a connection problem to an execution-engine will not require a long range of L1 data to be re-read. + +The pipeline consists of: +1. The L1 Data source: traverses the L1 chain one block at a time and fetches the corresponding data-inputs +2. The Channel Bank: parses data-inputs, buffering raw channel frames +3. The Channel Input Reader: reads channels one at a time, decompressing and decoding the data into batches +4. The Batch Queue: buffers and orders batches for at most a full sequencing window, and dequeues as soon as possible +5. The PayloadAttributes Queue: reads a batch and outputs payload attributes +6. The Engine Queue: buffers payload attributes, as well as full execution payloads that were received out-of-band, + to consolidate or process with the external Engine. + +Note that each of the stages in the pipeline (up to the Batch Queue) track the L1 origin that was last involved to +derive the data read from the previous stage. +The Batch Queue then uses the inclusion information to enforce timely adoption of a new L1 origin, +and thus liveness of user deposits without relying on the sequencer. + +### Recovering from L1 reorgs + +![](./assets/batch-deriv-pipeline.svg) + +The Derivation Pipeline assumes linear progression of the L1 chain. +It is also applicable for batch processing, meaning that any given point in time, the canonical L2 chain is given by +processing the whole L1 chain since the [L2 chain inception][g-inception]. + +If the L1 Chain re-orgs, the rollup node must re-derive sections of the L2 chain such that it derives the same L2 chain +that a rollup node would derive if it only followed the new L1 chain. + +A [reorg][g-reorg] can be recovered without re-deriving the full L2 chain, by resetting the pipeline as follows: +1. Reset the Engine Queue +2. Reset the Batch Queue, starting at the common L1 origin found during the Engine Queue reset. +3. Reset the stages down until the Channel Bank, wiping the contents and copying the safe origin of the Batch Queue. +4. Reset the Channel Bank +5. Reset the L1 source by wiping any buffered data, and copying the safe origin of the Channel Bank. + +When walking back on the L2 chain, care should be taken to not walk past the rollup or L1 genesis. + +After the resets are performed (possibly in smaller iterative chain traversal steps), +the regular pipeline derivation can dry-run to heal the buffer contents +(i.e. drop any outputs of a stage that lags behind the next stage closer to L2). + +#### Resetting the Engine Queue + +The engine queue has two starting points: +- The `safe` block: everything up to and including this block can be fully derived from the canonical L1 chain. +- The `unsafe` block: local data that serves the sequencer and happy-path sync, + considered to be "unsafe" while not confirmed on L1 yet. + +Starting at the previous `safe` head the execution knows of (i.e. fully derivable from L1 chain), +traverse back the L2 chain until a block with a canonical L1 origin is found. +This L2 block will be the `safe` head. + +The `unsafe` block starts as the last known tip of the L2 chain, +but is reset back to equal the `safe` block if at any point the `unsafe` block height is known but non-canonical. +An `unsafe` head with an origin beyond the `safe` origin is considered "plausible": +retaining the existing data is preferable, but it may be reorganized at a later point if consolidation with the `safe` chain fails. + + +#### Resetting the Batch Queue + +The Batch Queue is reset back by an additional `SEQUENCING_WINDOW` number of L1 blocks, +to ensure that if the sequencing window for a L2 block has changed since it was derived, +that L2 block is re-derived. + +The first L1 block of the sequencing window contains the L1 attributes for that L2 block. +The end of the sequencing window is the canonical L1 block whose number is `SEQUENCING_WINDOW` larger than the start. +The end of the window must be selected by number otherwise the sequencer would not be able to create batches. +The problem with selecting the end of the window by number is that when an L1 reorg occurs, +the blocks (and thus batches) in the window could change. +We must find the first L2 block whose complete sequencing window is unchanged in the reorg. + +[merge]: https://ethereum.org/en/eth2/merge/ + +#### Resetting the Channel Bank + +In the case of the Channel Bank the origin has to be reset further back, +by a full `CHANNEL_TIMEOUT` behind the desired L1 origin to continue the pipeline from. +The pipeline will drop the outputs from the Channel Bank stage until it has caught up to the next origins. + +This ensures the reset is performed in the same pipeline of small recoverable specs, without additional complexity. + +The rollback by a full `CHANNEL_TIMEOUT` is required to avoid previous channel frames from missing in the Channel Bank, +when it continues from the new safe L1 origin that was selected after the reorg. + +#### Reorgs Post-Merge + +Note that post-[merge], the depth of re-orgs will be bounded by the [L1 finality delay][l1-finality] +(every 2 epochs in the happy case, approximately 12 minutes). + +(\*) Post-merge, this is only possible for 12 minutes. In practice, we'll pick an already-finalized L1 block as L2 +inception point to preclude the possibility of a re-org past genesis, at the cost of a few empty blocks at the start of +the L2 chain. + + +### L1 Data Source + +The first stage starts at a L1 origin, and moves the next L1 origin on a read. +If there is no next L1 origin yet, the stage returns an `EOF` like an intermediate stage would, to indicate all input was exhausted, +and the next pipeline step may need to be triggered only after a hint of new inputs (e.g. the L1 head changed). + +After retrieving the next L1 origin, the data for the origin is retrieved from a [Data Availability Source](#data-availability-source). + +#### Data Availability Source + +A data availability source fetches a list of bytestrings corresponding to a certain L1 block hash/number combination. + +By default, ethereum calldata is supported as data-availability source. + +Transactions of the queried block are traversed, and filtered by receiver and sender: +- The transaction receiver MUST be the sequencer inbox address +- The transaction must be signed by a recognized batch submitter account + + +### ChannelBank + +#### Constants + +```text +CHANNEL_TIMEOUT = 600 # time in seconds until a channel is pruned from the channel bank +MAX_CHANNEL_BANK_SIZE = 100_000_000 # max size of the combined channels in the channel bank +FRAME_OVERHEAD = 200 # space each frame in a channel takes, even without frame contents +``` + +The channel bank processes L1 transactions and buffers the encoded frames to be read. + +Only future frames are buffered. If there is a full frame ready to read, it MUST be read first before a new L1 transaction can be processed. + +The channel bank does not handle reorgs, see the recovering-from-L1-reorgs section for information how these are handled. + +The channel bank tracks: + +- The collection of open channels, ordered in a FIFO queue. The first seen channel has priority. +- `currentL1Origin`: the current L1 block, updated every time a new block is appended to the L1 chain, before any of the data of the block is processed in the bank. + +Channel IDs are random `bytes32` value with timestamp. +In this may become a hash of the sender and a channel nonce or other similar information to separate control of channels between different submitters. + +When `channel.time + CHANNEL_TIMEOUT < currentL1Origin.timestamp` an open channel is removed, cannot be re-instantiated anymore, and cannot be read from anymore. +When a frame is missing this limits the amount of time that later frames pile up, preventing potential out-of-memory DoS and enabling a new channel to be read. + +A channel in the bank tracks: + +- its ID: random `bytes32` and timestamp that the first frame, part of the identity of the channel. +- `progress`: the frame number of the current frame to read, which may not be buffered yet. Initially 0. +- `ends_at`: the frame number of the frame that closed the channel. Initially set to a max uint64 integer value, mark there is no end. +- The existing collection of frames tagged with their respective origin information. Each entry is keyed by frame number. +- `size`: the amount of data the open channel has buffered. +- Its channel ID and index in the priority queue. + +The channel bank processes L1 data as follows: + +1. Read the version number of the L1 data, check if it is 0, the data is invalid if not. +2. Determine the `total_size` of the bank by summing the `size` of all channels in the bank. + For as long as `total_size > MAX_CHANNEL_BANK_SIZE` prune the first-seen channel (i.e. lowest `time`) and reduce `total_size` by that channel `size` . + The first-seen channel is pruned first, since this is the channel that fails to be read before the bank became this large. +3. Read the frames in a loop: +4. Parse the frame data +5. If the frame references a known `channel` (combined identity of `bytes32` and timestamp data): ignore the frame. +6. If it hits the timeout condition: `channel.time + CHANNEL_TIMEOUT < currentL1Origin.timestamp`: ignore the frame. +7. Process the frame in the corresponding channel in the bank. A new channel is created first if the channel does not yet exist. + +A channel processes a frame as follows: + +1. The frame is ignored if `progress > frame_number`: a frame with equal or lower ID was previously read, no duplicates are allowed, the first included frame is canonical. +2. The frame is ignored if `endsAt < frame_number`: a frame cannot be added after closing the channel +3. The frame is ignored if a frame with equal `frame_number` exists. +4. The channel stores the frame, keyed by `frame_number` and tagged with `currentL1Origin` +5. `endsAt` is set to `frame_number` if `is_last`, i.e. a closing frame does not close a channel before it can be read from, but does mark when it gets closed. +6. The channel `size` increases by `len(frame_data) + FRAME_OVERHEAD` to keep track of the amount of data that is buffered. + +All channels that can be read MUST be read before processing new L1 transaction data. + +A read from the channel bank is processed as: + +1. Check if the first-seen channel is timed out, or closed. +2. If yes, then the channel can be removed, and the data is returned +3. If not, the channel is incomplete and the bank is unchanged. An EOF should indicate that the bank needs the next L1 data. + +A read from a channel in the bank is processed as: +1. Read starting from frame number `0`, and continue until the next frame cannot be found. + +### Channel Input Reader + +The Channel Input Reader tracks which L1 block was involved in the last read from the stream, enabling the derivation to filter read contents by inclusion time. + +The reader can be reused for the next channel after a reader error, but it should reset before doing so. +Errors to recover this way may be expected, e.g. if a channel times out or if the submitted data cannot be decompressed or decoded. + +The reader decompresses the input with ZLIB, without dict. +The reader then decodes the input as an RLP stream of `BatchData` entries. +The RLP stream as a whole is limited to reading `MAX_RLP_BYTES_PER_CHANNEL"` to avoid reading too far, +e.g. due to a "zip-bomb" (when the decompression stream emits unexpected amounts of data). + +Decode `BatchData` entries, each entry is prefixed with a version byte. +Unknown versions are invalid just like malformatted `BatchData`, and the pipeline should move to the next frame. + +#### Batch encoding + +A batch is versioned by prefixing with a version byte: `BatchData = batch_version ++ content` +and encoded as a byte-string (including version prefix byte) in the bundle RLP list. + +``: ` = ` +- `0`: `BatchV1 = RLP([epoch, timestamp, transaction_list])` + +Batch contents: + +- `epoch` is the sequencing window epoch, i.e. the first L1 block number +- `timestamp` is the L2 timestamp of the block +- `transaction_list` is an RLP encoded list of [EIP-2718] encoded transactions. + +[EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718 + + +### Batch Queue + +TODO(josh): this changes to more eager batch-derivation, requiring less buffering of batches in the happy case. + +The Batch Queue buffers a full [sequencing window][g-sequencing-window] of data tagged with L1 origins, +and then derives payloads attributes as: + +- Of the *first* block in the window only: + - L1 block attributes: + - block number + - timestamp + - basefee + - *random* (the output of the [`RANDOM` opcode][random]) + - L1 log entries emitted for [user deposits][g-deposits], augmented with a [sourceHash](./deposits.md#). +- Of each batch in the window: + - Batches not matching filter criteria are ignored: + - `batch.epoch == sequencing_window.epoch`, i.e. for this sequencing window + - `(batch.timestamp - genesis_l2_timestamp) % block_time == 0`, i.e. timestamp is aligned + - `min_l2_timestamp <= batch.timestamp < max_l2_timestamp`, i.e. timestamp is within range + - `min_l2_timestamp = prev_l2_timestamp + l2_block_time` + - `prev_l2_timestamp` is the timestamp of the previous L2 block: the last block of the previous epoch, + or the L2 genesis block timestamp if there is no previous epoch. + - `l2_block_time` is a configurable parameter of the time between L2 blocks + - `max_l2_timestamp = max(l1_timestamp + max_sequencer_drift, min_l2_timestamp + l2_block_time)` + - `l1_timestamp` is the timestamp of the L1 block associated with the L2 block's epoch + - `max_sequencer_drift` is the most a sequencer is allowed to get ahead of L1 + - The batch is the first batch with `batch.timestamp` in this sequencing window, + i.e. one batch per L2 block number. + - The batch only contains sequenced transactions, i.e. it must NOT contain any Deposit-type transactions. + +Note that after the above filtering `min_l2_timestamp >= l1_timestamp` always holds, +i.e. a L2 block timestamp is always equal or ahead of the timestamp of the corresponding L1 origin block. + +[random]: https://eips.ethereum.org/EIPS/eip-4399 + +A sequencing window is derived into a variable number of L2 blocks, defined by a range of timestamps: + +- Starting at `min_l2_timestamp`, as defined in the batch filtering. +- Up to and including (including only if aligned with L2 block time) + `new_head_l2_timestamp = max(highest_valid_batch_timestamp, next_l1_timestamp - 1, min_l2_timestamp)` + - `highest_valid_batch_timestamp = max(batch.timestamp for batch in filtered_batches)`, + or `0` if no there are no `filtered_batches`. + `batch.timestamp` refers to the L2 block timestamp encoded in the batch. + - `next_l1_timestamp` is the timestamp of the next L1 block. + +The L2 chain is extended to `new_head_l2_timestamp` with blocks at a fixed block time (`l2_block_time`). +This means that every `l2_block_time` that has no batch is interpreted as one with no sequenced transactions. + + +### Payload Attributes Deriver + +Payload Attributes are derived by combining transactions derived from the referenced L1 origin, with transactions from the batch: + +- *[deposited transactions][g-deposited]*: two kinds: + - derived from the L1 chain: a single *[L1 attributes deposited transaction][g-l1-attr-deposit]* (always first). + - derived from [receipts][g-receipts]: zero or more *[user-deposited transactions][g-user-deposited]*. +- *[sequenced transactions][g-sequencing]*: derived from [sequencer batches][g-sequencer-batch], + zero or more regular transactions, signed by L2 users. + + +#### Reading L1 inputs + +The L1 attributes are read from the L1 block header, while deposits are read from the block's [receipts][g-receipts]. +Refer to the [**deposit contract specification**][deposit-contract-spec] for details on how deposits are encoded as log +entries. The deposited and sequenced transactions are combined when the Payload Attributes are constructed. + +[deposit-contract-spec]: deposits.md#deposit-contract + +##### Encoding the L1 Attributes Deposited Transaction + +The [L1 attributes deposited transaction][g-l1-attr-deposit] is a call that submits the L1 block attributes (listed +above) to the [L1 attributes predeployed contract][g-l1-attr-predeploy]. + +To encode the L1 attributes deposited transaction, refer to the following sections of the deposits spec: + +- [The Deposited Transaction Type](deposits.md#the-deposited-transaction-type) +- [L1 Attributes Deposited Transaction](deposits.md#l1-attributes-deposited-transaction) + +##### Encoding User-Deposited Transactions + +A [user-deposited-transactions][g-deposited] is an L2 transaction derived from a [user deposit][g-deposits] submitted on +L1 to the [deposit contract][g-deposit-contract]. Refer to the [deposit contract specification][deposit-contract-spec] +for more details. + +The user-deposited transaction is derived from the log entry emitted by the [depositing call][g-depositing-call], which +is stored in the [depositing transaction][g-depositing-transaction]'s log receipt. + +To encode user-deposited transactions, refer to the following sections of the deposits spec: + +- [The Deposited Transaction Type](deposits.md#the-deposited-transaction-type) +- [User-Deposited Transactions](deposits.md#user-deposited-transactions) + +#### Deriving all Payload Attributes of a sequencing window + +Each of the derived `PayloadAttributes` starts with a L1 Attributes transaction. +Like other derived deposits, this does not have to be batch-submitted, and exposes the required L1 information for the +process of finding the sync starting point of the L2 chain, without requiring L2 state access. + +The [User-deposited] transactions are all put in the first of the derived `PayloadAttributes`, +inserted after the L1 Attributes transaction, before any [sequenced][g-sequencing] transactions. + +#### Building individual Payload Attributes + +[payload attributes]: #building-individual-payload-attributes + +From the timestamped transaction lists derived from the sequencing window, the rollup node constructs [payload +attributes][g-payload-attr] as an [expanded version][expanded-payload] of the [`PayloadAttributesV1`] object, which +includes the additional `transactions` and `noTxPool` fields. + +Each of the timestamped transaction lists translates to a `PayloadAttributesV1` as follows: + +- `timestamp` is set to the timestamp of the transaction list. +- `random` is set to the *random* `execution_payload.prev_randao` L1 block attribute +- `suggestedFeeRecipient` is set to an address determined by the system +- `transactions` is the array of the derived transactions: deposited transactions and sequenced transactions. + All encoded with [EIP-2718]. +- `noTxPool` is set to `true`, to use the exact above `transactions` list when constructing the block. + +[expanded-payload]: exec-engine.md#extended-payloadattributesv1 +[`PayloadAttributesV1`]: https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#payloadattributesv1 + + +### Engine Queue + +The Engine Queue buffers payload-attributes derived in the previous stage, +and attempts to consolidate or process them to a linked [Execution Engine][g-exec-engine]. + +If the "unsafe head" (speculative tip of L2 chain) is ahead of the "safe head" (derived tip of L2 chain), +then consolidation is attempted: if the payload attributes match, the previously "unsafe" block can be regarded as "safe". + +However, if consolidation fails, the engine "unsafe" head is set back to the "safe" head. + +#### Consolidating payload attributes + +The "unsafe" block in question is retrieved from the engine, and the following attributes are checked to be exactly equal: +- block `parent_hash` +- block `timestamp` +- block `randao` +- block `transactions_list` (first length, then equality of each of the encoded transactions) + +#### Processing payload attributes + +Let + +- `refL2` be the (hash of) the current L2 chain head +- `refL1` be the (hash of) the L1 block from which `refL2` was derived +- `finalizedRef` be the (hash of) the L2 block that can be fully derived from finalized L1 input data. +- `payloadAttributes` be some previously derived [payload attributes] for the L1 block with number `l1Number(refL1) + 1` + +Then we can apply the following pseudocode logic to update the state of both the rollup driver and execution engine: + +```javascript +// request a new execution payload +forkChoiceState = { + headBlockHash: refL2, + safeBlockHash: refL2, + finalizedBlockHash: finalizedRef, +} +[status, payloadID, rpcErr] = engine_forkchoiceUpdatedV1(forkChoiceState, payloadAttributes) +if (rpcErr != null) soft_error() +if (status != "VALID") payload_error() + +// retrieve and execute the execution payload +[executionPayload, rpcErr] = engine_executePayloadV1(payloadID) +if (rpcErr != null) soft_error() + +[status, rpcErr] = engine_executePayloadV1(executionPayload) +if (rpcErr != null) soft_error() +if (status != "VALID") payload_error() + +refL2 = l2Hash(executionPayload) + +// update head to new refL2 +forkChoiceState = { + headBlockHash: refL2, + safeBlockHash: refL2, + finalizedBlockHash: finalizedRef, +} +[status, payloadID, rpcErr] = engine_forkchoiceUpdatedV1(refL2, null) +if (rpcErr != null) soft_error() +if (status != "SUCCESS") payload_error() +``` + +Error handling: +- A `payload_error()` means the inputs were wrong, and should thus be dropped from the queue, and not reattempted. +- A `soft_error()` means that the interaction failed by chance, and should be reattempted. +- If the function completes without error, the attributes were applied successfully, + and can be dropped from the queue while the tracked "safe head" is updated. + +TODO: `finalizedRef` is not being changed yet, but can be set to point to a L2 block fully derived from data up to a finalized L1 block. + +The following JSON-RPC methods are part of the [execution engine API][exec-engine]: + +[exec-engine]: exec-engine.md + +- [`engine_forkchoiceUpdatedV1`] — updates the forkchoice (i.e. the chain head) to `headBlockHash` if different, and + instructs the engine to start building an execution payload given payload attributes the second argument isn't `null` +- [`engine_getPayloadV1`] — retrieves a previously requested execution payload +- [`engine_executePayloadV1`] — executes an execution payload to create a block + +[`engine_forkchoiceUpdatedV1`]: exec-engine.md#engine_forkchoiceUpdatedV1 +[`engine_getPayloadV1`]: exec-engine.md#engine_executePayloadV1 +[`engine_executePayloadV1`]: exec-engine.md#engine_executePayloadV1 + +The execution payload is an object of type [`ExecutionPayloadV1`]. + +[`ExecutionPayloadV1`]: https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#executionpayloadv1 + +Within the `forkChoiceState` object, the properties have the following meaning: + +- `headBlockHash`: block hash of the last block of the L2 chain, according to the rollup driver. +- `safeBlockHash`: same as `headBlockHash`. +- `finalizedBlockHash`: the hash of the L2 block that can be fully derived from finalized L1 data, making it impossible to derive anything else. diff --git a/specs/rollup-node.md b/specs/rollup-node.md index 99eee4cc91090..b3d3bf15557ce 100644 --- a/specs/rollup-node.md +++ b/specs/rollup-node.md @@ -8,34 +8,10 @@ [g-exec-engine]: glossary.md#execution-engine [g-reorg]: glossary.md#re-organization [g-rollup-driver]: glossary.md#rollup-driver -[g-inception]: glossary.md#L2-chain-inception [g-receipts]: glossary.md#receipt -[g-deposit-contract]: glossary.md#deposit-contract -[g-deposits]: glossary.md#deposits -[g-deposited]: glossary.md#deposited-transaction -[g-l1-attr-deposit]: glossary.md#l1-attributes-deposited-transaction -[g-user-deposited]: glossary.md#user-deposited-transaction -[g-l1-attr-predeploy]: glossary.md#l1-attributes-predeployed-contract -[g-depositing-call]: glossary.md#depositing-call -[g-depositing-transaction]: glossary.md#depositing-transaction -[g-mpt]: glossary.md#merkle-patricia-trie -[g-sequencing-window]: glossary.md#sequencing-window -[g-sequencing]: glossary.md#sequencing -[g-sequencer-batch]: glossary.md#sequencer-batch The [rollup node][g-rollup-node] is the component responsible for [deriving the L2 chain][g-derivation] from L1 blocks -(and their associated [receipts][g-receipts]). This process happens in three steps: - -1. Select a [sequencing window][g-sequencing-window] from the L1 chain, on top of the last L2 block: - a list of blocks, with transactions and associated receipts. -2. Read L1 information, deposits, and sequencing batches in order to generate [payload attributes][g-payload-attr] - (essentially [a block without output properties][g-block]). -3. Pass the payload attributes to the [execution engine][g-exec-engine], so that the L2 block (including [output block - properties][g-block]) may be computed. - -While this process is conceptually a pure function from the L1 chain to the L2 chain, it is in practice incremental. The -L2 chain is extended whenever new L1 blocks are added to the L1 chain. Similarly, the L2 chain re-organizes whenever the -L1 chain [re-organizes][g-reorg]. +(and their associated [receipts][g-receipts]). The part of the rollup node that derives the L2 chain is called the [rollup driver][g-rollup-driver]. This document is currently only concerned with the specification of the rollup driver. @@ -43,314 +19,34 @@ currently only concerned with the specification of the rollup driver. **Table of Contents** - -- [Rollup Node Specification](#rollup-node-specification) -- [L2 Chain Derivation](#l2-chain-derivation) - - [From L1 Sequencing Window to L2 Payload Attributes](#from-l1-sequencing-window-to-l2-payload-attributes) - - [Reading L1 inputs](#reading-l1-inputs) - - [Encoding the L1 Attributes Deposited Transaction](#encoding-the-l1-attributes-deposited-transaction) - - [Encoding User-Deposited Transactions](#encoding-user-deposited-transactions) - - [Deriving all Payload Attributes of a sequencing window](#deriving-all-payload-attributes-of-a-sequencing-window) - - [Building individual Payload Attributes](#building-individual-payload-attributes) - - [From Payload Attributes to L2 Block](#from-payload-attributes-to-l2-block) - - [Inductive Derivation Step](#inductive-derivation-step) - - [Engine API Error Handling](#engine-api-error-handling) - - [Finalization Guarantees](#finalization-guarantees) - - [Whole L2 Chain Derivation](#whole-l2-chain-derivation) - - [L2 Output RPC method](#l2-output-rpc-method) - - [Output Method API](#output-method-api) -- [Handling L1 Re-Orgs](#handling-l1-re-orgs) +TODO -# L2 Chain Derivation - -[l2-chain-derivation]: #l2-chain-derivation - -This section specifies how the [rollup driver][g-rollup-driver] derives a sequence of L2 blocks per sequencing window. - -Every L2 block carries transactions of two categories: - -- *[deposited transactions][g-deposited]*: two kinds: - - derived from the L1 chain: a single *[L1 attributes deposited transaction][g-l1-attr-deposit]* (always first). - - derived from [receipts][g-receipts]: zero or more *[user-deposited transactions][g-user-deposited]*. -- *[sequenced transactions][g-sequencing]*: derived from [sequencer batches][g-sequencer-batch], - zero or more regular transactions, signed by L2 users. - ------------------------------------------------------------------------------------------------------------------------- - -## From L1 Sequencing Window to L2 Payload Attributes - -A [sequencing window][g-sequencing-window] is a fixed number consecutive L1 blocks that a derivation step takes as -input. The window is identified by an `epoch`, equal to the block number of the first block in the window. - -The derivation of the L2 chain from the L1 chain happens in steps. -Each step adds a variable number of L2 blocks to the L2 chain, derived from the sequencing window for the given epoch. -For epoch `N`, the sequencing window comprises L1 blocks `[N, N + SEQUENCING_WINDOW_SIZE)`. -Note that the sequencing windows overlap. - -### Reading L1 inputs - -The rollup reads the following data from the [sequencing window][g-sequencing-window]: - -- Of the *first* block in the window only: - - L1 block attributes: - - block number - - timestamp - - basefee - - *random* (the output of the [`RANDOM` opcode][random]) - - L1 log entries emitted for [user deposits][g-deposits], augmented with a [sourceHash](./deposits.md#). -- Of each block in the window: - - Sequencer batches, derived from the transactions: - - The transaction receiver is the sequencer inbox address - - The transaction must be signed by a recognized sequencer account - - The calldata may contain a bundle of batches. *(calldata will be substituted with blob data in the future.)* - - Batches not matching filter criteria are ignored: - - `batch.epoch == sequencing_window.epoch`, i.e. for this sequencing window - - `(batch.timestamp - genesis_l2_timestamp) % block_time == 0`, i.e. timestamp is aligned - - `min_l2_timestamp <= batch.timestamp < max_l2_timestamp`, i.e. timestamp is within range - - `min_l2_timestamp = prev_l2_timestamp + l2_block_time` - - `prev_l2_timestamp` is the timestamp of the previous L2 block: the last block of the previous epoch, - or the L2 genesis block timestamp if there is no previous epoch. - - `l2_block_time` is a configurable parameter of the time between L2 blocks - - `max_l2_timestamp = max(l1_timestamp + max_sequencer_drift, min_l2_timestamp + l2_block_time)` - - `l1_timestamp` is the timestamp of the L1 block associated with the L2 block's epoch - - `max_sequencer_drift` is the most a sequencer is allowed to get ahead of L1 - - The batch is the first batch with `batch.timestamp` in this sequencing window, - i.e. one batch per L2 block number. - - The batch only contains sequenced transactions, i.e. it must NOT contain any Deposit-type transactions. - -Note that after the above filtering `min_l2_timestamp >= l1_timestamp` always holds, -i.e. a L2 block timestamp is always equal or ahead of the timestamp of the corresponding L1 origin block. - -[random]: https://eips.ethereum.org/EIPS/eip-4399 - -A bundle of batches is versioned by prefixing with a bundle version byte: `bundle = bundle_version ++ bundle_data`. - -Bundle versions: - -- `0`: `bundle_data = RLP([batch_0, batch_1, ..., batch_N])` -- `1`: `bundle_data = compress(RLP([batch_0, batch_1, ..., batch_N]))` (compression algorithm TBD) - -A batch is also versioned by prefixing with a version byte: `batch = batch_version ++ batch_data` -and encoded as a byte-string (including version prefix byte) in the bundle RLP list. - -Batch versions: - -- `0`: `batch_data = RLP([epoch, timestamp, transaction_list])`, where each - -Batch contents: - -- `epoch` is the sequencing window epoch, i.e. the first L1 block number -- `timestamp` is the L2 timestamp of the block -- `transaction_list` is an RLP encoded list of [EIP-2718] encoded transactions. - -[EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718 - -The L1 attributes are read from the L1 block header, while deposits are read from the block's [receipts][g-receipts]. -Refer to the [**deposit contract specification**][deposit-contract-spec] for details on how deposits are encoded as log -entries. The deposited and sequenced transactions are combined when the Payload Attributes are constructed. - -[deposit-contract-spec]: deposits.md#deposit-contract - -### Encoding the L1 Attributes Deposited Transaction - -The [L1 attributes deposited transaction][g-l1-attr-deposit] is a call that submits the L1 block attributes (listed -above) to the [L1 attributes predeployed contract][g-l1-attr-predeploy]. - -To encode the L1 attributes deposited transaction, refer to the following sections of the deposits spec: - -- [The Deposited Transaction Type](deposits.md#the-deposited-transaction-type) -- [L1 Attributes Deposited Transaction](deposits.md#l1-attributes-deposited-transaction) - -### Encoding User-Deposited Transactions - -A [user-deposited-transactions][g-deposited] is an L2 transaction derived from a [user deposit][g-deposits] submitted on -L1 to the [deposit contract][g-deposit-contract]. Refer to the [deposit contract specification][deposit-contract-spec] -for more details. - -The user-deposited transaction is derived from the log entry emitted by the [depositing call][g-depositing-call], which -is stored in the [depositing transaction][g-depositing-transaction]'s log receipt. - -To encode user-deposited transactions, refer to the following sections of the deposits spec: - -- [The Deposited Transaction Type](deposits.md#the-deposited-transaction-type) -- [User-Deposited Transactions](deposits.md#user-deposited-transactions) - -### Deriving all Payload Attributes of a sequencing window - -A sequencing window is derived into a variable number of L2 blocks, defined by a range of timestamps: - -- Starting at `min_l2_timestamp`, as defined in the batch filtering. -- Up to and including (including only if aligned with L2 block time) - `new_head_l2_timestamp = max(highest_valid_batch_timestamp, next_l1_timestamp - 1, min_l2_timestamp)` - - `highest_valid_batch_timestamp = max(batch.timestamp for batch in filtered_batches)`, - or `0` if no there are no `filtered_batches`. - `batch.timestamp` refers to the L2 block timestamp encoded in the batch. - - `next_l1_timestamp` is the timestamp of the next L1 block. - -The L2 chain is extended to `new_head_l2_timestamp` with blocks at a fixed block time (`l2_block_time`). -This means that every `l2_block_time` that has no batch is interpreted as one with no sequenced transactions. - -Each of the derived `PayloadAttributes` starts with a L1 Attributes transaction. -Like other derived deposits, this does not have to be batch-submitted, and exposes the required L1 information for the -process of finding the sync starting point of the L2 chain, without requiring L2 state access. - -The [User-deposited] transactions are all put in the first of the derived `PayloadAttributes`, -inserted after the L1 Attributes transaction, before any [sequenced][g-sequencing] transactions. - -#### Building individual Payload Attributes - -[payload attributes]: #building-individual-payload-attributes - -From the timestamped transaction lists derived from the sequencing window, the rollup node constructs [payload -attributes][g-payload-attr] as an [expanded version][expanded-payload] of the [`PayloadAttributesV1`] object, which -includes the additional `transactions` and `noTxPool` fields. - -Each of the timestamped transaction lists translates to a `PayloadAttributesV1` as follows: - -- `timestamp` is set to the timestamp of the transaction list. -- `random` is set to the *random* `execution_payload.prev_randao` L1 block attribute -- `suggestedFeeRecipient` is set to an address determined by the system -- `transactions` is the array of the derived transactions: deposited transactions and sequenced transactions. - All encoded with [EIP-2718]. -- `noTxPool` is set to `true`, to use the exact above `transactions` list when constructing the block. +## Driver -[expanded-payload]: exec-engine.md#extended-payloadattributesv1 -[`PayloadAttributesV1`]: https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#payloadattributesv1 +The task of the [driver][g-rollup-driver] in the [rollup node][g-rollup-node] +is to manage the [derivation][g-derivation] process: +- Keep track of L1 head block +- Keep track of the L2 chain sync progress +- Iterate over the derivation steps as new inputs become available ------------------------------------------------------------------------------------------------------------------------- +### Derivation -## From Payload Attributes to L2 Block +This process happens in three steps: -Once the [payload attributes] for a given L1 block `B` have been built, and if we have already derived an L2 block from -`B`'s parent block, then we can use the payload attributes to derive a new L2 block. - -### Inductive Derivation Step - -Let - -- `refL2` be the (hash of) the current L2 chain head -- `refL1` be the (hash of) the L1 block from which `refL2` was derived -- `payloadAttributes` be some previously derived [payload attributes] for the L1 block with number `l1Number(refL1) + 1` - -Then we can apply the following pseudocode logic to update the state of both the rollup driver and execution engine: - -```javascript -// request a new execution payload -forkChoiceState = { - headBlockHash: refL2, - safeBlockHash: refL2, - finalizedBlockHash: l2BlockHashAt(l2Number(refL2) - FINALIZATION_DELAY_BLOCKS) -} -[status, payloadID] = engine_forkchoiceUpdatedV1(forkChoiceState, payloadAttributes) -if (status != "SUCCESS") error() - -// retrieve and execute the execution payload -[executionPayload, error] = engine_getPayloadV1(payloadID) -if (error != null) error() -[status, latestValidHash, validationError] = engine_newPayloadV1(executionPayload) -if (status != "VALID" || validationError != null) error() - -refL2 = latestValidHash -refL1 = l1HashForNumber(l1Number(refL1) + 1)) - -// update head to new refL2 -forkChoiceState = { - headBlockHash: refL2, - safeBlockHash: refL2, - finalizedBlockHash: l2BlockHashAt(l2Number(headBlockHash) - FINALIZATION_DELAY_BLOCKS) -} -[status, payloadID] = engine_forkchoiceUpdatedV1(refL2, null) -if (status != "SUCCESS") error() -``` - -The following JSON-RPC methods are part of the [execution engine API][exec-engine]: - -> **TODO** fortify the execution engine spec with more information regarding JSON-RPC, notably covering -> information found [here][json-rpc-info-1] and [here][json-rpc-info-2] - -[json-rpc-info-1]: https://github.com/ethereum-optimism/optimistic-specs/blob/a3ffa9a8c825d155a0469659b3101db5f41eecc4/specs/rollup-node.md#from-l1-blocks-to-payload-attributes -[json-rpc-info-2]: https://github.com/ethereum-optimism/optimistic-specs/blob/a3ffa9a8c825d155a0469659b3101db5f41eecc4/specs/rollup-node.md#building-the-l2-block-with-the-execution-engine - -[exec-engine]: exec-engine.md - -- [`engine_forkchoiceUpdatedV1`] — updates the forkchoice (i.e. the chain head) to `headBlockHash` if different, and - instructs the engine to start building an execution payload given payload attributes the second argument isn't `null` -- [`engine_getPayloadV1`] — retrieves a previously requested execution payload -- [`engine_newPayloadV1`] — executes an execution payload to create a block - -[`engine_forkchoiceUpdatedV1`]: exec-engine.md#engine_forkchoiceUpdatedV1 -[`engine_getPayloadV1`]: exec-engine.md#engine_newPayloadV1 -[`engine_newPayloadV1`]: exec-engine.md#engine_newPayloadV1 - -The execution payload is an object of type [`ExecutionPayloadV1`]. - -[`ExecutionPayloadV1`]: https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#executionpayloadv1 - -Within the `forkChoiceState` object, the properties have the following meaning: - -- `headBlockHash`: block hash of the last block of the L2 chain, according to the rollup driver. -- `safeBlockHash`: same as `headBlockHash`. -- `finalizedBlockHash`: the hash of the block whose number is `l2Number(headBlockHash) - FINALIZATION_DELAY_BLOCKS` if - the number of that block is `>= L2_CHAIN_INCEPTION`, 0 otherwise (\*) See the [Finalization Guarantees][finalization] - section for more details. - -(\*) where: - -- `FINALIZATION_DELAY_BLOCKS == 50400` (approximately 7 days worth of L1 blocks) -- `L2_CHAIN_INCEPTION` is the [L2 chain inception][g-inception] (the number of the first L1 block for which an L2 block - was produced). - -Finally, the `error()` function signals an error that must be handled by the implementation. Refer to the next section -for more details. - -### Engine API Error Handling - -[error-handling]: #engine-api-error-handling - -All invocations of [`engine_forkchoiceUpdatedV1`], [`engine_getPayloadV1`] and [`engine_newPayloadV1`] by the -rollup driver should not result in errors assuming conformity with the specification. Said otherwise, all errors are -implementation concerns and it is up to them to handle them (e.g. by retrying, or by stopping the chain derivation and -requiring manual user intervention). - -The following scenarios are assimilated to errors: - -- [`engine_forkchoiceUpdatedV1`] returning a `status` of `"SYNCING"` instead of `"SUCCESS"` whenever passed a - `headBlockHash` that it retrieved from a previous call to [`engine_newPayloadV1`]. -- [`engine_newPayloadV1`] returning a `status` of `"SYNCING"` or `"INVALID"` whenever passed an execution payload - that was obtained by a previous call to [`engine_getPayloadV1`]. - -### Finalization Guarantees - -[finalization]: #finalization-guarantees - -As stated earlier, an L2 block is considered *finalized* after a delay of `FINALIZATION_DELAY_BLOCKS == 50400` L1 blocks -after the L1 block that generated it. This is a duration of approximately 7 days worth of L1 blocks. This is also known -as the "fault proof window", as after this time the block can no longer be challenged by a fault proof. - -L1 Ethereum reaches [finality][l1-finality] approximately every [12.8 minutes][consensus-time-params]. L2 blocks -generated from finalized L1 blocks are "safer" than most recent L2 blocks because they will never disappear from the -chain's history because of a re-org. However, they can still be challenged by a fault proof until the end of the fault -proof window. - -[l1-finality]: https://www.paradigm.xyz/2021/07/ethereum-reorgs-after-the-merge -[consensus-time-params]: https://github.com/ethereum/consensus-specs/blob/v1.0.0/specs/phase0/beacon-chain.md#time-parameters - -> **TODO** the spec doesn't encode the notion of fault proof yet, revisit this (and include links) when it does - -## Whole L2 Chain Derivation - -The [block derivation](#from-l1-blocks-to-payload-attributes) presents an inductive process: -given that we know the last L2 block derived from the previous [sequencing window][g-sequencing-window], as well as the -next [sequencing window][g-sequencing-window], then we can derive [payload attributes] of the next L2 blocks. +1. Select inputs from the L1 chain, on top of the last L2 block: + a list of blocks, with transactions and associated data and receipts. +2. Read L1 information, deposits, and sequencing batches in order to generate [payload attributes][g-payload-attr] + (essentially [a block without output properties][g-block]). +3. Pass the payload attributes to the [execution engine][g-exec-engine], so that the L2 block (including [output block + properties][g-block]) may be computed. -To derive the whole L2 chain from scratch, we simply start with the L2 genesis block as the last L2 block, and the -block at height `L2_CHAIN_INCEPTION + 1` as the start of the next sequencing window. -Then we iteratively apply the derivation process from the previous section by shifting the sequencing window one L1 -block forward each step, until there is an insufficient number of L1 blocks left for a complete sequencing window. +While this process is conceptually a pure function from the L1 chain to the L2 chain, it is in practice incremental. The +L2 chain is extended whenever new L1 blocks are added to the L1 chain. Similarly, the L2 chain re-organizes whenever the +L1 chain [re-organizes][g-reorg]. -> **TODO** specify genesis block +For a complete specification of the L2 block derivation, refer to the [L2 block derivation document](./derivation.md). ## L2 Output RPC method @@ -373,62 +69,7 @@ The input and return types here are as defined by the [engine API specs][engine- 1. `version`: `DATA`, 32 Bytes - the output root version number, beginning with 0. 1. `l2OutputRoot`: `DATA`, 32 Bytes - the output root -# Handling L1 Re-Orgs - -[l1-reorgs]: #handling-L1-re-orgs - -The [previous section on L2 chain derivation][l2-chain-derivation] assumes linear progression of the L1 chain. It is -also applicable for batch processing, meaning that any given point in time, the canonical L2 chain is given by -processing the whole L1 chain since the [L2 chain inception][g-inception]. - -If the L1 Chain re-orgs, the rollup node must re-derive sections of the L2 chain such that it derives the same L2 chain -that a rollup node would derive if it only followed the new L1 chain. - -> By itself, the previous section fully specifies the behavior of the rollup driver. **The current section is -> non-specificative** but shows how L1 re-orgs can be handled in practice. - -In practice, the L1 chain is processed incrementally. However, the L1 chain may occasionally [re-organize][g-reorg], -meaning the head of the L1 chain changes to a block that is not the child of the previous head but rather another -descendant of an ancestor of the previous head. In that case, the rollup driver must first search for the common L1 -ancestor, and can re-derive the L2 chain from that L1 block and onward. - -The rollup node maintains two heads of the L2 Chain: the unsafe head (often called head) and the safe head. -Each L2 block has an L1 origin block (corresponding to its epoch) that it references in the -[L1 attributes deposited transaction][l1-attr-deposit]. The unsafe head is the head of the L2 chain. -Its L1 origin block should be canonical or potentially extending the canonical chain -(if the rollup node has not yet seen the L1 block that it is based upon). -The safe head is the the last L2 block of the last epoch whose sequencing window is complete -(i.e. the epoch with number `L1Head.number` - `SEQUENCING_WINDOW_SIZE`). - -[l1-attr-deposit]: glossary.md#l1-attributes-deposited-transaction - -Steps during a reorg: - -1. Set "unsafe head" to equal the l2 head we retrieved, just as default -2. Set "latest block" to equal the l2 head we retrieved, also just as default -3. Walk back L2, and stop until block.l1Origin is found AND canonical, and update "latest block" to this block. -And don't override "unsafe head" if it's not found, but do override it when block.l1Origin does not match the -canonical L1 block at that height. -4. Walk back L2 from the "latest block" until a full sequencing window of L1 blocks has been passed. -This is the "safe block". - -The purpose of this is to ensure that if the sequencing window for a L2 block has changed since it was derived, -that L2 block is re-derived. - -The first L1 block of the sequencing window is the L1 attributes for that L2 block. The end of the sequencing -window is the canonical L1 block whose number is `SEQUENCING_WINDOW` larger than the start. The end of the -window must be selected by number otherwise the sequencer would not be able to create batches. The problem -with selecting the end of the window by number is that when an L1 reorg occurs, the blocks (and thus batches) -in the window could change. We must find the find the first L2 block whose complete sequencing window is -unchanged in the reorg. - -When walking back on the L2 chain, care should be taken to not walk past the rollup genesis. - -Note that post-[merge], the depth of re-orgs will be bounded by the [L1 finality delay][l1-finality] (every 2 epochs, -approximately 12 minutes). +## L2 Batch creation RPC method -(\*) Post-merge, this is only possible for 12 minutes. In practice, we'll pick an already-finalized L1 block as L2 -inception point to preclude the possibility of a re-org past genesis, at the cost of a few empty blocks at the start of -the L2 chain. +See [Batch derivation](./batching.md) for an RPC specification for batch data submission. -[merge]: https://ethereum.org/en/eth2/merge/