Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(permissionless batches): batch production toolkit and operator recovery #1555

Draft
wants to merge 26 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
dca69ce
implement first steps for minimal recovery to permissionlessly produc…
jonastheis Oct 23, 2024
2e09118
Merge remote-tracking branch 'origin/develop' into jt/permissionless-…
jonastheis Oct 24, 2024
496314f
add config for recovery mode
jonastheis Oct 24, 2024
c12d380
structure code and implement restoreMinimalPreviousState and fetchL2B…
jonastheis Oct 24, 2024
c329959
produce chunks from specified L2 blocks and batch from chunks
jonastheis Oct 24, 2024
71f240b
start implementation of restoring full previous state for relayer
jonastheis Oct 25, 2024
acc7083
implement processFinalizedBatch
jonastheis Oct 28, 2024
59ea991
handle batches
jonastheis Oct 29, 2024
2df07a9
introduce ForceL1MessageCount to config to be able to set a custom L1…
jonastheis Nov 6, 2024
0f5ebf3
add Dockerfile for relayer in permissionless batches mode
jonastheis Nov 6, 2024
f96af8e
add docker compose file to spin up all necessary services together
jonastheis Nov 6, 2024
16a471d
move docker file to build/dockerfiles
jonastheis Nov 19, 2024
d25094b
add coordinator-cron and proving-service-bundle
jonastheis Nov 19, 2024
603feed
add bundle creation
jonastheis Nov 19, 2024
5ddd6d6
add permissionless-batches/conf/ to dockerignore files
jonastheis Nov 20, 2024
5ff6fd0
refactor cmd/permissionless_batches/app
jonastheis Nov 20, 2024
0108873
implement RecoveryNeeded functionality to allow re-running without ov…
jonastheis Nov 20, 2024
a6f914a
refactor cmd/rollup_relayer/app and split into FullRecovery struct
jonastheis Nov 20, 2024
0123502
clean up
jonastheis Nov 21, 2024
596d9fe
introduce profiles to docker-compose.yml
jonastheis Nov 21, 2024
d85bdf5
initial instructions in README.md
jonastheis Nov 21, 2024
a9eac08
address review comments
jonastheis Nov 22, 2024
606162e
add dummy configuration and documentation for permissionless batch pr…
jonastheis Nov 25, 2024
30c0201
ignore /conf folder from git
jonastheis Nov 25, 2024
ec9d862
add documentation for operator recovery
jonastheis Nov 26, 2024
6ef4775
address review comments
jonastheis Nov 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build/dockerfiles/coordinator-api.Dockerfile.dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ docs/
l2geth/
rpc-gateway/
*target/*

permissionless-batches/conf/
2 changes: 2 additions & 0 deletions build/dockerfiles/coordinator-cron.Dockerfile.dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ docs/
l2geth/
rpc-gateway/
*target/*

permissionless-batches/conf/
2 changes: 2 additions & 0 deletions build/dockerfiles/db_cli.Dockerfile.dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ docs/
l2geth/
rpc-gateway/
*target/*

permissionless-batches/conf/
5 changes: 4 additions & 1 deletion build/dockerfiles/gas_oracle.Dockerfile.dockerignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
assets/
contracts/
docs/
l2geth/
rpc-gateway/
*target/*
*target/*

permissionless-batches/conf/
30 changes: 30 additions & 0 deletions build/dockerfiles/recovery_permissionless_batches.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Download Go dependencies
FROM scrolltech/go-rust-builder:go-1.21-rust-nightly-2023-12-03 as base

WORKDIR /src
COPY go.work* ./
COPY ./rollup/go.* ./rollup/
COPY ./common/go.* ./common/
COPY ./coordinator/go.* ./coordinator/
COPY ./database/go.* ./database/
COPY ./tests/integration-test/go.* ./tests/integration-test/
COPY ./bridge-history-api/go.* ./bridge-history-api/
RUN go mod download -x

# Build rollup_relayer
FROM base as builder

RUN --mount=target=. \
--mount=type=cache,target=/root/.cache/go-build \
cd /src/rollup/cmd/permissionless_batches/ && CGO_LDFLAGS="-ldl" go build -v -p 4 -o /bin/rollup_relayer

# Pull rollup_relayer into a second stage deploy ubuntu container
FROM ubuntu:20.04

RUN apt update && apt install vim netcat-openbsd net-tools curl ca-certificates -y

ENV CGO_LDFLAGS="-ldl"

COPY --from=builder /bin/rollup_relayer /bin/
WORKDIR /app
ENTRYPOINT ["rollup_relayer"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
assets/
contracts/
docs/
l2geth/
rpc-gateway/
*target/*

permissionless-batches/conf/
5 changes: 4 additions & 1 deletion build/dockerfiles/rollup_relayer.Dockerfile.dockerignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
assets/
contracts/
docs/
l2geth/
rpc-gateway/
*target/*
*target/*

permissionless-batches/conf/
1 change: 1 addition & 0 deletions go.work
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ use (
./database
./rollup
./tests/integration-test
//../go-ethereum
)
249 changes: 244 additions & 5 deletions go.work.sum

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions permissionless-batches/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
conf/
161 changes: 161 additions & 0 deletions permissionless-batches/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# Permissionless Batches
Permissionless batches aka enforced batches is a feature that provides guarantee to users that they can exit Scroll even if the operator is down or censoring.
It allows anyone to take over and submit a batch (permissionless batch submission) together with a proof after a certain time period has passed without a batch being finalized on L1.

Once permissionless batch mode is activated, the operator can no longer submit batches in a permissioned way. Only the security council can deactivate permissionless batch mode and reinstate the operator as the only batch submitter.
There are two types of situations to consider:
- `Permissionless batch mode is activated:` This means that finalization halted for some time. Now anyone can submit batches utilizing the [batch production toolkit](#batch-production-toolkit).
- `Permissionless batch mode is deactivated:` This means that the security council has decided to reinstate the operator as the only batch submitter. The operator needs to [recover](#operator-recovery) the sequencer and relayer to resume batch submission and the valid L2 chain.


## Batch production toolkit
The batch production toolkit is a set of tools that allow anyone to submit a batch in permissionless mode. It consists of three main components:
1. l2geth state recovery from L1
2. l2geth block production
3. production, proving and submission of batch with `docker-compose.yml`

### Prerequisites
- Unix-like OS, 32GB RAM
- Docker
- [l2geth](https://github.com/scroll-tech/go-ethereum/) or [Docker image](https://hub.docker.com/r/scrolltech/l2geth) of corresponding version [TODO link list with versions](#batch-production-toolkit).
- access to an Ethereum L1 RPC node (beacon node and execution client)
- ability to run a prover or access to a proving service (e.g. Sindri)
- L1 account with funds to pay for the batch submission

### 1. l2geth state recovery from L1
Once permissionless mode is activated there's no blocks being produced and propagated on L2. The first step is to recover the latest state of the L2 chain from L1. This is done by running l2geth in recovery mode.
More information about l2geth recovery (aka L1 follower mode) can be found [here TODO: put correct link once released](https://github.com/scroll-tech/scroll-documentation/pull/374).

Running l2geth in recovery mode requires following configuration:
- `--scroll` or `--scroll-sepolia` - enables Scroll Mainnet or Sepolia mode
- `--da.blob.beaconnode` - L1 RPC beacon node
- `--l1.endpoint` - L1 RPC execution client
- `--da.sync=true` - enables syncing with L1
- `--da.recovery` - enables recovery mode
- `--da.recovery.initiall1block` - initial L1 block (commit tx of initial batch)
- `--da.recovery.initialbatch` - batch where to start recovery from. Can be found on [Scrollscan Explorer](https://scrollscan.com/batches).
- `--da.recovery.l2endblock` - until which L2 block recovery should run (optional)

```bash
./build/bin/geth --scroll<-sepolia> \
--datadir "tmp/datadir" \
--gcmode archive \
--http --http.addr "0.0.0.0" --http.port 8545 --http.api "eth,net,web3,debug,scroll" --http.vhosts "*" \
--da.blob.beaconnode "<L1 RPC beacon node>" \
--l1.endpoint "<L1 RPC execution client>" \
--da.sync=true --da.recovery --da.recovery.initiall1block "<initial L1 block (commit tx of initial batch)>" --da.recovery.initialbatch "<batch where to start recovery from>" --da.recovery.l2endblock "<until which L2 block recovery should run (optional)>" \
--verbosity 3
```

### 2. l2geth block production
After the state is recovered, the next step is to produce blocks on L2. This is done by running l2geth in block production mode.
As a prerequisite, the state recovery must be completed and the latest state of the L2 chain must be available.

You also need to generate a keystore e.g. with [Clef](https://geth.ethereum.org/docs/fundamentals/account-management) to be able to sign blocks.
This key is not used for any funds, but required for block production to work. Once you generated blocks you can safely discard it.

Running l2geth in block production mode requires following configuration:
- `--scroll` or `--scroll-sepolia` - enables Scroll Mainnet or Sepolia mode
- `--da.blob.beaconnode` - L1 RPC beacon node
- `--l1.endpoint` - L1 RPC execution client
- `--da.sync=true` - enables syncing with L1
- `--da.recovery` - enables recovery mode
- `--da.recovery.produceblocks` - enables block production
- `--miner.etherbase '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' --mine` - enables mining. the address is not used, but required for mining to work
- `---miner.gaslimit 1 --miner.gasprice 1 --miner.maxaccountsnum 100 --rpc.gascap 0 --gpo.ignoreprice 1` - gas limits for block production

```bash
./build/bin/geth --scroll<-sepolia> \
--datadir "tmp/datadir" \
--gcmode archive \
--http --http.addr "0.0.0.0" --http.port 8545 --http.api "eth,net,web3,debug,scroll" --http.vhosts "*" \
--da.blob.beaconnode "<L1 RPC beacon node>" \
--l1.endpoint "<L1 RPC execution client>" \
--da.sync=true --da.recovery --da.recovery.produceblocks \
--miner.gaslimit 1 --miner.gasprice 1 --miner.maxaccountsnum 100 --rpc.gascap 0 --gpo.ignoreprice 1 \
--miner.etherbase '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' --mine \
--ccc \
--verbosity 3
```

### 3. production, proving and submission of batch with `docker-compose.yml`
After the blocks are produced, the next step is to produce a batch, prove it and submit it to L1. This is done by running the `docker-compose.yml` in the `permissionless-batches` folder.


#### Producing a batch
To produce a batch you need to run the `batch-production-submission` profile in `docker-compose.yml`.

1. Fill `conf/genesis.json` with the latest genesis state from the L2 chain. The genesis for the current fork can be found here: [TODO link list with versions](#batch-production-toolkit)
2. Make sure that `l2geth` with your locally produced blocks is running and reachable from the Docker network (e.g. `http://host.docker.internal:8545`)
3. Fill in required fields in `conf/relayer/config.json`


Run with `docker compose --profile batch-production-submission up`.

#### Proving a batch
To prove a batch you need to run the `proving` profile in `docker-compose.yml`.

1. Make sure `verifier` `low_version_circuit` and `high_version_circuit` in `conf/coordinator/config.json` are correct for the latest fork: [TODO link list with versions](#batch-production-toolkit)
2. Download the latest `assets` and `params` for the circuit from [TODO link list with versions](#batch-production-toolkit) into `conf/coordinator/assets` and `conf/coordinator/params` respectively.
3. Fill in the required fields in `conf/proving-service/config.json`. It is recommended to use Sindri. You'll need to obtain credits and an API key from their [website](https://sindri.app/).
4. Alternatively, you can run your own prover: https://github.com/scroll-tech/scroll-prover. However, this requires more configuration.

Run with `docker compose --profile proving up`.


#### Batch submission
TODO


## Operator recovery
Operator recovery needs to be run by the rollup operator to resume normal rollup operation after permissionless batch mode is deactivated. It consists of two main components:
1. l2geth recovery
2. Relayer recovery

These steps are required to resume permissioned batch submission and the valid L2 chain. They will restore the entire history of the batches submitted during permissionless mode.

### Prerequisites
- l2geth with the latest state of the L2 chain (before permissionless mode was activated)
- signer key for the sequencer according to Clique consensus
- relayer and coordinator are set up, running and up-to-date with the latest state of the L2 chain (before permissionless mode was activated)

### l2geth recovery
Running l2geth in recovery mode requires following configuration:
- `--scroll` or `--scroll-sepolia` - enables Scroll Mainnet or Sepolia mode
- `--da.blob.beaconnode` - L1 RPC beacon node
- `--l1.endpoint` - L1 RPC execution client
- `--da.sync=true` - enables syncing with L1
- `--da.recovery` - enables recovery mode
- `--da.recovery.signblocks` - enables signing blocks with the sequencer and configured key
- `--da.recovery.initiall1block` - initial L1 block (commit tx of initial batch)
- `--da.recovery.initialbatch` - batch where to start recovery from. Can be found on [Scrollscan Explorer](https://scrollscan.com/batches).
- `--da.recovery.l2endblock` - until which L2 block recovery should run (optional)

```bash
./build/bin/geth --scroll<-sepolia> \
--datadir "tmp/datadir" \
--gcmode archive \
--http --http.addr "0.0.0.0" --http.port 8545 --http.api "eth,net,web3,debug,scroll" --http.vhosts "*" \
--da.blob.beaconnode "<L1 RPC beacon node>" \
--l1.endpoint "<L1 RPC execution client>" \
--da.sync=true --da.recovery --da.recovery.signblocks --da.recovery.initiall1block "<initial L1 block (commit tx of initial batch)>" --da.recovery.initialbatch "<batch where to start recovery from>" --da.recovery.l2endblock "<until which L2 block recovery should run (optional)>" \
--verbosity 3
```

After the recovery is finished, start the sequencer in normal operation and continue issuing L2 blocks as normal. This will resume the L2 chain, allow the relayer (after running recovery) to create new batches and allow other L2 follower nodes to sync up the valid and signed L2 chain.

### Relayer recovery
Start the relayer with the following additional top-level configuration:
```
"recovery_config": {
"enable": true
}
```

This will make the relayer recover all the chunks, batches and bundles that were submitted during permissionless mode. These batches are marked automatically as proven and finalized.
Once this process is finished, start the relayer normally without the recovery config to resume normal operation.
```
"recovery_config": {
"enable": false
}
```
Empty file.
38 changes: 38 additions & 0 deletions permissionless-batches/conf/coordinator/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"prover_manager": {
"provers_per_session": 1,
"session_attempts": 5,
"bundle_collection_time_sec": 3600,
"batch_collection_time_sec": 3600,
"chunk_collection_time_sec": 3600,
"verifier": {
"mock_mode": false,
"low_version_circuit": {
"params_path": "./conf/params",
"assets_path": "./conf/assets",
"fork_name": "darwinV2",
"min_prover_version": "v4.4.55"
},
"high_version_circuit": {
"params_path": "./conf/params",
"assets_path": "./conf/assets",
"fork_name": "darwinV2",
"min_prover_version": "v4.4.56"
}
}
},
"db": {
"driver_name": "postgres",
"dsn": "postgres://db/scroll?sslmode=disable&user=postgres",
"maxOpenNum": 200,
"maxIdleNum": 20
},
"l2": {
"chain_id": 111
},
"auth": {
"secret": "prover secret key",
"challenge_expire_duration_sec": 3600,
"login_expire_duration_sec": 3600
}
}
Empty file.
1 change: 1 addition & 0 deletions permissionless-batches/conf/genesis.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<fill with correct genesis.json>
Empty file.
Empty file.
Empty file.
26 changes: 26 additions & 0 deletions permissionless-batches/conf/proving-service/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"prover_name_prefix": "prover_",
"keys_dir": "/app/",
"db_path": "/app/",
"coordinator": {
"base_url": "http://coordinator:8390",
"retry_count": 3,
"retry_wait_time_sec": 5,
"connection_timeout_sec": 60
},
"l2geth": {
"endpoint": "<L2 RPC with generated blocks reachable from Docker network>"
},
"prover": {
"circuit_type": 2,
"circuit_version": "v0.13.1",
"n_workers": 1,
"cloud": {
"base_url": "https://sindri.app/api/v1/",
"api_key": "<API key>",
"retry_count": 3,
"retry_wait_time_sec": 5,
"connection_timeout_sec": 60
}
}
}
55 changes: 55 additions & 0 deletions permissionless-batches/conf/relayer/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"l1_config": {
"endpoint": "<L1 RPC execution node>"
},
"l2_config": {
"confirmations": "0x0",
"endpoint": "<L2 RPC with generated blocks reachable from Docker network>",
"relayer_config": {
"commit_sender_signer_config": {
"signer_type": "PrivateKey",
"private_key_signer_config": {
"private_key": "1414141414141414141414141414141414141414141414141414141414141414"
}
},
"l1_commit_gas_limit_multiplier": 1.2
},
"chunk_proposer_config": {
"propose_interval_milliseconds": 100,
"max_block_num_per_chunk": 100,
"max_tx_num_per_chunk": 100,
"max_l1_commit_gas_per_chunk": 11234567,
"max_l1_commit_calldata_size_per_chunk": 112345,
"chunk_timeout_sec": 300,
"max_row_consumption_per_chunk": 1048319,
"gas_cost_increase_multiplier": 1.2,
"max_uncompressed_batch_bytes_size": 634880
},
"batch_proposer_config": {
"propose_interval_milliseconds": 1000,
"max_l1_commit_gas_per_batch": 11234567,
"max_l1_commit_calldata_size_per_batch": 112345,
"batch_timeout_sec": 300,
"gas_cost_increase_multiplier": 1.2,
"max_uncompressed_batch_bytes_size": 634880
},
"bundle_proposer_config": {
"max_batch_num_per_bundle": 20,
"bundle_timeout_sec": 36000
}
},
"db_config": {
"driver_name": "postgres",
"dsn": "postgres://db/scroll?sslmode=disable&user=postgres",
"maxOpenNum": 200,
"maxIdleNum": 20
},
"recovery_config": {
"enable": true,
"l1_block_height": <commit tx of last finalized batch on L1>,
"latest_finalized_batch": <last finalized batch on L1>,
"l2_block_height_limit": <L2 block up to which to produce batch>,
"force_latest_finalized_batch": false,
"force_l1_message_count": 0
}
}
Loading