Commit 11320c87 authored by Maurelian's avatar Maurelian Committed by GitHub

Merge pull request #1867 from ethereum-optimism/remove-specs

parents fdbe72d8 343c3d30
# @eth-optimism/specs
## 0.3.0
### Minor Changes
- 3f590e33: Remove the "OVM" Prefix from contract names
### Patch Changes
- b70ee70c: upgraded to solidity 0.8.9
## 0.2.1
### Patch Changes
- 6d3e1d7f: Update dependencies
## 0.2.0
### Minor Changes
- 318857e: Add a new package to contain specs
(The MIT License)
Copyright 2020-2021 Optimism
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# The Optimistic Ethereum Spec
## Notice
We're in the process of moving our specifications to the [optimistic-specs](https://github.com/ethereum-optimism/optimistic-specs) repository.
**The specifications within this folder may be entirely outdated**.
Please refer to the `optimistic-specs` repository for the most up-to-date system specifications.
We will remove this folder once the migration process is complete.
# L2 Geth Specs
## Contents
- [Transaction indexer](./transaction-indexer.md)
- [Transaction ingestor](./transaction-ingestor.md)
- [Transaction types](./transaction-types.md)
# Sequencer specs
## Contents
- [Batch Submitter](./batch-submitter.md) (empty file)
- [Fees](./fees.md) (empty file)
### Flow of a tx sent to a Sequencer
1. An L2 transaction is submitted to [l2geth](https://github.com/ethereum-optimism/go-ethereum)'s RPC server.
2. The transaction then sent to the [`Sync Service`](https://github.com/ethereum-optimism/specs/blob/main/transaction-ingestor.md)'s (aka `transaction-ingestor`) `applyTransaction(..) function.
3. The raw transaction data is supplied as the input to the [`run(...)`](https://github.com/ethereum-optimism/contracts/blob/master/contracts/optimistic-ethereum/OVM/execution/OVM_ExecutionManager.sol#L155-L158) function in the ExecutionManager.
4. The block including a state root, transaction receipt, and transaction are all stored in Geth's default `EthDB`.
5. The batch submitter periodically queries L1 & L2Geth for unsubmitted transactions & if any are detected queries L2Geth for a range of blocks.
6. The GetBlock endpoint gets the block & transaction data from the EthDB.
7. Blocks are returned to the batch submitter.
8. The batch submitter generates a `appendSequencerBatch` transaction to the CTC on L1 and broadcasts it to its local L1Geth node. From there the transactions makes it into the Eth mainchain for availability & ordering.
<pre>
Ethereum Mainnet
┌──────────────────────────────┐
│┼────────────────────────────┼│
││ Ethereum L1 Mempool/Miners ││
│┼────────────────────────────┼│
└────────────▲─────────────────┘
Sequencer │
┌────────────────────────────────┼─────────────────────────────────────┐
│ │ │
│ ┌───────▼────────┐ 8)AppendBatch (many blocks)│
│ │ L1Geth ◄──────────────┐ │
│ └────────────────┘ │ │
│ L2Geth │ │
│ ┌────────────────────────────────────┐ │ │
│ │ │ │ │
│ │ ┌──────────────────────────┐ 5)Get│Blocks ┌──────┴────────┐ │
│ │ │ <a href="https://github.com/ethereum-optimism/go-ethereum/blob/master/internal/ethapi/api.go">RPC</a> ◄───────┼───────┤ │ │
│ │ │ │ │ 7)│Blocks │ │ │
1)Transaction┌────┼────┼─► SendTx(..) │GetBlock(..) ├───────┼───────►<a href="/sequencer/batch-submitter.md">Batch Submitter</a>│ │
────────────┘ │ │ └──────┬─────┴─────┬──▲────┘ │ └───────────────┘ │
│ │ 2)Tx│ │6)│Get Block │ │
│ │ │ ┌───▼──┴────┐ │ │
│ │ ┌──────▼─────┐ │ │ │ │
│ │ │<a href="/l2-geth/transaction-ingestor.md">Sync Service</a>│ │ <a href="https://github.com/ethereum-optimism/go-ethereum/tree/83593f20c213129f6dceac6321e7cbbad0035a26/core/rawdb">EthDB</a> │ │ │
│ │ └──────┬─────┘ │ │ │ │
│ │ │ └─────▲─────┘ │ │
│ │ 3)Tx│ 4)│Tx, Receipts │ │
│ │ │ │State root │ │
│ │ ┌──────▼─────────────┴───┐ │ │
│ │ │ <a href="/ovm/README.md">OVM</a> │ │ │
│ │ └────────────────────────┘ │ │
│ │ │ │
│ └────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────┘
</pre>
### Verifier Syncing from Mainnet
1. Batches full of L2 transactions are pulled in from an L1Geth node syncing mainnet Ethereum.
2. Each transaction is applied to the Sync Service (AKA the `transaction-ingestor`).
3. The Sync Service pipes the transaction into the OVM (using Geth's Clique Miner).
4. The OVM executes the transaction and stores the resulting state & blocks in the EthDB.
5. The Fraud Prover service pulls all of the recent state roots from L2Geth.
6. The RPC returns state roots stored in the EthDB that were originally stored by the OVM.
7. Now with all of the Verifier computed state roots, the Fraud Prover pulls all of the proposed state roots on L1. It compares the state roots that were computed **locally** against the **proposed** state roots in the State Commitment Chain. _UH OH_ the proposed state root DOES NOT equal the locally computed (and therefore correct) state root! We need to prove fraud!
8. The Fraud Prover requests a full fraud proof from the L2Geth node. This includes the contracts and storage slots that were accessed during the execution of the transaction.
9. The Fraud Prover signs Ethereum transactions and executes a fraud proof on L1! Claiming the bond of the state root proposer.
Yay the chain is secured by L1 and cryptoeconomics!
<pre>
Ethereum Mainnet
┌──────────────────────────────┐
│┼────────────────────────────┼│
││ Ethereum L1 Mempool/Miners ││
│┼────────────────────────────┼│
└────────────▲─────────────────┘
Verifier │
┌─────────────────────────┼───────────────────────────────────┐
│ │ │
│ ┌───────▼────────┐ 9)Execute Fraud proof │
│ │ L1Geth │◄─────────────────────┐ │
│ └─┬────┬─────────┘ │ │
│ │ │7)State Commitment Chain │ │
│ │ └───────────────────────┐ │ │
│ L2Geth │ │ │ │
│ ┌─────────────────┼───────────────────────┐ ┌──▼────────┴─┐ │
│ │ 1)│Batches │ │<a href="/verifier/fraud-prover.md">Fraud Prover</a> │ │
│ │ │ ┌─────────┬┤ └─▲─────────▲─┘ │
│ │ ┌───────▼────────┐ │ <a href="https://github.com/ethereum-optimism/go-ethereum/blob/master/internal/ethapi/api.go">RPC</a> ││ │ │ │
│ │ │<a href="/l2-geth/l1-data-indexer.md">Tx Indexer (DTL)</a>│ │ ◄├───┘ │ │
│ │ └──┬─────────────┘ └▲───────┬─┤5)Get State │ │
│ │ 2)Tx│ 6)│State │ │ roots │ │
│ │ │ │roots, │ │ │ │
│ │ │ │ └─┼─────────────┘ │
│ │ ┌──────▼─────┐ ┌──────────▼┐ │ 8)Fraud │
│ │ │<a href="/l2-geth/transaction-ingestor.md">Sync Service</a>│ │ <a href="https://github.com/ethereum-optimism/go-ethereum/tree/83593f20c213129f6dceac6321e7cbbad0035a26/core/rawdb">EthDB</a> │ │ proof │
│ │ └──────┬─────┘ └─────▲─────┘ │ data │
│ │ 3)Tx│ 4)│Tx, Receipts, │ │
│ │ │ │State roots │ │
│ │ ┌──────▼─────────────┴───┐ │ │
│ │ │ <a href="/ovm/README.md">OVM</a> │ │ │
│ │ └────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
</pre>
# Transaction Fees Spec
## Goals
- Allow projects to easily query how high of a gasPrice their users need to set to get their transaction approved
- Allow the sequencer to only accept transactions that at least cover their cost to the system (L1 calldata gas costs + L2 gas costs)
## Non-goals
- Minimum possible fees
- Fee calculation that is completely automated
- Congestion Fees
## Why a separate service?
N/A
## Pre-launch timeline
- [ ] getGasPrice endpoint in geth + rejecting underfunded txs 2 weeks before launch
- [ ] Paying fees in Kovan -both Synthetix and Kovan by 1.5 week before launch
- [ ] Paying fees in Mainnet 1 week before launch
## Inputs & dependencies
- OVM_ETH (for fee payment)
- ETH Deposit Withdrawal Dapp
- Seeing your L2 WETH in SNX frontend
### getGasPrice(tx)
- Input is tx calldata
## Outputs
### estimateGasPrice(tx, ?gasLimit)
- returns `gasPrice` in wei to set for a given tx, taking into account the current `dataPrice` and `l2GasPrice` along with the `tx` calldata.
### getDataPrice()
- returns the `dataPrice` in wei (cost per zero byte). This will approximately track 4 \* l1 gasPrice
### getGasPrice()
- returns the gasPrice for L2 execution
### validateTx
- returns error message for eth_sendRawTransaction
- If tx is underfunded, "Error: Inadequate transaction fee. For this transaction to be accepted, set a gasPrice of \_\_ gwei"
- If tx has a gasLimit !== 9m, "Error: Expected a gas limit of 9m. Please set your tx gasLimit to 9m."
## Internals
Geth sets a dataPrice and a gasPrice internally. This can be displayed publicly on an `*.optimism.io` site.
- `dataPrice` in wei is the cost per zero byte of calldata. A non-zero byte of calldata will cost `dataPrice * 4` in wei.
- `gasPrice` in wei is the cost per unit of gas consumed during L2 execution.
```
// estimates the gas price based on a txs size and its gasLimit so gasPrice * gasLimit = intended fee
function estimateGasPrice(tx, ?gasLimit=9000000) {
const dataCost = dataPrice * (tx.zeroBytes + (tx.nonZeroBytes * 4))
const gasCost = gasLimit * gasPrice
return ((gasCost + dataCost) / gasLimit)
}
```
# Synchronization Spec
The transaction data can be indexed from either L1 or L2. This data is
then used for execution which generates state. The benefit of syncing from
L2 is that the data will be synced before the transactions are batch submitted.
The benefit of syncing from L1 is a guarantee that the transactions are final
relative to L2, assuming no fraud proof.
## Indexing Data From Layer 1
We need a reliable stream of data from our Layer 1 smart contracts.
This doc specifies what data we need and how we're getting it.
See [Appendix](#appendix) for a list of data structures.
### L1 Transaction Indexer API
#### getEnqueuedTransactionByIndex
```ts
function getEnqueuedTransactionByIndex(index: number): EnqueuedTransaction;
```
#### getLatestEnqueuedTransaction
```ts
function getLatestEnqueuedTransaction(): EnqueuedTransaction;
```
#### getTransactionByIndex
```ts
function getTransactionByIndex(index: number): Transaction;
```
#### getLatestTransaction
```ts
function getLatestTransaction(): Transaction;
```
#### getStateRootByIndex
```ts
function getStateRootByIndex(index: number): StateRoot;
```
#### getLatestStateRoot
```ts
function getLatestStateRoot(): StateRoot;
```
### Requirements
- Must get data within X amount of time (?)
- Must not get reorg'd out of existence.
### Retrieving Relevant Data
We need to retrieve the following information reliably:
- All transactions enqueued for inclusion in the CanonicalTransactionChain in the order in which they were enqueued.
- All transactions included in the CanonicalTransactionChain in the order in which they were included.
- All state roots included in the StateCommitmentChain in the order in which they were included.
All relevant data can be retrieved by parsing data from the following functions:
- `CanonicalTransactionChain.enqueue`
- `CanonicalTransactionChain.appendQueueBatch`
- `CanonicalTransactionChain.appendSequencerBatch`
- `StateCommitmentChain.appendStateBatch`
#### Enqueued Transactions
Transactions are "enqueued" when users make calls to `CanonicalTransactionChain.enqueue`.
Calls to this function can be detected by searching for [`TransactionEnqueued`](#transactionenqueued) events.
All relevant transaction data can be pulled out of the event, here's a pseudocode function for doing so:
```ts
function parseTransactionEnqueuedEvent(
event: TransactionEnqueued,
): EnqueuedTransaction {
return {
queueIndex: event.queueIndex,
timestamp: event.timestamp,
blockNumber: getBlockNumber(event),
l1QueueOrigin: QueueOrigin.L1TOL2_QUEUE,
l1TxOrigin: event.l1TxOrigin
entrypoint: event.target,
gasLimit: event.gasLimit,
data: event.data
}
}
```
So the process of parsing and indexing these transactions is pretty straightforward:
1. Listen to [`TransactionEnqueued`](#transactionenqueued) events.
2. Parse each found event into `EnqueuedTransaction` structs.
3. Store each event based on their `queueIndex` field.
#### Transactions (via appendQueueBatch)
::: tip Note
`appendQueueBatch` is currently disabled on mainnet.
:::
Transactions can be inserted into the "canonical transaction chain" via either `appendQueueBatch` or `appendSequencerBatch`.
`appendQueueBatch` is used to move enqueued transactions into the canonical set of transactions.
Until this is done, these enqueued transactions are not considered part of the L2 history.
Whenever `appendQueueBatch` is called, a [`TransactionBatchAppended`](#transactionbatchappended) event is emitted followed by a [`QueueBatchAppended`](#queuebatchappended) event in the same transaction (such that the index of the second event is equal to the index of the first event plus one).
These events do **not** include the complete forms of the various transactions that were "appended" by this function call -- only pointers to the corresponding queue elements.
As a result, we're required to pull this information from a combination of the events and the `EnqueuedTransaction` objects previously parsed from `enqueue`.
More pseudocode for parsing this list of transactions:
```ts
function parseQueueBatchAppendedEvent(
event: QueueBatchAppended
): Transaction[] {
// Get the `TransactionBatchAppended` event. Really should be turned into a
// single event to avoid having to do this extra network request.
const event2: TransactionBatchAppended = getEventByIndex(
getEventIndex(event) - 1
)
const transactions: Transaction[] = []
for (let i = 0; i < event.numQueueElements) {
// Note that this places an assumption on how events are parsed. This
// only works if enqueued transactions are parsed before
// `appendQueueBatch` events.
const enqueuedTransaction: EnqueuedTransaction = getEnqueuedTransactionByIndex(
event.startingQueueIndex + i
)
transactions.push({
l1QueueOrigin: QueueOrigin.L1TOL2_QUEUE,
timestamp: enqueuedTransaction.timestamp,
blockNumber: enqueuedTransaction.blockNumber,
l1TxOrigin: enqueuedTransaction.l1TxOrigin,
entrypoint: enqueuedTransaction.entrypoint,
gasLimit: enqueuedTransaction.gasLimit,
data: enqueuedTransaction.data
})
}
// TODO: Add parsing batches.
return transactions
}
```
#### Transactions (via appendSequencerBatch)
`appendSequencerBatch` is the other method by which transactions can be inserted into the canonical transaction chain.
`appendSequencerBatch` makes use of a custom encoding scheme for efficiency reasons and does not have any explicit parameters.
The function internally parses `calldata` directly using the following format:
- Bytes 0-3 (4 bytes) of `calldata` are the 4 byte function selector derived from `keccak256("appendSequencerBatch()")`. Just skip these four bytes.
- Bytes 4-8 (5 bytes; `uint40`) describe the index of the "canonical transaction chain" that this batch of transactions expects to follow.
- Bytes 9-11 (3 bytes; `uint24`) are the total number of elements that the sequencer wants to append to the chain.
- Bytes 12-14 (3 bytes; `uint24`) are the total number of "batch contexts," effectively timestamp/block numbers to be assigned to given sets of transactions.
- After byte 14, we have a series of encoded "batch contexts." Each batch context is exactly **16 bytes**. The number of contexts comes from bytes 12-14, as described above. Each context has the following structure:
- Bytes 0-2 (3 bytes; `uint24`) are the number of sequencer transactions that will utilize this batch context.
- Bytes 3-5 (3 bytes; `uint24`) are the number of _queue_ transactions that will be inserted into the chain after these sequencer transactions.
- Bytes 6-10 (5 bytes; `uint40`) are the timestamp that will be assigned to these sequencer transactions.
- Bytes 11-15 (5 bytes; `uint40`) are the block number that will be assigned to these sequencer transactions.
- After the batch context section, we have a series of dynamically sized transactions. Each transaction consists of the following information:
- Bytes 0-2 (3 bytes: `uint24`) are the total size of the coming transaction data in bytes.
- Some arbitrary data of a length equal to that described by the first three bytes.
We can represent the input as an object roughly equivalent to the following json-ish thing:
```ts
interface AppendSequencerBatchParams {
sighash: 4 bytes,
shouldStartAtElement: 5 bytes,
totalElementsToAppend: 3 bytes,
numContexts: 3 bytes,
contexts: Array<{
numSequencedTransactions: 3 bytes,
numSubsequentQueueTransactions: 3 bytes,
ctxTimestamp: 5 bytes,
ctxBlockNumber: 5 bytes
}>,
transactions: Array<{
txDataLength: 3 bytes,
txData: txDataLength bytes
}>
}
```
Decoding function (in pseudocode):
```ts
function decode(calldata: bytes): AppendSequencerBatchParams {
const sighash = calldata[0:4]
const shouldStartAtElement = uint40(calldata[4:9])
const totalElementsToAppend = uint24(calldata[9:12])
const numContexts = uint24(calldata[12:15])
let ptr = 15
const contexts = []
for (let i = 0; i < numContexts; i++) {
contexts.push({
numSequencedTransactions: uint24(calldata[ptr:ptr+3])
numSubsequentQueueTransactions: uint24(calldata[ptr+3:ptr+6]),
ctxTimestamp: uint40(calldata[ptr+6:ptr+11]),
ctxBlockNumber: uint40(calldata[ptr+11:ptr+16])
})
ptr = ptr + 16
}
const transactions = []
while (ptr < length(calldata)) {
const txDataLength = uint24(calldata[ptr:ptr+3])
transactions.push({
txDataLength: txDataLength,
txData: calldata[ptr+3:ptr+3+txDataLength]
})
ptr = ptr + 3 + txDataLength
}
return {
sighash: sighash,
shouldStartAtElement: shouldStartAtElement,
totalElementsToAppend: totalElementsToAppend,
numContexts: numContexts,
contexts: contexts,
transactions: transactions,
}
}
```
Encoding function (in pseudocode):
```ts
function encode(params: AppendSequencerBatchParams): bytes {
let calldata = bytes()
calldata[0:4] = bytes4(params.sighash)
calldata[4:9] = bytes5(params.shouldStartAtElement)
calldata[9:12] = bytes3(params.totalElementsToAppend)
calldata[12:15] = bytes3(params.numContexts)
let ptr = 15
for (const context of params.contexts) {
calldata[ptr:ptr+3] = bytes3(context.numSequencedTransactions)
calldata[ptr+3:ptr+6] = bytes3(context.numSubsequentQueueTransactions)
calldata[ptr+6:ptr+11] = bytes5(context.ctxTimestamp)
calldata[ptr+11:ptr+16] = bytes5(context.ctxBlockNumber)
ptr = ptr + 16
}
for (const transaction of params.transactions) {
const txDataLength = transaction.txDataLength
calldata[ptr:ptr+3] = bytes3(txDataLength)
calldata[ptr+3:ptr+3+txDataLength] = bytes(transaction.data)
ptr = ptr + 3 + txDataLength
}
return calldata
}
```
When the sequencer calls `appendQueueBatch`, `contexts` are processed one by one.
For each context, we first append `numSequencedTransactions` which are popped off of the `transactions` array.
Each of these transactions have the following form:
```solidity
Transaction({
timestamp: context.ctxTimestamp,
blockNumber: context.ctxBlockNumber,
l1QueueOrigin: QueueOrigin.SEQUENCER_QUEUE,
l1TxOrigin: 0x0000000000000000000000000000000000000000,
entrypoint: 0x4200000000000000000000000000000000000005,
gasLimit: OVM_ExecutionManager.getMaxTransactionGasLimit(),
data: tx.txData,
})
```
We next pull `numSubsequentQueueTransactions` transactions in from the queue (by reference to the queue).
This process is repeated for every provided `context`.
`appendSequencerBatch` can be detected (and parsed) by looking for [`SequencerBatchAppended`](#sequencerbatchappended) events.
Each of these events will always be immediately preceeded by a [`TransactionBatchAppended`](#transactionbatchappended) event.
Somewhat like with `appendQueueBatch`, we'll have to carefully pull all of the relevant information out of these two events and previously parsed `EnqueuedTransactions`.
Notably, we also need access to the `calldata` sent to `appendSequencerBatch`.
We can retrieve this data by making a call to [`debug_traceTransaction`](https://geth.ethereum.org/docs/rpc/ns-debug#debug_tracetransaction) (or an equivalent endpoint that exposes call traces).
If the client does not have access to the `debug_traceTransaction` endpoint, then the `calldata` can only be retrieved if `appendSequencerBatch` is called directly by an externally owned account (because then `calldata === transaction.input`, which is easily [accessible via the standard API](https://eth.wiki/json-rpc/API#eth_getTransactionByHash)).
When the client detects a `SequencerBatchAppended` event, they should pull the preceeding `TransactionBatchAppended` event.
Then they should retrieve the `calldata` and decode it using the above decoding scheme.
Client **should** validate input under the assumption that data from the L1 node is not reliable.
Finally, pseudocode for parsing the event:
```ts
function parseSequencerBatchAppendedEvent(
event: QueueBatchAppended
): Transaction[] {
// Get the `TransactionBatchAppended` event. Really should be turned into a
// single event to avoid having to do this extra network request.
const event2: TransactionBatchAppended = getEventByIndex(
getEventIndex(event) - 1
);
const calldata: bytes = getCalldataByTransaction(getTransaction(event));
const params: AppendSequencerBatchParams = decode(calldata);
let sequencerTransactionCount = 0;
let queueTransactionCount = 0;
const transactions: Transaction[] = [];
for (const context of params.contexts) {
for (let i = 0; i < context.numSequencerTransactions; i++) {
transactions.push({
l1QueueOrigin: QueueOrigin.SEQUENCER_QUEUE,
timestamp: context.ctxTimestamp,
blockNumber: context.ctxBlockNumber,
l1TxOrigin: 0x0000000000000000000000000000000000000000,
entrypoint: 0x4200000000000000000000000000000000000005,
gasLimit: OVM_ExecutionManager.getMaxTransactionGasLimit(),
data: params.transactions[sequencerTransactionCount],
});
sequencerTransactionCount = sequencerTransactionCount + 1;
}
for (let i = 0; i < context.numSubsequentQueueTransactions; i++) {
// Note that this places an assumption on how events are parsed. This
// only works if enqueued transactions are parsed before
// `appendQueueBatch` events.
const enqueuedTransaction: EnqueuedTransaction =
getEnqueuedTransactionByIndex(
event.startingQueueIndex + queueTransactionCount
);
transactions.push({
l1QueueOrigin: QueueOrigin.L1TOL2_QUEUE,
timestamp: enqueuedTransaction.timestamp,
blockNumber: enqueuedTransaction.blockNumber,
l1TxOrigin: enqueuedTransaction.l1TxOrigin,
entrypoint: enqueuedTransaction.entrypoint,
gasLimit: enqueuedTransaction.gasLimit,
data: enqueuedTransaction.data,
});
queueTransactionCount = queueTransactionCount + 1;
}
}
// TODO: Add parsing batches.
return transactions;
}
```
## Appendix
### Enums
#### QueueOrigin
```solidity
enum QueueOrigin {
SEQUENCER_QUEUE,
L1TOL2_QUEUE
}
```
### Structs
#### Transaction
```solidity
struct Transaction {
QueueOrigin l1QueueOrigin;
uint256 timestamp;
uint256 blockNumber;
address l1TxOrigin;
address entrypoint;
uint256 gasLimit;
bytes data;
}
```
#### EnqueuedTransaction
```solidity
struct EnqueuedTransaction {
QueueOrigin l1QueueOrigin;
uint256 timestamp;
uint256 blockNumber;
address l1TxOrigin;
address entrypoint;
uint256 gasLimit;
bytes data;
uint256 queueIndex;
}
```
### Events
#### TransactionEnqueued
```solidity
event TransactionEnqueued(
address l1TxOrigin,
address target,
uint256 gasLimit,
bytes data,
uint256 queueIndex,
uint256 timestamp,
uint256 blockNumber
);
```
#### TransactionBatchAppended
```solidity
event TransactionBatchAppended(
uint256 indexed batchIndex,
bytes32 batchRoot,
uint256 batchSize,
uint256 prevTotalElements,
bytes extraData
);
```
#### SequencerBatchAppended
```solidity
event SequencerBatchAppended(
uint256 startingQueueIndex,
uint256 numQueueElements,
uint256 totalElements
);
```
#### QueueBatchAppended
```solidity
event QueueBatchAppended(
uint256 startingQueueIndex,
uint256 numQueueElements,
uint256 totalElements
);
```
## Indexing Data From Layer 2
It is possible to sync data from L2 and expose the same API
# Transaction Ingestor
This spec is written in Python pseudocode. It may differ slightly from actual Python, but it should be close enough to
implement correctly in any language. It is currently implemented as a fork of `go-ethereum` and includes some implementation
specific details.
### Types
The types used throughout this document are listed below.
#### `Transaction`
```python
class Transaction:
meta: TransactionMeta
nonce: int
to: Address
data: bytes
gas: int
gasPrice: int
signature: bytes[65]
```
The `Transaction` is similar to an `ethereum.Transaction` but includes an extra field `meta`. In the future account abstracted world,
a `Transaction` may just be a typed blob. Note that Optimism already has account abstraction, but it is not a first class citizen
relative to the node software as to maintain a compatible Ethereum RPC API.
#### `TransactionMeta`
```python
class TransactionMeta:
index: int
queue_index: int
l1_timestamp: int
l1_blocknumber: int
l1_tx_origin: Address
queue_origin: QueueOrigin
type: TransactionType
```
The `TransactionMeta` includes specific Layer Two information that differs from the traditional Ethereum Transaction.
`TransactionMeta.index` being present indicates that this transaction has been sequenced by the sequencer and
its value is equal to the transaction's CTC index or the future CTC index in the case where the transaction has yet to be
batch submitted. `TransactionMeta.queue_index` is the queue index and is only present for L1 to L2 transactions.
#### `QueueOrigin`
```python
class QueueOrigin(enum):
L1_TO_L2
SEQUENCER
```
The `QueueOrigin` is a property of the `TransactionMeta` and the value indicates the place that the transaction originated
from. An `L1_TO_L2` transaction originates from Layer One and a `SEQUENCER` transaction originates from the sequencer.
#### `TransactionType`
```python
class TransactionType(enum):
EIP155
ETH_SIGN
```
The `TransactionType` is a property of the `TransactionMeta`. The `EIP155` type represents an EIP155 transaction without
a value property. The `ETH_SIGN` type represents an EIP155 like transaction where the signature hash is based on an
abi encoding `eth_sign` signature hash. There is no native value property.
#### `NodeType`
```python
class NodeType(enum):
VERIFIER
SEQUENCER
```
The `NodeType` is a property of the `TransactionExecutor` and refers to the permissions of the node in the system. The
`SEQUENCER` can extend the chain by submitting batches and the `VERIFIER` is used to compute state roots to determine
if a fraud proof should be submitted.
#### `Block`
```python
class Block(ethereum.Block):
transactions: Tuple[ethereum.Transaction]
```
The `Block` is similar to the standard Ethereum Block except that it is defined to have only a single transaction.
A `Tuple` is used to define the fixed size nature of `Block.transactions`.
#### `Blockchain`
```python
class Blockchain(ethereum.Blockchain):
@abc.abstractmethod
def get_block_at_index(index: int) -> Block:
return
@abc.abstractmethod
def reorg(index: int):
return
```
The `Blockchain` represents the underlying blockchain implementation. It is functionally the same as the blockchain
implementation in an Ethereum node implementation. The underlying implementations of `get_block_at_index` and
`reorg` are omitted and assumed to be correct.
#### `Miner`
```python
class Miner(ethereum.Miner):
@abc.abstractmethod
def apply_transaction(tx: Transaction) -> bool:
return
```
The `Miner` represents the underlying `Miner` implementation. It is functionally the same as the `Miner` implementation
in an Ethereum implementation. Note that `apply_transaction` will add a block with a single transaction to the
`Blockchain` after it is called.
#### `TxPool`
```python
class TxPool:
@abc.abstractmethod
def validate_tx(tx: Transaction):
return
```
The `TxPool` represents the underlying `TxPool` implementation. It is only used validate incoming `QueueOrigin.SEQUENCER`
transactions. It is the same validation method that is called in a normal Ethereum node implementation before a
transaction is added to the mempool. It checks that the nonce is correct and that there is enough gas to cover the
instrinsic gas.
#### `Batch`
```python
class Batch:
index: int
blockNumber: int
timestamp: int
submitter: Address
size: int
root: str
prevTotalElements: int
extraData: str
```
The `Batch` represents batched elements that have been submitted to the
Canonical Transaction Chain. The same data structure applies for both
transaction batches and state root batches.
#### `TransactionBatch`
```python
class TransactionBatch:
batch: Batch
transactions: List[Transaction]
```
#### `Backend`
The backend represents the data transport layer backend. It can be configured to
sync from either L1 or L2
```python
class Backend(enum):
L1
L2
```
#### `RollupClient`
```python
class RollupClient:
@abc.abstractmethod
def get_enqueue(index: int) -> Transaction:
return
@abc.abstractmethod
def get_latest_enqueue() -> Transaction:
return
@abc.abstractmethod
def get_transaction(index: int, backend: Backend) -> Transaction:
return
@abc.abstractmethod
def get_latest_transaction(backend: Backend) -> Transaction:
return
@abc.abstractmethod
def get_eth_context(index: int) -> EthContext:
return
@abc.abstractmethod
def get_latest_eth_context(index: int) -> EthContext:
return
@abc.abstractmethod
def get_last_confirmed_enqueue() -> Transaction:
return
@abc.abstractmethod
def sync_status() -> SyncStatus:
return
@abc.abstractmethod
def get_latest_tx_batch() -> TransactionBatch:
return
@abc.abstractmethod
def get_tx_batch(index: int) -> TransactionBatch:
return
```
The `RollupClient` is used to communicate with the transaction indexer. The implementation of the transaction indexer currently
exists as a different process but it may be pulled into the same process in the future.
#### `RollupContext`
```python
class RollupContext:
current_index: int
current_queue_index: int
current_tx_batch_index: int
```
The `RollupContext` consists of the `current_index` and `current_queue_index`. This allows the sequencer to prevent
replaying of transactions as well as safely restarting without double ingesting transactions. The `current_index` corresponds to the
transaction index in the Canonical Transaction Chain and the `current_queue_index` corresponds to the `queue_index` in the Canonical
Transaction Chain. The Canonical Transaction Chain is deployed on Layer One and is the source of truth for the state of the system.
These must be stored in a database.
Note that the `queue_index` refers to the index of the L1 to L2 transactions that have been sent to the `enqueue()` method in the CTC
while the `index` refers to the index of transactions in the CTC that have been appended with `sequencerBatchAppend()`
or `queueBatchAppend()`. After transactions are submitted via one of those methods, they can be considered finalized relative to Layer Two.
#### `EthContext`
```python
class EthContext:
last_l1_blocknumber: int
last_l1_timestamp: int
```
The `EthContext` includes the`last_l1_timestamp` and the `last_l1_blocknumber`. They are used for the EVM context at
transaction execution time. These values must be monotonic.
An L1 to L2 transaction must have the same timestamp and blocknumber in its EVM context on L2 as the L1 transaction itself.
The `TransactionExecutor` is also responsible for assigning timestamps to transactions sent directly to the sequencer.
These must be stored in a database.
#### `DB`
```python
class DB:
@abc.abstractmethod
def get_current_index() -> int:
return
@abc.abstractmethod
def get_current_queue_index() -> int:
return
@abc.abstractmethod
def get_current_tx_batch_index() -> int:
return
```
The `DB` is used to persist the `RollupContext`. Its implementation is omitted but it is required for clean
shutdowns of the node.
#### `NodeOptions`
```python
class NodeOptions:
db: DB
ctc_deploy_height: int
type: NodeType
l1_transaction_indexer_url: str
replica_transaction_indexer_url: str
```
The `NodeOptions` are the options passed to a node at runtime. It has both a `l1_transaction_indexer_url` as well as a
`replica_transaction_indexer_url` to allow for upgrades when running a `ReplicaSequencer`.
## Node Types + Modes
There are different node types and modes. Each extends the `TransactionExecutor` and has slightly different behavior.
- Sequencer
- Sequencer Replica
- Verifier
- Verifier Replica
### `TransactionExecutor`
The `TransactionExecutor` needs to track both the `RollupContext` and the `EthContext`. The `TransactionExecutor.start` method
is used to start the node and each moded type must implement this method.
```python
import abc
class TransactionExecutor:
def __init__(self, options):
self.eth_context = EthContext()
self.rollup_context = RollupContext()
self.db = options.db
self.rollup_context.current_index = self.db.get_current_index()
self.rollup_context.current_queue_index = self.db.get_current_queue_index()
self.rollup_context.current_batch_tx_index = options.db.get_current_tx_batch_index()
self.backend = None
self.type = None
self.client = None
self.ctc_deploy_height = options.ctc_deploy_height
self.miner = Miner()
self.blockchain = Blockchain()
self.tx_pool = TxPool()
self.poll_interval = options.poll_interval
@abc.abstractmethod
def start():
return
def set_initial_eth_context():
if self.eth_context.last_l1_timestamp is None:
context = self.client.get_eth_context(self.ctc_deploy_height)
self.eth_context.last_l1_timestamp = context.timestamp
self.eth_context.last_l1_blocknumber = context.blocknumber
else:
block = self.blockchain.get_block_at_index(self.ctc_deploy_height)
tx = block.transactions[0]
self.eth_context.last_l1_timestamp = tx.meta.l1_timestamp
self.eth_context.last_l1_blocknumber = tx.meta.l1_blocknumber
def set_initial_rollup_context():
if self.rollup_context.current_enqueue is None:
tx = self.client.get_last_confirmed_enqueue()
if tx is not None:
self.rollup_context.current_enqueue = tx.meta.index
block = self.blockchain.current_block()
if block.number - 1 != self.rollup_context.current_index:
self.rollup_context.current_index = block.number - 1
def apply_transaction(self, tx):
if tx.meta.index != None:
return self.apply_indexed_transaction(tx)
return self.apply_transaction_to_tip(tx)
def apply_indexed_transaction(self, tx):
index = tx.meta.index
if index == self.rollup_context.current_index + 1:
return apply_transaction_to_tip(tx)
if index <= self.rollup_context.current_index:
return apply_historical_transaction(tx)
raise Exception("Received future transaction")
def apply_historical_transaction(self, tx):
index = tx.meta.index
block = self.blockchain.get_block_at_index(index)
if block is None:
raise Exception("Missing historical block")
if block.txs[0] is not tx:
self.blockchain.reorg(tx.meta.index - 1)
return self.apply_transaction_to_tip(tx)
def apply_transaction_to_tip(self, tx):
if tx.meta.l1_timestamp is None:
tx.meta.l1_timestamp = self.eth_context.last_l1_timestamp
tx.meta.l1_block_number = self.l1_block_number
elif tx.meta.l1_timestamp > self.eth_context.last_l1_timestamp:
self.eth_context.last_l1_timestamp = tx.meta.l1_timestamp
self.l1_block_number = tx.meta.l1_block_number
elif tx.meta.l1_timestamp < self.eth_context.last_l1_timestamp:
raise Exception("Out of order timestamp!")
if tx.meta.index is None:
tx.meta.index = self.rollup_context.current_index + 1
self.rollup_context.current_index = tx.meta.index
if tx.meta.queue_index is not None:
self.current_queue_index = tx.meta.queue_index
return miner.apply_transaction(tx)
def apply_batched_transaction(self, tx):
assert tx.meta.index is not None
self.apply_indexed_transaction(tx)
self.rollup_context.current_batch_tx_index = tx.meta.index
def validate_and_apply_sequencer_transaction(self, tx):
if tx.meta.queue_origin != QueueOrigin.SEQUENCER:
raise Exception("Expected sequencer transactions only")
self.tx_pool.validate_tx(tx)
return apply_transaction(tx)
@abc.abstractmethod
def handle_eth_send_raw_transaction(self, tx):
return
```
##### `TransactionExecutor.apply_transaction`
The `tx` argument will have an index if it is synced from the canonical transaction chain or if it is synced from a replica.
##### `TransactionExecutor.set_initial_rollup_context`
This function is required for clean startups. It sets the `current_enqueue_index` based on the latest confirmed L1 to L2 transaction.
This allows the sequencer to continue ingesting `QueueOrigin.L1_TO_L2` transactions without skipping or double ingesting a transaction.
It also updates the `current_index` based on the tip of the chain if the indexed number is different. This is used because the
act of adding a block to the chain is asynchronous and sometimes the indices can be off after shutting down. Note that the index is
off by one relative to the block number due to the CTC being zero indexed while the node implementation is not zero indexed.
### Verifier
The `Verifier` is meant to execute transactions so that a fraud prover can observe the computed state
roots and the state roots posted to the State Commitement Chain on Layer One. The fraud prover can
submit a fraud proof when the state roots are observed to be different.
A verifier syncs from a data transport layer that is syncing from L1. It pulls in transactions that have been appended
to the canonical transaction chain and executes them. It does not need to manage timestamps as all transactions in the
canonical transaction chain have a timestamp already. It does not play enqueue transactions.
It syncs based on batches because that eliminates configuration confusion around
what the data transport layer is syncing. The batch indexes will only be present
in the data transport layer if it is syncing from layer one.
`Verifier.sync_transactions_to_tip` cannot give a guarantee on the source of the
transaction as it will return different results based on the configuration of
the data transport layer. It is much safer to sync based on batches to track the
verified transaction index.
```python
class Verifier(TransactionExecutor):
def __init__(self, options):
self.type = VERIFIER
self.backend = L1
super().set_initial_eth_context()
super().set_initial_rollup_context()
self.client = RollupClient(options.l1_transaction_indexer_url)
self.start()
def start():
while True:
self.sync_tx_batches_to_tip()
sleep self.poll_interval
def sync_transactions_to_tip(self):
latest_indexed_transaction = self.client.get_latest_transaction(self.backend)
latest_indexed_transaction_index = latest_indexed_transaction.meta.index
latest_local_transaction_index = self.rollup_context.index
while latest_indexed_transaction_index != latest_local_transaction_index:
for tx_index in range(latest_local_transaction_index, latest_indexed_transaction_index + 1):
tx = self.client.get_transaction(tx_index)
self.apply_transaction(tx)
latest_indexed_transaction = self.client.get_latest_transaction()
latest_indexed_transaction_index = latest_indexed_transaction.index
latest_local_transaction = self.rollup_context.index
def sync_tx_batches_to_tip(self):
latest_indexed_tx_batch = self.client.get_latest_tx_batch()
latest_indexed_tx_batch_index = tx_batch.batch.index
local_batch_index = self.rollup_context.current_tx_batch_index
while latest_indexed_tx_batch_index != local_batch_index:
for batch_index in range(local_batch_index, latest_indexed_tx_batch_index + 1)
tx_batch = self.client.get_tx_batch(batch_index)
for tx in tx_batch.transactions:
self.apply_batched_transaction(tx)
latest_indexed_tx_batch = self.client.get_latest_tx_batch()
latest_indexed_tx_batch_index = tx_batch.batch.index
def handle_eth_send_raw_transaction(self, tx):
raise Exception("Cannot accept transactions")
```
### Sequencer
The `Sequencer` holds a privileged role in the system as it is allowed to extend the chain. It holds the keys to the
EOAs in the Layer One AddressManager `OVM_Sequencer` and the `OVM_Proposer`. These roles are the only roles allowed to
extend the Canonical Transaction Chain and the State Commitment Chain. In the future, any account should be able to
call `queueBatchAppend` on the Canonical Transaction Chain to make appending to CTC permissionless.
Note that EOAs must be used to submit batches.
A sequencer syncs from a data transport layer that is syncing from L1. It pulls in enqueue transactions as soon as they are
enqueued and executes them. It must update its timestamps such that it can give a timestamp and blocknumber to queue origin
sequencer transactions that maintains monotonicity.
```python
class Sequencer(Verifier):
def __init__(self, options):
self.type = SEQUENCER
self.backend = L1
self.client = RollupClient(options.l1_transaction_indexer_url)
super().set_initial_eth_context()
super().set_initial_rollup_context()
super().sync_tx_batches_to_tip()
self.start()
def start():
while True:
self.sync_queue_to_tip()
self.sync_tx_batches_to_tip()
sleep options.poll_interval
def sync_queue_to_tip(self):
latest_indexed_queue_element = self.client.get_latest_enqueue()
latest_indexed_queue_element_index = latest_indexed_queue_element.meta.index
latest_local_queue_element_index = self.rollup_context.current_queue_index
while latest_indexed_queue_element_index != latest_local_queue_element:
for tx_index in range(latest_local_queue_element_index, latest_indexed_queue_element_index):
tx = self.client.get_queue_element(tx_index)
self.apply_transaction(tx)
latest_indexed_queue_element = self.client.get_latest_enqueue()
latest_indexed_queue_element_index = latest_indexed_queue_element.meta.index
latest_local_queue_element_index = self.rollup_context.current_queue_index
def handle_eth_send_raw_transaction(self, tx):
tx.set_timestamp(self.eth_context.last_l1_timestamp)
tx.set_blocknumber(self.eth_context.last_l1_blocknumber)
return validate_and_apply_sequencer_transaction(tx)
```
### Sequencer Replica
A sequencer replica syncs from a data transport layer that is syncing from a sequencer. When it receives a transaction via RPC,
it will ensure that it is at the tip of the replica data transport layer and then switch to syncing from a layer one data transport layer.
```python
class SequencerReplica(Sequencer):
def __init__(self, options):
self.type = SEQUENCER
self.backend = L2
self.client = RollupClient(options.replica_transaction_indexer_url)
super().set_initial_eth_context()
super().set_initial_rollup_context()
self.start()
def start():
while True:
if self.backend == L2:
self.sync_transactions_to_tip(self.backend)
elif self.backend == L1:
self.sync_queue_to_tip()
self.sync_tx_batches_to_tip()
sleep options.poll_interval
def handle_eth_send_raw_transaction(self, tx):
if self.backend == L2:
self.sync_transactions_to_tip(self.backend)
self.backend = L1
self.apply_transaction(tx)
```
### `VerifierReplica`
A verifier replica syncs from a data transport layer that is syncing from a sequencer. It will refuse to accept transactions via RPC
```python
class VerifierReplica(Verifier):
def __init__(self, options):
self.type = VERIFIER
self.backend = L2
self.client = RollupClient(options.replica_transaction_indexer_url)
super().set_initial_eth_context()
super().set_initial_rollup_context()
self.start()
def start():
while True:
self.sync_transactions_to_tip(self.backend)
sleep self.poll_interval
def handle_eth_send_raw_transaction(self, tx):
raise Exception("Cannot accept transactions")
```
## Sequencer Deployment
Deploy:
- Sequencer
- Layer One Data Transport Layer
## Sequencer Upgrades
Deploy new:
- Replica Sequencer
- Replica Data Transport Layer
The replica data transport layer is configured to sync from the sequencer. The replica sequencer is syncing from the replica data transport layer.
The DNS is updated to point `mainnet.optimism.io` to the replica sequencer. When the replica sequencer receives a transaction, it syncs to the
tip of the replica data transport layer, plays the transaction and then switches to syncing from the layer one data transport layer.
# Transaction Types
This defines the serialization of the transactions that are submitted to the
Canonical Transaction Chain.
Transaction types are defined by the leading byte which is used as an enum. The
purpose of a transaction type is to enable transaction compression as well as
allowing for new types to be defined that take advantage of the account
abstraction.
## EIP155
A compressed EIP155 transaction. The transaction that is signed follows
[EIP155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md). The type enum is `0`.
| Field | Size (bytes) |
| -------- | ------------ |
| Type | 1 |
| R | 32 |
| S | 32 |
| V | 1 |
| gasLimit | 3 |
| gasPrice | 3 |
| nonce | 3 |
| target | 20 |
| data | variable |
The `gasPrice` must be scaled by a factor of `1,000,000` when encoding and
decoding. This means that precision is lost and must be divisibl by 1 million.
From a user experience perspective, the `gasPrice` must be at least 1 gwei and
at most 16777215 gwei. A partial gwei will result in an invalid transaction.
The max nonce is 16777215. Any nonces greater than that must result in an
invalid transaction.
## EthSign
A compressed EIP155 transaction that uses an alternative signature hashing
algorithm. The data is ABI encoded hashed and then signed with `eth_sign`.
The type enum is `1`.
| Field | Size (bytes) |
| -------- | ------------ |
| Type | 1 |
| R | 32 |
| S | 32 |
| V | 1 |
| gasLimit | 3 |
| gasPrice | 3 |
| nonce | 3 |
| target | 20 |
| data | variable |
The same `gasPrice` and `nonce` rules apply as the `EIP155` transaction.
The following table shows how the fields are ABI encoded before hashing.
| Field | ABI Type |
| -------- | -------- |
| nonce | uint256 |
| gasLimit | uint256 |
| gasPrice | uint256 |
| chainId | uint256 |
| target | address |
| data | bytes |
The ABI encoded data is hashed with `keccak256` and then prepended with
`\x19Ethereum Signed Message:\n32` before being hashed again with `keccak256`
to create the digest that is signed with the secp256k1 private key.
{
"name": "@eth-optimism/specs",
"version": "0.3.0",
"license": "MIT",
"private": true,
"devDependencies": {
"@types/prettier": "^2.2.3",
"prettier": "^2.3.1",
"lint-staged": "11.0.0"
},
"scripts": {
"lint": "yarn lint:fix && yarn lint:check",
"lint:fix": "yarn prettier --write \"{l2geth,protocol}/**/*.md\"",
"lint:check": "yarn prettier --check \"{l2geth,protocol}/**/*.md\""
}
}
## Optimistic Ethereum Protocol Specification
This page serves as an annotated table of contents for the documents within the protocol specification.
## Contents
- **[Glossary](./glossary.md):** A list of definitions and abbreviations for commonly used terms.
- **[Data Structures](./data-structures.md):** All of the Data Structures found in the Protocol are listed here, along with descriptions and validation information.
- **[Actors and Roles](./actors-and-roles.md):** Describes the various actors within the system, and what actions they are permitted to take.
- **components:** These pages describe the correct functioning of key processes within the protocol.
- [Chains](./components/chains.md): Specification of the CTC and SCC.
- [Execution](./components/execution.md): Specification of the EM and SM.
- [Fraud Proving](./components/verification.md): Specification of the Fraud Proving process.
- [Cross Domain Messaging](./components/bridge.md): Specification of Cross Domain Messaging components.
- [Cross Domain Messaging](./components/predeploys.md): Specification of Cross Domain Messaging components.
# Actor and Roles
## Sequencer
The Sequencer is a semi-privileged service provider in Optimistic Ethereum which enables instant transactions. The sequencer is given the role of assigning an order to L2 transactions, similarly to miners on L1.
There is only one sequencer at a time, allowing consensus on transactions to be reached extremely rapidly. Users can send transactions to the sequencer, and (within seconds) receive confirmation that their transaction was processed and will be included in the next rollup batch. These instant confirmations have weaker security than confirmed L1 transactions, but stronger security than 0-conf L1 transactions.
Once the sequencer's batch is confirmed on L1, the security is the same.
### On censorship resistance
In the event that a malicious Sequencer censors user's transactions, the user SHOULD `enqueue()` their transactions directly to the L1 Queue, forcing the sequencer to include them in the L2 chain within the `FORCE_INCLUSION_PERIOD`.
If any transaction in the queue are more than `FORCE_INCLUSION_PERIOD` blocks old, those transactions MUST be added to the Chain before the protocol will allow the Sequencer to add any other transactions.
In the event that the Sequencer stops submitting transactions entirely, the protocol will allow users to add transactions to the CTC by calling `appendQueueBatch()`
### Roles
- Receives new transactions from users
- SHOULD process transactions instantly to determine an optimal ordering
- Determines the ordering of transactions in the CTC, which MUST follow the [constraints](./processes/chains.md#properties-enforced-by-appendsequencerbatch) imposed by the `appendSequencerBatch()` function.
- SHOULD append transactions from the CTC's `queue` within the "Force Inclusion Period".
## Proposers
Proposers evaluate the transactions in the CTC, and 'commit' to the resulting state by writing them to the SCC. They must deposit a bond for the privilege of this role.
This bond will be slashed in the event of a successful fraud proof on a state root committed by the Proposer.
### Roles
- Process transactions from the CTC, and propose new state roots by posting them to the SCC.
- MUST be collateralized by depositing a bond to the `BondManager` .
**Future note:** The Proposer is currently identical to the Sequencer. This is expected to change.
## Verifier
Like Proposers, Verifiers evaluate the transactions in the CTC, in order to determine the resulting state root following each transaction.
If a Verifier finds that a proposed state root is incorrect, they can prove fraud, and earn a reward taken from the Proposer's bond.
### Roles
- Read transactions from the CTC, process them, and verify the correctness of state roots in the SCC.
- If an invalid state root is detected: initiate and complete a fraud proof.
- Note that multiple accounts may contribute to a fraud proof and earn the reward.
## Users
Any account may transact on OE.
### Roles
- MAY post L2 transactions via the Sequencer's RPC endpoint, to be appended in sequencer batches
- MAY submit an L2 transaction via the CTC's queue on L1
- Can be used to circumvent censorship by the Sequencer
- Can be used to send a cross domain message from an L1 contract account.
# Accounts
This document is a WIP. It is kept here to maintain a consistent structure mirroring the [implementation directory](../../../contracts/contracts/).
<!--
### ProxyEOAs
- Created by call to ovmCREATEEOA with signature
- `ecrecover` is used to retrieve address and deploy the trusted ProxyEOA contract, which implements a simple DelegateCall pattern there.
### OVM_ECDSAContractAccount
The ECDSA Contract Account can be used as the implementation for a ProxyEOA deployed by the
ovmCREATEEOA operation. It enables backwards compatibility with Ethereum's Layer 1, by
providing EIP155 formatted transaction encodings.
#### Value Transfer
Value transfer is currently only supported in the first call frame of a transaction on L2. -->
# Cross Domain Messaging
This specification covers the sending and relaying of messages, either from L2 to L1, or L1 to L2.
A high-level description I find useful to summarize the difference between the two flows is that:
1. From L2 to L1, messages are validated by verifying the inclusion of the message data in a mapping in a contract on the L2 state.
2. From L1 to L2, messages are validated simply by checking that the `ovmL1TXORIGIN` matches the expected address
## Cross Domain Messengers Contracts (aka xDMs)
There are two 'low level' bridge contracts (the L1 and L2 Cross Domain Messengers), which are 'paired' in the sense that they reference each other's addresses in order to validate cross domain messages.
## L2 to L1 messaging flow
**Starting on L2:**
- Any account on L2 may call `L2CrossDomainMessenger.sendMessage()` with the information for the L1 message (aka `xDomainCalldata`)
- (ie. `_target`, `msg.sender`, `_message`)
- This data is hashed with the `messageNonce` storage variable, and the hash is store in the `sentMessages` mapping (this is not actually used AFAIK)
- The `messageNonce` is then incremented.
- The `L2CrossDomainMessenger` then passes the `xDomainCalldata` to `OVM_L2ToL1MessagePasser.passMessageToL1()`
- the `xDomainCalldata` is hashed with `msg.sender` (ie. `ovmCaller`), and written to the `sentMessages` mapping.
**Then on L1:**
- The `Relayer` (and currently only the `Relayer`) may call `L1CrossDomainMessenger.relayMessage()` providing the raw message inputs and an L2 inclusion proof.
- The validity of the message is confirmed by the following functions:
- `_verifyStateRootProof()`:
- checks that the fraud proof window has closed for the batch to which the transaction belongs.
- checks that the batch is stored in the `ChainStorageContainer`.
- `_verifyStorageProof()`:
- checks the proof to confirm that the message data provided is in the `OVM_L2ToL1MessagePasser.sentMessages` mapping
- checks that this transaction has not already been written to the `successfulMessages` mapping.
- The address of the L2 `ovmCALLER` is then written to the `xDomainMessageSender` state variable
- the call is then executed, allow the `target` to query the value of the `L1CrossDomainMessenger.xDomainMessageSender` for authorization.
- if it succeeds it is added to the `successfulMessages` and cannot be relayed again.
- regardless of success, an entry is written to the `relayedMessages` mapping.
**Then the receiver (ie. `SynthetixBridgeToOptimism`):**
- Checks that the caller is the `L1CrossDomainMessenger` and that the `xDomainMessageSender` is the `synthetixBridgeToBase` on L2.
## L1 to L2 messaging flow
**Starting on L1:**
- Any account may call the L1xDM's `sendMessage()`, specifying the details of the call that the L2xDM should make.
- The L1xDM call `enqueue` on the CTC to add to the Transaction Queue, with the L2xDM as the `target`.
- The [`Transaction.data`](../data-structures.md#transaction) field should be ABI encoded to call `L2CrossDomainMessenger.relayMessage()`.
**Then on L2:**
- A transaction will be sent to the `L2CrossDomainMessenger`.
- The cross-domain message is deemed valid if the `ovmL1TXORIGIN` is the `L1CrossDomainMessenger`.
- If not valid, execution reverts.
- If the message is valid, the arguments are ABI encoded and keccak256 hashed to `xDomainCalldataHash`.
- The `succesfulMessages` mapping is checked to verify that `xDomainCalldataHash` has not already been executed successfully.
- If an entry is found in `succesfulMessages` execution reverts.
- A check is done to disallow calls to the `OVM_L2ToL1MessagePasser`, which would allow an attacker to spoof a withdrawal.
- Execution reverts if the check fails.
- **Future note:** The `OVM_L2ToL1MessagePasser`, and this check should be removed, in favor of putting the `sentMessages` mapping into the L2xDM.
- The address of the L2 `ovmCALLER` is then written to the `xDomainMessageSender` state variable
- the call is then executed, allow the `target` to query the value of the `L1CrossDomainMessenger.xDomainMessageSender` for authorization.
- If it succeeds it is added to the `successfulMessages`.
# Chains
This document describes the Transaction Queue, the State Commitment Chain, the Canonical Transaction Chain.
- [Chains](#chains)
- [Transaction Queue](#transaction-queue)
- [Structure of the Transaction Queue](#structure-of-the-transaction-queue)
- [Transaction Queue Processes](#transaction-queue-processes)
- [Appending to the Transaction Queue](#appending-to-the-transaction-queue)
- [Canonical Transaction Chain](#canonical-transaction-chain)
- [Canonical Transaction Chain Structure](#canonical-transaction-chain-structure)
- [Verifying transaction inclusion in the CTC](#verifying-transaction-inclusion-in-the-ctc)
- [Updating the CTC](#updating-the-ctc)
- [Appending to the CTC by `appendSequencerBatch()`](#appending-to-the-ctc-by-appendsequencerbatch)
- [Transaction ordering requirements](#transaction-ordering-requirements)
- [Force Inclusion Period rules](#force-inclusion-period-rules)
- [Context properties](#context-properties)
- [Appending to the CTC by `appendQueueBatch()`](#appending-to-the-ctc-by-appendqueuebatch)
- [`appendQueueBatch()` requirements](#appendqueuebatch-requirements)
- [State Commitment Chain](#state-commitment-chain)
- [SCC Structure](#scc-structure)
- [Verifying state root inclusion in the SCC](#verifying-state-root-inclusion-in-the-scc)
- [Updating the SCC](#updating-the-scc)
- [Appending batches to the SCC](#appending-batches-to-the-scc)
- [`appendStateBatch()` requirements](#appendstatebatch-requirements)
- [Deleting batches from the SCC](#deleting-batches-from-the-scc)
- [`deleteStateBatch()` requirements](#deletestatebatch-requirements)
## Transaction Queue
The Transaction Queue is an append-only array of transaction data which MUST eventually be included in the Canonical Transaction Chain. The Transaction Queue itself does not describe the L2 state, but it serves as a source of transaction inputs to the CTC (the other source being the Sequencer).
## Structure of the Transaction Queue
Two `bytes32` entries are stored for each Queue transaction:
1. The first entry is `transactionHash` defined in solidity as:
```jsx
bytes32 transactionHash = keccak256(
abi.encode(
msg.sender,
_target,
_gasLimit,
_data
)
);
```
2. The second entry is `timestampAndBlockNumber`, which records the `TIMESTAMP` and `NUMBER` values in the EVM at write time. This entry is defined in solidity as:
```jsx
bytes32 timestampAndBlockNumber;
assembly {
timestampAndBlockNumber := timestamp()
timestampAndBlockNumber := or(timestampAndBlockNumber, shl(40, number()))
}
```
There is a [`QueueElement`](../data-structures.md#queueelement) type, which is also sometimes used to represent this data.
### Transaction Queue Processes
The Transaction Queue is append-only, thus the only allowed update operation is appending to it.
The Queue has two distinct purposes:
1. Resistance against a censorious Sequencer (see [Actors and Roles](../actors-and-roles.md)).
2. Sending messages (ie. deposits) from L1 to L2 (see [Cross Domain Messaging](./cross-domain-messaging.md)).
#### Appending to the Transaction Queue
Any account may append to the Transaction Queue by calling the CTC's `enqueue()` function:
```jsx
function enqueue(
address _target,
uint256 _gasLimit,
bytes _data
)
```
Where the parameters are:
- `address _target`: Target L2 contract to send the transaction to.
- `uint256 _gasLimit`: Gas limit for the enqueued L2 transaction.
- `bytes _data`: Arbitrary calldata for the enqueued L2 transaction.
## Canonical Transaction Chain
The Canonical Transaction Chain (CTC) is an append-only array of transactions which MUST be processed in-order to determine and verify the L2 state. The transaction data is compressed into batches to reduce storage costs.
Note: there are no blocks here, the CTC is just an ordered list of transactions.
### Canonical Transaction Chain Structure
Entries in the CTC are of type `bytes32 batchHeaderHash`, defined as the hash of the elements of a [`ChainBatchHeader`](../data-structures.md#chainbatchheader) (note that the `batchIndex` is not included).
```jsx
keccak256(
abi.encode(
_batchHeader.batchRoot,
_batchHeader.batchSize,
_batchHeader.prevTotalElements,
_batchHeader.extraData
)
);
```
Additionally, the CTC maintains its 'global context' in a `bytes27 latestBatchContext`, which encodes the fields of the [Extra Data](../data-structures#extra-data) structures as follows:
```jsx
bytes27 extraData;
assembly {
extraData := _totalElements
extraData := or(extraData, shl(40, _nextQueueIndex))
extraData := or(extraData, shl(80, _timestamp))
extraData := or(extraData, shl(120, _blockNumber))
extraData := shl(40, extraData)
}
```
### Verifying transaction inclusion in the CTC
The CTC's `verifyTransaction` function returns a boolean indicating whether a transaction is included in the chain:
```jsx
function verifyTransaction(
Transaction _transaction,
TransactionChainElement _txChainElement,
ChainBatchHeader _batchHeader,
ChainInclusionProof _inclusionProof
)
returns(
bool
)
```
Where the parameters are:
- `Transaction _transaction`: Transaction to verify.
- `TransactionChainElement _txChainElement`: Transaction chain element corresponding to the transaction.
- `ChainBatchHeader _batchHeader`: Header of the batch the transaction was included in.
- `ChainInclusionProof _inclusionProof`: Inclusion proof for the provided transaction chain element.
<!-- TODO: we should add a note somewhere in this section explaining the implicit structure underneath these batches, i.e. the actual leaves of the trees. -->
### Updating the CTC
The Transaction Queue is append-only, thus the only allowed update operation is appending to it. There are two methods by which this can be done.
1. `appendSequencerBatch()`
2. `appendQueueBatch()`
#### Appending to the CTC by `appendSequencerBatch()`
The Sequencer appends transactions to the chain in batches by calling the CTC's `appendSequencerBatch()` function, defined in solidity as:
```jsx
function appendSequencerBatch()
```
The data provided MUST conform to a custom encoding scheme (which is used for efficiency reasons). The scheme is described [here](../../l2-geth/transaction-indexer.md#transactions-via-appendsequencerbatch).
The `BatchContext` data provided by the Sequencer will be used to determine the ordering of transactions in the CTC:
- First `BatchContext.numSequencedTransactions` are added from the `_transactionDataFields`
- Then `BatchContext.numSubsequentQueueTransactions` are added from the `queue`.
This process is repeated until [`totalElements`](../data-structures.md#extra-data) transactions have been appended.
#### Transaction ordering requirements
The following constraints MUST be imposed on the ordering of Sequencer and Queue transactions (for the purpose of preventing a malicious Sequencer attempting to censor transaction):
##### Force Inclusion Period rules
The `Force Inclusion Period` is a storage variable defined in the CTC. It is expected to be on the order to 10 to 60 minutes.
1. If any queue elements are older than the Force Inclusion Period, they must be appended to the chain before any Sequencer transactions.
2. The Sequencer MUST not be able to insert Sequencer Transactions older than Force Inclusion Period.
##### Context properties
`BatchContext.blockNumber` and `BatchContext.timestamp` MUST be:
- monotonically increasing
- less than or equal to the L1 `blockNumber` and `timestamp` when `appendSequencerBatch()` is called
- less than or equal to the `blockNumber` and `timestamp` on all `QueueElements`
An important high level property which emerges from these rules is that Queue transactions will always have the same timestamp/blocknumber on L2 as the L1 block during which they were enqueued.
_Note: There may be some implicit requirements missing from here_.
#### Appending to the CTC by `appendQueueBatch()`
Any account MAY append transactions from the Queue to the CTC by calling `appendQueueBatch()`:
```jsx
function appendQueueBatch(
uint256 _numQueuedTransactions
)
```
Where the parameter is:
- `uint256 _numQueuedTransactions`: the number of transactions from the queue to be appended to the CTC.
##### `appendQueueBatch()` requirements
Transactions MUST have been added to the Queue earlier than `now - forceInclusionPeriod`. If any transactions are newer, `appendQueueBatch()` will revert.
## State Commitment Chain
The State Commitment Chain (SCC) is an array of StateCommitmentBatches, where each batch is a Merkle root derived from an array of state roots.
In practice the SCC should be append-only, but in the event of a successful fraud proof, the state may be rolled back to the most recent honest state.
### SCC Structure
The SCC is an array of [`ChainBatchHeader`s](../data-structures.md#chainbatchheader).
### Verifying state root inclusion in the SCC
The SCC's `verifyTransaction` function returns a boolean indicating whether a transaction is included in the chain:
```jsx
function verifyStateCommitment(
bytes32 _element,
ChainBatchHeader _batchHeader,
ChainInclusionProof _proof
)
returns (
bool _verified
);
```
Where the parameters are:
- `bytes32 _element`: Hash of the element to verify a proof for.
- `ChainBatchHeader _batchHeader`: Header of the batch in which the element was included.
- `ChainInclusionProof _proof`: Merkle inclusion proof for the element.
### Updating the SCC
#### Appending batches to the SCC
An account MAY act as a Proposer, call the SCC's `appendStateBatch()` function to commit to L2 state roots (if and only if it is approved by the Bond Manager contract).
_At this time, the Bond Manager will only approve the Sequencer. In the future this will be extended to any account which has deposited the required collateral._
```jsx
function appendStateBatch(
bytes32[] _batch,
uint256 _shouldStartAtElement
)
```
The logic of `appendStateBatch()` will compute the Merkle root of the batches and append it to the SCC.
##### `appendStateBatch()` requirements
- The batch may not be empty, at least one new state root must be added
- The `BondManager.isCollateralized(msg.sender)` must return `true`
- The resulting number of elements in the SCC (ie. all state commitments, not batches) must be less than or equal to the number of elements (ie. transactions) in the CTC.
#### Deleting batches from the SCC
In the event of a successful fraud proof, the Fraud Verifier contract may delete the State Batch which contains the fraudulent state root, by calling `deleteStateBatch()`:
```jsx
function deleteStateBatch(
ChainBatchHeader _batchHeader
)
```
Where the parameters are:
- `ChainBatchHeader _batchHeader`: The header of the state batch to delete.
Note that this may result in valid state commitments prior to the fraudulent state being deleted. This is OK, the next honest proposer can resubmit them.
##### `deleteStateBatch()` requirements
- The caller MUST be the Fraud Verifier contract
- The fraud proof window must not have passed at runtime
- The batch header must be valid
# Execution
The Optimistic Virtual Machine (OVM) provides a sandboxed execution environment built on the EVM, with the goal of guaranteeing deterministic execution which maintains agreement between L1 and L2.
## Security properties and invariants
- Deterministic execution; maintaining consensus of rollup state between L1 and L2.
- Corollary: L1 execution context MUST not be accessible (except in cases where the context can be guaranteed to agree with L2, ie. `GAS` is allowed, but must agree at all time during execution.
- Unsafe opcodes must not be deployable.
- It must be possible to complete the execution of a fraud proof within the L1 block gas limit.
- The execution context should be ephemeral, and not persist between calls to `run()`
- More precisely: although the Execution Manager does hold some permanent values in storage, those values should remain constant before and after each execution. The state root of the contract should be constant.
## Safety Checking
In order to maintain the property of Deterministic Execution, we consider the following opcodes unsafe, and MUST prevent them from being deployed on L2.
All currently unassigned opcodes which are not yet assigned in the EVM are also disallowed.
**Unsafe Opcodes**
- `ADDRESS`
- `BALANCE`
- `ORIGIN`
- `EXTCODESIZE`
- `EXTCODECOPY`
- `EXTCODEHASH`
- `BLOCKHASH`
- `COINBASE`
- `TIMESTAMP`
- `NUMBER`
- `DIFFICULTY`
- `GASLIMIT`
- `GASPRICE`
- `CREATE`
- `CREATE2`
- `CALLCODE`
- `DELEGATECALL`
- `STATICCALL`
- `SELFDESTRUCT`
- `SELFBALANCE`
- `SSTORE`
- `SLOAD`
- `CHAINID`
- `CALLER`\*
- `CALL`\*
- `REVERT`\*
\* The `CALLER`, `CALL`, and `REVERT` opcodes are also banned, except in the special case that they appear as part of one of the following "magic strings" of bytecode:
1. `CALLER PUSH1 0x00 SWAP1 GAS CALL PC PUSH1 0x0E ADD JUMPI RETURNDATASIZE PUSH1 0x00 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x00 REVERT JUMPDEST RETURNDATASIZE PUSH1 0x01 EQ ISZERO PC PUSH1 0x0a ADD JUMPI PUSH1 0x01 PUSH1 0x00 RETURN JUMPDEST`
2. `CALLER POP PUSH1 0x00 PUSH1 0x04 GAS CALL`
The first magic string MUST:
**TODO**
The second magic string does this:
## Defining the OVM Sandbox
The OVM Sandbox consists of:
- The Execution Manager
- The State Manager
- The Safety Cache and Safety Checker
- The
## Transaction lifecycle
[`TRANSACTION`s](./../data-structures.md#transaction) submitted to L2 (via Transaction Queue, Sequencer or other source) have a data-structure similar to the format of L1 Transactions.
### 1. Modify to ensure the Execution Manager is called first
All transactions begin as calls to the Execution Manager contract.
Thus clients (the Sequencer or Verifiers) MUST modify the `Transaction` with the following modifications:
1. Replace the `to` field with the Execution Manager’s address.
2. Encode the `data` field as arguments to `run()`.
```jsx
function run(
Transaction _transaction,
address _ovmStateManager
)
```
Where the parameters are:
- `Transaction _transaction`:
- `address _ovmStateManager`:
Importantly, the `Transaction` parameter includes all the contextual information (ie. `ovmTIMESTAMP`, `ovmBLOCKNUMBER`, `ovmL1QUEUEORIGIN`, `ovmL1TXORIGIN`, `ovmGASLIMIT`) which will be made available to the execution environment.
### 2. OVM Messages via the Sequencer Entrypoint
For **Sequencer transactions only**, the `Transaction.entrypoint` SHOULD be the Sequencer Entrypoint (or simply Entrypoint) contract. In order to achieve this, the Sequencer MUST modify the transaction's `to` field to the address of the Entrypoint.
The Entrypoint contract accepts a more efficient compressed calldata format. This is done at a low level, using the contract's fallback function, which expects an RLP-encoded EIP155 transaction as input.
The Entrypoint then:
- decodes the input to extract the hash, and signature of [`EIP155Transaction`](#eip155transaction).
- calculates an address using `ecrecover()`
- checks for the existence of a contract at that address
- calls `ovmCREATEEOA` to ensure the necessary 'Account' contract exists.
- initiates an `ovmCALL` to the 'Account' contract's `execute()` function.
### 3. Execution Proceeds within the Sandbox
Given the guarantees provided by the SafetyChecker contract, henceforth all calls to overridden opcodes will be routed through the Execution Manager.
## Exception handling within the OVM
It is critical to handle different exceptions properly during execution.
### Invalid transactions
If a transaction (or more generally a call to `run()`) is 'invalid', the Execution Manager's run function should `RETURN` prior to initiating the first `ovmCALL`.
Invalid calls to to run include calls which:
- don't change the context from its default values
- have a `_gasLimit` outside the minimum and maximum transaction gas limits
### Revert
Refer to Data Structures spec for a description of [`RevertFlag`](./../data-structures.md#revertflag-enum) enum fields.
## Gas Considerations
### Epoch limitations
The OVM does not have blocks, it just maintains an ordered list of transactions. Because of this, there is no notion of a block gas limit; instead, the overall gas consumption is rate limited based on time segments, called epochs8. Before a transaction is executed, there’s a check to see if a new epoch needs to be started, and after execution its gas consumption is added on the cumulative gas used for that epoch. There is a separate gas limit per epoch for sequencer submitted transactions and “L1 to L2” transactions. Any transactions exceeding the gas limit for an epoch return early. This implies that an operator can post several transactions with varying timestamps in one on-chain batch (timestamps are defined by the sequencer, with some restrictions which we explain in the “Data Availability Batches” section).
### GAS Metering
Notably, the `GAS` opcode is not disallowed or overridden. This enables us to use the EVM's built in gas metering, but also creates an attack vector in the case that fees diverge on L1 and L2.
An important property to maintain is that the amount of gas passed to `run()`'s first `ovmCALL` is deterministic, and that within that call-frame, the `GAS` value remains deterministic. Assuming L2 geth is in consensus with L1 geth, and no _other_ L1 context is exposed to the OVM, this property should follow.
### Nuisance Gas
Nuisance-gas is used to enforce an upper-bound on the net gas cost of fraud proofs, by charging a fee for any operation that increases the number of accounts and storage slots which require proving in either the pre or post-state.
Nuisance-gas is initialized in `run()` to be equal to the transaction's `gasLimit`, but from then on the two values are treated independently.
A nuisance gas fee is charged on the following OVM operations the first time they occur:
- a new account is loaded
- the base fee is `MIN_NUISANCE_GAS_PER_CONTRACT = 30000`
- the variable fee is `NUISANCE_GAS_PER_CONTRACT_BYTE = 100`
- a new storage slot is read from
- the fee is `NUISANCE_GAS_SLOAD = 20000`
- a new storage slot is written to
- the fee is `NUISANCE_GAS_SSTORE = 20000`
If a message tries to use more nuisance gas than allowed in the message’s context, execution reverts.
### OVM_DeployerWhitelist
The Deployer Whitelist is a temporary predeploy used to provide additional safety during the initial phases of our mainnet roll out. It is owned by the Optimism team, and defines accounts which are allowed to deploy contracts on Layer2. The Execution Manager will only allow an ovmCREATE or ovmCREATE2 operation to proceed if the deployer's address whitelisted.
### OVM_ETH
The ETH predeploy provides an ERC20 interface for ETH deposited to Layer 2. Note that unlike on Layer 1, Layer 2 accounts do not have a balance field.
### OVM_L1MessageSender
The L1MessageSender is a predeploy contract running on L2. During the execution of cross-domain transaction from L1 to L2, it returns the address of the L1 account (either an EOA or contract) which sent the message to L2 via the Canonical Transaction Chain's `enqueue()` function.
This contract exclusively serves as a getter for the `ovmL1TXORIGIN` operation. This is necessary because there is no corresponding operation in the EVM which the the optimistic solidity compiler can be replaced with a call to the ExecutionManager's `ovmL1TXORIGIN()` function.
### OVM_L2ToL1MessagePasser
The L2 to L1 Message Passer is a utility contract which facilitate an L1 proof of the
of a message on L2. The L1 Cross Domain Messenger performs this proof in its
\_verifyStorageProof function, which verifies the existence of the transaction hash in this
contract's `sentMessages` mapping.
### OVM_ProxySequencerEntrypoint
The Proxy Sequencer Entrypoint is a predeployed proxy to the implementation of the
Sequencer Entrypoint. This will enable the Optimism team to upgrade the Sequencer Entrypoint
contract.
### OVM_SequencerEntrypoint
It accepts a more efficient compressed calldata format, which it decompresses and encodes to the standard EIP155 transaction format.
This contract is the implementation referenced by the Proxy Sequencer Entrypoint, thus enabling the Optimism team to upgrade the decompression of calldata from the Sequencer.
### ERC1820Registry
This contract has been included as a popular standard which MUST be deployed at a specific address using CREATE2. This is not achievable in the OVM as the bytecode will not be a perfect match.
See EIP-1820 for more information.
# Fraud Proving
This document is a WIP. It is kept here to maintain a consistent structure mirroring the [implementation directory](../../../contracts/contracts/).
# Optimistic Ethereum Data Structures
For convenience, the data structures are currently organized according to the solidity file in which they are defined. We may wish to relocate them.
- [Optimistic Ethereum Data Structures](#optimistic-ethereum-data-structures)
- [Chain](#chain)
- [TransactionChainElement](#transactionchainelement)
- [QueueElement](#queueelement)
- [ChainBatchHeader](#chainbatchheader)
- [Extra Data](#extra-data)
- [BatchContext](#batchcontext)
- [ChainInclusionProof](#chaininclusionproof)
- [Accounts](#accounts)
- [Address](#address)
- [EVMAccount](#evmaccount)
- [Account](#account)
- [EOASignatureType](#eoasignaturetype)
- [EIP155Transaction](#eip155transaction)
- [Execution](#execution)
- [Transaction](#transaction)
- [QueueOrigin](#queueorigin)
- [RevertFlag (enum)](#revertflag-enum)
- [GasMetadataKey (enum)](#gasmetadatakey-enum)
- [GasMeterConfig](#gasmeterconfig)
- [GlobalContext](#globalcontext)
- [TransactionContext](#transactioncontext)
- [TransactionRecord](#transactionrecord)
- [MessageContext](#messagecontext)
- [MessageRecord](#messagerecord)
- [Bridge](#bridge)
- [L2MessageInclusionProof](#l2messageinclusionproof)
- [L2ToL1Message](#l2tol1message)
- [Verification](#verification)
- [ItemState (enum)](#itemstate-enum)
- [TransitionPhase (enum)](#transitionphase-enum)
- [Misc](#misc)
- [RLPItem](#rlpitem)
- [RLPItemType (enum)](#rlpitemtype-enum)
## Chain
### TransactionChainElement
The elements which are added (in batches) in the Canonical Transaction Chain. One per transaction.
| Name | Type | Description | Validation | Notes |
| ----------- | ------- | --------------------------------------------------------------------- | ------------------------------------------------------ | ------------------------------------ |
| isSequenced | bool | The transaction was included by the Sequencer (rather than enqueued). | \* | \*️⃣ Redundant with QueueOrigin enum. |
| queueIndex | uint256 | The index of the transaction in the queue. | 0 for Sequenced Transactions. | |
| timestamp | uint256 | L1 timestamp at which the transaction was included in the chain. | Monotonically increasing. Equivalent within a context. | Description needs confirmation |
| blockNumber | uint256 | L1 block number at which the transaction was included in the chain. | Monotonically increasing. Equivalent within a context. | Description needs confirmation |
| txData | bytes | Transaction data | | |
Refer to "[Verifying transaction inclusion in the CTC](#verifying-transaction-inclusion-in-the-ctc)" for details on its usage.
**Note:** this structure is tightly coupled with the [Transaction](#transaction) type, and should perhaps be combined or otherwise refactored.
### QueueElement
Elements in the Queue.
| Name | Type | Description | Validation | Notes |
| --------------- | ------- | ------------------------------------------------------------------------------------------------------------------------ | ---------- | ------------------------------------------------------- |
| transactionHash | bytes32 | keccak256( abi.encode( \_transaction.l1TxOrigin, \_transaction.entrypoint, \_transaction.gasLimit, \_transaction.data )) | | \*️⃣ As formulated, there will definitely be collisions. |
| timestamp | uint40 | timestamp at the time of enqueue | | |
| blockNumber | uint40 | block number at the time of enqueue | | |
### ChainBatchHeader
The chain itself is compressed into a sequence of batches. `ChainBatchHeader`s contain information related to a batch of transactions.
This structure is used in both the CTC and SCC.
| Name | Type | Description | Validation |
| ----------------- | ---------------- | -------------------------------------------------------------------- | --------------------------------- |
| batchIndex | uint256 | Index of a given batch in the array of batches. | MUST be monotonically increasing. |
| batchRoot | bytes32 | Root node of a Merkle Tree encoding the elements within a batch. | \* |
| batchSize | uint256 | Number of transactions in the batch. | \* |
| prevTotalElements | uint256 | Total number of transactions submitted prior to this batch. | \* |
| extraData | bytes27 / bytes? | Provides additional context data for a batch (see `ExtraData` below) | \* |
### Extra Data
Additional context for use in the execution environment.
- (\*️⃣ This structure is not explicitly defined as a struct in solidity. It's also represented as both bytes and bytes27 in different places.)
- (\*️⃣ Consider renaming `extraData` to something more descriptive such as `CanonicalContext` or `CanonicalBatchContext`.
| Name | Type | Description | Validation |
| -------------- | ------ | ----------------------------------------------------------------------------- | --------------------------------- |
| totalElements | uint40 | Sum total number of transactions submitted prior to AND including this batch. | |
| nextQueueIndex | uint40 | Index of the queue element to process in the next batch. | |
| timestamp | uint40 | The timestamp of the _last context_ in the `ChainBatchHeader`. | MUST increase monotonically. |
| blockNumber | uint40 | The blockNumber of the _last context_ in the `ChainBatchHeader`. | MUST increase monotonically by 1. |
### BatchContext
The Sequencer calls `appendSequencerBatch()` with an array of `BatchContext` structs. The CTC contract uses the `BatchContext` to determine the ordering of transactions to append to the chain, and provides the timestamp and blocknumber which will be provided in the transaction context during execution.
The nomenclature is slightly confusing here, because each "Batch" contains multiple `BatchContexts`.
Additional context for use in the execution environment, and for enforcing monotonicity of contexts.
The difference or lack thereof of this and the previous structure needs to be elaborated.
| Name | Type | Description | Validation |
| ------------------------------ | ------- | ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------- |
| numSequencedTransactions | uint256 | Number of transactions to append to the CTC from calldata provided by the Sequencer. | \* (Zero is valid and will result in appending only enqueued transactions) |
| numSubsequentQueueTransactions | uint256 | Number of transactions to append | \* (Zero is valid and will result in appending only Sequencer transactions) |
| timestamp | uint256 | Timestamp of the transaction. | Must be strictly increasing. |
| blockNumber | uint256 | Block Number of the transaction. | Must be strictly increasing. |
### ChainInclusionProof
(\*️⃣ I noticed the proof is decomposed before passing to Lib_MerkleTree.verify(). It might be cleaner to pass the struct and decompose in the lib.)
Data required to verify the inclusion of a transaction (leaf) in the Merkle Tree defined by `ChainBatchHeader.batchRoot`.
| Name | Type | Description | Validation |
| -------- | --------- | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
| index | uint256 | The index in the tree of the leaf for which inclusion is being proven. | Must be less than total number of leaves in the tree. |
| siblings | bytes32[] | Array of sibling nodes in the inclusion proof, starting from depth 0 (bottom of the tree). | Length must be greater than 0. Length must be equal to the integer ceiling of log2(x), where x is the total number of leaves in the tree. |
## Accounts
### Address
Address is a type alias for bytes20.
### EVMAccount
An Ethereum (Layer 1) account.
| Name | Type | Description | Validation |
| ----------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
| nonce | uint256 | A one time use value used to prevent collisions during contract creation. Note that because all accounts are 'abstract', the protocol does not natively prevent nonce reuse. Nonce validation must be handled by the contract account implementation (see OVM_ECDSAContractAccount) | Must increase monotonically. |
| balance | uint256 | The native ETH balance in wei | \* |
| storageRoot | bytes32 | Root node of a Merkle Patricia Tree encoding the storage contents of the account. | \* |
| storageRoot | bytes32 | Keccak-256 hash of the bytecode of the account | \* |
### Account
An Optimistic Ethereum account (L2). Similar to an EVM account with some additional fields.
| Name | Type | Description | Validation |
| ----------- | -------------- | -------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| nonce | uint256 | See EVMAccount.nonce | MUST be initialized at 1. MUST increase monotonically by 1. |
| balance | uint256 | See EVMAccount.balance | MUST be equal to 0. |
| storageRoot | bytes32 | See EVMAccount.storageRoot | \* |
| codeHash | bytes32 | See EVMAccount.codeHash | \* |
| ethAddress | address (link) | The layer-1 Ethereum address associated with an Optimistic Ethereum Account | \* |
| isFresh | bool | Indicates that an account will or has been created during the course of Fraud Proving. | \* |
### EOASignatureType
An enum indicating the type of a ECDSA signature.
| Name | Description |
| ------------------ | ------------------------------------------------------------------------------ |
| EIP155_TRANSACTION | Indicates that the signed message includes replay protection based on EIP-155. |
| ETH_SIGNED_MESSAGE | Indicates that the signed message does not include replay protection. |
### EIP155Transaction
<!-- todo update to EIP155Tx -->
Encoding used in the `OVM_ECDSAContractAccount` and `OVM_SequencerEntrypoint` (though seems to have been removed on the `OZ` branch).
| Name | Type | Description | Validation |
| -------- | ------- | ----------- | ---------- |
| nonce | uint256 | | |
| gasPrice | uint256 | | |
| gasLimit | uint256 | | |
| to | address | | |
| value | uint256 | | |
| data | bytes | | |
| chainId | uint256 | | |
## Execution
### Transaction
Optimistic Ethereum transaction data.
| Name | Type | Description | Validation | Notes |
| ------------- | ----------- | ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- | ----- |
| timestamp | uint256 | Timestamp of the transaction. | MUST be identical across transactions with a context, and monotonically increasing across contexts. | |
| blockNumber | uint256 | Block Number of the transaction. | MUST be identical across transactions with a context, and monotonically increasing across contexts. | |
| l1QueueOrigin | QueueOrigin | See QueueOrigin below. | \* | |
| l1TxOrigin | address | The EOA or contract address on L1 which called the CTC to enqueue the transaction. | \* | |
| entrypoint | address | The address to call within the ExecutionManager's run() function. | \* | |
| gasLimit | uint256 | The gas limit (minus the minTransactionGasLimit) to pass to the call within the ExecutionManager's run() function. | MUST be > gasMeterConfig.minTransactionGasLimit MUST be < gasMeterConfig.maxTransactionGasLimit | |
| data | bytes | Transaction input data | \* | |
### QueueOrigin
Enum indicating whether a transaction on L2 was provided by the Sequencer or the Queue.
| Name | Description |
| --------------- | --------------------------------------------------- |
| SEQUENCER_QUEUE | The transaction was included by the Sequencer |
| L1TOL2_QUEUE | The transaction was enqueued via an L1 transaction. |
### RevertFlag (enum)
Enum encoding the reason for reverting during execution in the OVM.
| Name | Description |
| -------------------- | ----------- |
| OUT_OF_GAS | |
| INTENTIONAL_REVERT | |
| EXCEEDS_NUISANCE_GAS | |
| INVALID_STATE_ACCESS | |
| UNSAFE_BYTECODE | |
| CREATE_COLLISION | |
| STATIC_VIOLATION | |
| CREATOR_NOT_ALLOWED | |
### GasMetadataKey (enum)
Predefined keys for values to look up in storage of the contract act `GAS_METADATA_ADDRESS`.
| Name | Description |
| ------------------------------ | ----------- |
| CURRENT_EPOCH_START_TIMESTAMP | |
| CUMULATIVE_SEQUENCER_QUEUE_GAS | |
| CUMULATIVE_L1TOL2_QUEUE_GAS | |
| PREV_EPOCH_SEQUENCER_QUEUE_GAS | |
| PREV_EPOCH_L1TOL2_QUEUE_GAS | |
### GasMeterConfig
Constraints for gas metering during execution.
| Name | Type | Description | Validation |
| ---------------------- | ------- | ----------- | ---------------------------------------- |
| minTransactionGasLimit | uint256 | | Must be less than maxTransactionGasLimit |
| maxTransactionGasLimit | uint256 | | \* |
| maxGasPerQueuePerEpoch | uint256 | | \* |
| secondsPerEpoch | uint256 | | |
### GlobalContext
Context which remains consistent across all transactions and messages.
| Name | Type | Description | Validation |
| ---------- | ------- | ----------- | ---------- |
| ovmCHAINID | uint256 | 10 | MUST be 10 |
### TransactionContext
Context which remains consistent within a transaction.
| Name | Type | Description | Validation |
| ---------------- | ----------- | ----------- | ---------- |
| ovmL1QUEUEORIGIN | QueueOrigin | | |
| ovmTIMESTAMP | uint256 | | |
| ovmNUMBER | uint256 | | |
| ovmGASLIMIT | uint256 | | |
| ovmTXGASLIMIT | uint256 | | |
| ovmL1TXORIGIN | address | | |
### TransactionRecord
A record of the result of transaction execution.
| Name | Type | Description | Validation |
| ------------ | ------- | ----------- | ---------- |
| ovmGasRefund | uint256 | | |
### MessageContext
Context pertaining to a given message within the OVM.
| Name | Type | Description | Validation |
| ---------- | ------- | ----------- | ---------- |
| ovmCALLER | address | | |
| ovmADDRESS | address | | |
| isStatic | bool | | |
### MessageRecord
A record of the result of message execution.
| Name | Type | Description | Validation |
| --------------- | ------- | -------------------------- | ---------- |
| nuisanceGasLeft | uint256 | The nuisance gas remaining | |
## Bridge
### L2MessageInclusionProof
Data required to verify the inclusion of an element in the SecureMerkleTrie which encodes the L2 state.
| Name | Type | Description | Validation | Notes |
| -------------------- | ------------------- | ----------- | ---------- | ----- |
| stateRoot | bytes32 | | | |
| stateRootBatchHeader | ChainBatchHeader | | | |
| stateRootProof | ChainInclusionProof | | | |
| stateTrieWitness | bytes | | | |
| storageTrieWitness | bytes | | | |
### L2ToL1Message
Data used to send a message from L2 to and relay it on L1.
| Name | Type | Description | Validation |
| ------------ | ----------------------- | --------------------- | ---------- |
| target | address | Address to call on L1 | |
| sender | address | Sender address | |
| message | bytes | | |
| messageNonce | uint256 | | |
| proof | L2MessageInclusionProof | | |
## Verification
### ItemState (enum)
Possible states of both accounts and storage slots. Used for metering nuisance gas during execution for a fraud proof.
| Name | Description |
| --------------- | ----------- |
| ITEM_UNTOUCHED, | |
| ITEM_LOADED, | |
| ITEM_CHANGED, | |
| ITEM_COMMITTED | |
### TransitionPhase (enum)
Distinct phases in the lifecycle of a fraud proof.
| Name | Description |
| -------------- | ----------- |
| PRE_EXECUTION | |
| POST_EXECUTION | |
| COMPLETE | |
## RLP Structures
### RLPItem
| Name | Type | Description | Validation |
| ------ | ---- | ----------- | ---------- |
| length | uint | | |
| ptr | uint | | |
### RLPItemType (enum)
| Name | Description |
| ---------- | ----------- |
| DATA_ITEM, | |
| LIST_ITEM | |
# Glossary
The following definitions are intended only to disambiguate some of the terms and abbreviations specific to Optimistic Ethereum protocol. They are intentionally kept incomplete, as they are described in more detail elsewhere within this specification.
## General concepts
- **Optimistic Rollup (ORU):** A design pattern using fraud proofs to enforce security assumptions on a layer 2 blockchain.
- **Optimistic Ethereum (OE):** Refers to the protocol described within this specification. An Optimistic Rollup implementation
- **Optimistic Virtual Machine (OVM):** A 'containerized' virtual machine designed run on the Ethereum Virtual Machine (EVM), and mirror the functionality and behavior of the EVM.
- **Domain:** A synchronous execution environment. Typically either the Ethereum Mainnet or Optimistic Ethereum.
- **Layer 1 (L1):** Typically refers to the Ethereum Mainnet. More generally the base chain which provides security to L2.
- **Layer 2 (L2):** Typically refers to the Optimistic Ethereum Rollup. More generally the chain which depends on L1 for security.
## OE System Components
- **Canonical Transaction Chain (CTC):** The chain of transactions executed on the Rollup chain.
- **State Commitment Chain (SCC):** The chain of state roots resulting from the execution of each transaction.
- **State Transitioner (ST):** During a fraud proof, this contract manages the setup of the prestate, execution of the transaction, and computation of the poststate.
- **Execution Manager (EM)**: The contract defining the OVM operations necessary to override EVM operations.
- **State Manager (SM):** The contract which manages a remapping of L2 to L1 addresses.
- **Fraud Prover:** The contract used to initiate and adjudicate a fraud proof.
- **Bond Manager:** The contract which accepts the collateral deposit required to act as a state proposer.
- **Cross Domain Messenger (xDM):** The pair of contracts used to pass messages between L1 and L2. These contracts are sometimes referred to as the L1xDM, and L2xDM.
## Implementation Specific Concepts
- **The Queue** / **Enqueued Transactions:** The Queue is an append only list of transaction on layer 1. Enqueued transactions must be added to the L2 chain within the Force Inclusion Period.
- **Force Inclusion Period:** The duration of time in which the Sequencer may still insert other transactions before an Enqueued Transaction is added to the CTC.
- **Stale transactions:** Enqueued transactions which have not yet been added to the Canonical Transaction Chain.
- **Batching:** The act of 'rolling up' or Merkleizing data for efficient on-chain storage, while making the data available in order to prove properties of the L2 state.
- **Safe/Unsafe opcodes:** Unsafe opcodes are those which would return a different value when executed on L1 or L2, and thus invalidate the property of deterministic execution.
- **Magic Strings:** The specific bytecode strings which are allowable despite containing unsafe opcodes, which force contracts to call to the Execution Manager.
- **Source** or **Feed:** The origin of a transaction, currently either the Sequencer, or Transaction Queue.
......@@ -2984,7 +2984,7 @@
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.19.1.tgz#33509849f8e679e4add158959fdb086440e9553f"
integrity sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ==
"@types/prettier@^2.1.1", "@types/prettier@^2.2.3":
"@types/prettier@^2.1.1":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.3.2.tgz#fc8c2825e4ed2142473b4a81064e6e081463d1b3"
integrity sha512-eI5Yrz3Qv4KPUa/nSIAi0h+qX0XyewOliug5F2QAtuRg6Kjg6jfmxe1GIwoIRhZspD1A0RP8ANrPwvEXXtRFog==
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment