Commit 26235d2a authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into qbzzt/230322-pause-unpause

parents ffb28ca1 70da7ad1
---
'@eth-optimism/batch-submitter-service': patch
---
Allow deposit only batches
---
'@eth-optimism/chain-mon': minor
---
Introduces the balance-mon service to chain-mon.
...@@ -18,6 +18,7 @@ jobs: ...@@ -18,6 +18,7 @@ jobs:
l2geth: ${{ steps.packages.outputs.l2geth }} l2geth: ${{ steps.packages.outputs.l2geth }}
message-relayer: ${{ steps.packages.outputs.message-relayer }} message-relayer: ${{ steps.packages.outputs.message-relayer }}
fault-detector: ${{ steps.packages.outputs.fault-detector }} fault-detector: ${{ steps.packages.outputs.fault-detector }}
balance-mon: ${{ steps.packages.outputs.balance-mon }}
drippie-mon: ${{ steps.packages.outputs.drippie-mon }} drippie-mon: ${{ steps.packages.outputs.drippie-mon }}
wd-mon: ${{ steps.packages.outputs.wd-mon }} wd-mon: ${{ steps.packages.outputs.wd-mon }}
data-transport-layer: ${{ steps.packages.outputs.data-transport-layer }} data-transport-layer: ${{ steps.packages.outputs.data-transport-layer }}
...@@ -230,6 +231,33 @@ jobs: ...@@ -230,6 +231,33 @@ jobs:
push: true push: true
tags: ethereumoptimism/fault-detector:${{ needs.canary-publish.outputs.canary-docker-tag }} tags: ethereumoptimism/fault-detector:${{ needs.canary-publish.outputs.canary-docker-tag }}
balance-mon:
name: Publish Balance Monitor Version ${{ needs.canary-publish.outputs.canary-docker-tag }}
needs: canary-publish
if: needs.canary-publish.outputs.balance-mon != ''
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./ops/docker/Dockerfile.packages
target: balance-mon
push: true
tags: ethereumoptimism/balance-mon:${{ needs.canary-publish.outputs.canary-docker-tag }}
drippie-mon: drippie-mon:
name: Publish Drippie Monitor Version ${{ needs.canary-publish.outputs.canary-docker-tag }} name: Publish Drippie Monitor Version ${{ needs.canary-publish.outputs.canary-docker-tag }}
needs: canary-publish needs: canary-publish
......
...@@ -14,6 +14,7 @@ jobs: ...@@ -14,6 +14,7 @@ jobs:
l2geth: ${{ steps.packages.outputs.l2geth }} l2geth: ${{ steps.packages.outputs.l2geth }}
message-relayer: ${{ steps.packages.outputs.message-relayer }} message-relayer: ${{ steps.packages.outputs.message-relayer }}
fault-detector: ${{ steps.packages.outputs.fault-detector }} fault-detector: ${{ steps.packages.outputs.fault-detector }}
balance-mon: ${{ steps.packages.outputs.drippie-mon }}
drippie-mon: ${{ steps.packages.outputs.drippie-mon }} drippie-mon: ${{ steps.packages.outputs.drippie-mon }}
wd-mon: ${{ steps.packages.outputs.wd-mon }} wd-mon: ${{ steps.packages.outputs.wd-mon }}
data-transport-layer: ${{ steps.packages.outputs.data-transport-layer }} data-transport-layer: ${{ steps.packages.outputs.data-transport-layer }}
...@@ -364,6 +365,33 @@ jobs: ...@@ -364,6 +365,33 @@ jobs:
push: true push: true
tags: ethereumoptimism/wd-mon:${{ needs.release.outputs.wd-mon }},ethereumoptimism/wd-mon:latest tags: ethereumoptimism/wd-mon:${{ needs.release.outputs.wd-mon }},ethereumoptimism/wd-mon:latest
balance-mon:
name: Publish Balance Monitor Version ${{ needs.release.outputs.balance-mon }}
needs: release
if: needs.release.outputs.balance-mon != ''
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./ops/docker/Dockerfile.packages
target: balance-mon
push: true
tags: ethereumoptimism/balance-mon:${{ needs.release.outputs.balance-mon }},ethereumoptimism/balance-mon:latest
drippie-mon: drippie-mon:
name: Publish Drippie Monitor Version ${{ needs.release.outputs.drippie-mon }} name: Publish Drippie Monitor Version ${{ needs.release.outputs.drippie-mon }}
needs: release needs: release
......
...@@ -66,6 +66,7 @@ You'll need the following: ...@@ -66,6 +66,7 @@ You'll need the following:
* [Yarn](https://classic.yarnpkg.com/en/docs/install) * [Yarn](https://classic.yarnpkg.com/en/docs/install)
* [Docker](https://docs.docker.com/get-docker/) * [Docker](https://docs.docker.com/get-docker/)
* [Docker Compose](https://docs.docker.com/compose/install/) * [Docker Compose](https://docs.docker.com/compose/install/)
* [Go](https://go.dev/dl/)
* [Foundry](https://getfoundry.sh) * [Foundry](https://getfoundry.sh)
### Setup ### Setup
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"github.com/ethereum-optimism/optimism/batch-submitter/drivers/sequencer" "github.com/ethereum-optimism/optimism/batch-submitter/drivers/sequencer"
l2common "github.com/ethereum-optimism/optimism/l2geth/common" l2common "github.com/ethereum-optimism/optimism/l2geth/common"
"github.com/ethereum-optimism/optimism/l2geth/core/types"
l2types "github.com/ethereum-optimism/optimism/l2geth/core/types" l2types "github.com/ethereum-optimism/optimism/l2geth/core/types"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -47,3 +48,76 @@ func TestBatchElementFromBlock(t *testing.T) { ...@@ -47,3 +48,76 @@ func TestBatchElementFromBlock(t *testing.T) {
require.False(t, element.IsSequencerTx()) require.False(t, element.IsSequencerTx())
require.Nil(t, element.Tx) require.Nil(t, element.Tx)
} }
func TestGenSequencerParams(t *testing.T) {
tx := types.NewTransaction(0, l2common.Address{}, big.NewInt(0), 0, big.NewInt(0), []byte{})
shouldStartAtElement := uint64(1)
blockOffset := uint64(1)
batches := []sequencer.BatchElement{
{Timestamp: 1, BlockNumber: 1},
{Timestamp: 1, BlockNumber: 1, Tx: sequencer.NewCachedTx(tx)},
}
params, err := sequencer.GenSequencerBatchParams(shouldStartAtElement, blockOffset, batches)
require.NoError(t, err)
require.Equal(t, uint64(0), params.ShouldStartAtElement)
require.Equal(t, uint64(len(batches)), params.TotalElementsToAppend)
require.Equal(t, len(batches), len(params.Contexts))
// There is only 1 sequencer tx
require.Equal(t, 1, len(params.Txs))
// There are 2 contexts
// The first context contains the deposit
context1 := params.Contexts[0]
require.Equal(t, uint64(0), context1.NumSequencedTxs)
require.Equal(t, uint64(1), context1.NumSubsequentQueueTxs)
require.Equal(t, uint64(1), context1.Timestamp)
require.Equal(t, uint64(1), context1.BlockNumber)
// The second context contains the sequencer tx
context2 := params.Contexts[1]
require.Equal(t, uint64(1), context2.NumSequencedTxs)
require.Equal(t, uint64(0), context2.NumSubsequentQueueTxs)
require.Equal(t, uint64(1), context2.Timestamp)
require.Equal(t, uint64(1), context2.BlockNumber)
}
func TestGenSequencerParamsOnlyDeposits(t *testing.T) {
shouldStartAtElement := uint64(1)
blockOffset := uint64(1)
batches := []sequencer.BatchElement{
{Timestamp: 1, BlockNumber: 1},
{Timestamp: 1, BlockNumber: 1},
{Timestamp: 2, BlockNumber: 2},
}
params, err := sequencer.GenSequencerBatchParams(shouldStartAtElement, blockOffset, batches)
require.NoError(t, err)
// The batches will pack deposits into the same context when their
// timestamps and blocknumbers are the same
require.Equal(t, uint64(0), params.ShouldStartAtElement)
require.Equal(t, uint64(len(batches)), params.TotalElementsToAppend)
// 2 deposits have the same timestamp + blocknumber, they go in the
// same context. 1 deposit has a different timestamp + blocknumber,
// it goes into a different context. Therefore there are 2 contexts
require.Equal(t, 2, len(params.Contexts))
// No sequencer txs
require.Equal(t, 0, len(params.Txs))
// There are 2 contexts
// The first context contains the deposit
context1 := params.Contexts[0]
require.Equal(t, uint64(0), context1.NumSequencedTxs)
require.Equal(t, uint64(2), context1.NumSubsequentQueueTxs)
require.Equal(t, uint64(1), context1.Timestamp)
require.Equal(t, uint64(1), context1.BlockNumber)
context2 := params.Contexts[1]
require.Equal(t, uint64(0), context2.NumSequencedTxs)
require.Equal(t, uint64(1), context2.NumSubsequentQueueTxs)
require.Equal(t, uint64(2), context2.Timestamp)
require.Equal(t, uint64(2), context2.BlockNumber)
}
...@@ -222,11 +222,6 @@ func (p *AppendSequencerBatchParams) Write( ...@@ -222,11 +222,6 @@ func (p *AppendSequencerBatchParams) Write(
return ErrMalformedBatch return ErrMalformedBatch
} }
// There must be transactions if there are contexts
if len(p.Txs) == 0 && len(p.Contexts) != 0 {
return ErrMalformedBatch
}
// copy the contexts as to not malleate the struct // copy the contexts as to not malleate the struct
// when it is a typed batch // when it is a typed batch
contexts := make([]BatchContext, 0, len(p.Contexts)+1) contexts := make([]BatchContext, 0, len(p.Contexts)+1)
...@@ -361,9 +356,6 @@ func (p *AppendSequencerBatchParams) Read(r io.Reader) error { ...@@ -361,9 +356,6 @@ func (p *AppendSequencerBatchParams) Read(r io.Reader) error {
if len(p.Contexts) == 0 && len(p.Txs) != 0 { if len(p.Contexts) == 0 && len(p.Txs) != 0 {
return ErrMalformedBatch return ErrMalformedBatch
} }
if len(p.Txs) == 0 && len(p.Contexts) != 0 {
return ErrMalformedBatch
}
return closeReader() return closeReader()
} else if err != nil { } else if err != nil {
return err return err
......
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
} }
], ],
"txs": [], "txs": [],
"error": true "error": false
}, },
{ {
"name": "multiple-contexts-no-txs", "name": "multiple-contexts-no-txs",
...@@ -80,7 +80,7 @@ ...@@ -80,7 +80,7 @@
} }
], ],
"txs": [], "txs": [],
"error": true "error": false
}, },
{ {
"name": "complex", "name": "complex",
......
...@@ -201,7 +201,7 @@ Once you’ve built both repositories, you’ll need head back to the Optimism M ...@@ -201,7 +201,7 @@ Once you’ve built both repositories, you’ll need head back to the Optimism M
- Replace `"BATCHER"` with the address of the Batcher account you generated earlier. - Replace `"BATCHER"` with the address of the Batcher account you generated earlier.
- Replace `"SEQUENCER"` with the address of the Sequencer account you generated earlier. - Replace `"SEQUENCER"` with the address of the Sequencer account you generated earlier.
- Replace `"BLOCKHASH"` with the blockhash you got from the `cast` command. - Replace `"BLOCKHASH"` with the blockhash you got from the `cast` command.
- Replace `"TIMESTAMP"` with the timestamp you got from the `cast` command. Note that although all the other fields are strings, this field is a number! Don’t include the quotation marks. - Replace `TIMESTAMP` with the timestamp you got from the `cast` command. Note that although all the other fields are strings, this field is a number! Don’t include the quotation marks.
## Deploy the L1 contracts ## Deploy the L1 contracts
...@@ -390,9 +390,7 @@ Head over to the `op-node` package and start the `op-node` using the following c ...@@ -390,9 +390,7 @@ Head over to the `op-node` package and start the `op-node` using the following c
--rollup.config=./rollup.json \ --rollup.config=./rollup.json \
--rpc.addr=0.0.0.0 \ --rpc.addr=0.0.0.0 \
--rpc.port=8547 \ --rpc.port=8547 \
--p2p.listen.ip=0.0.0.0 \ --p2p.disable \
--p2p.listen.tcp=9003 \
--p2p.listen.udp=9003 \
--rpc.enable-admin \ --rpc.enable-admin \
--p2p.sequencer.key=<SEQUENCERKEY> \ --p2p.sequencer.key=<SEQUENCERKEY> \
--l1=<RPC> \ --l1=<RPC> \
...@@ -402,6 +400,26 @@ Head over to the `op-node` package and start the `op-node` using the following c ...@@ -402,6 +400,26 @@ Head over to the `op-node` package and start the `op-node` using the following c
Once you run this command, you should start seeing the `op-node` begin to process all of the L1 information after the starting block number that you picked earlier. Once the `op-node` has enough information, it’ll begin sending Engine API payloads to `op-geth`. At that point, you’ll start to see blocks being created inside of `op-geth`. We’re live! Once you run this command, you should start seeing the `op-node` begin to process all of the L1 information after the starting block number that you picked earlier. Once the `op-node` has enough information, it’ll begin sending Engine API payloads to `op-geth`. At that point, you’ll start to see blocks being created inside of `op-geth`. We’re live!
::: tip Peer to peer synchronization
If you use a chain ID that is also used by others, for example the default (42069), your `op-node` will try to use peer to peer to speed up synchronization.
These attempts will fail, because they will be signed with the wrong key, but they will waste time and network resources.
To avoid this , we start with peer to peer synchronization disabled (`--p2p.disable`).
Once you have multiple nodes, it makes sense to use these command line parameters to synchronize between them without getting confused by other blockchains.
```
--p2p.static=<nodes> \
--p2p.listen.ip=0.0.0.0 \
--p2p.listen.tcp=9003 \
--p2p.listen.udp=9003 \
```
:::
## Run op-batcher ## Run op-batcher
The final component necessary to put all the pieces together is the `op-batcher`. The `op-batcher` takes transactions from the Sequencer and publishes those transactions to L1. Once transactions are on L1, they’re officially part of the Rollup. Without the `op-batcher`, transactions sent to the Sequencer would never make it to L1 and wouldn’t become part of the canonical chain. The `op-batcher` is critical! The final component necessary to put all the pieces together is the `op-batcher`. The `op-batcher` takes transactions from the Sequencer and publishes those transactions to L1. Once transactions are on L1, they’re officially part of the Rollup. Without the `op-batcher`, transactions sent to the Sequencer would never make it to L1 and wouldn’t become part of the canonical chain. The `op-batcher` is critical!
...@@ -516,15 +534,47 @@ To use any other development stack, see the getting started tutorial, just repla ...@@ -516,15 +534,47 @@ To use any other development stack, see the getting started tutorial, just repla
### Stopping your Rollup ### Stopping your Rollup
To stop `op-geth` you should use Ctrl-C. An orderly shutdown is done in the reverse order to the order in which components were started:
1. Stop `op-batcher`.
1. Stop `op-node`.
1. Stop `op-geth`.
### Starting your Rollup
To restart the blockchain, use the same order of components you did when you initialized it.
1. `op-geth`
1. `op-node`
1. `op-batcher`
::: tip Synchronization takes time
`op-batcher` might have warning messages similar to:
```
WARN [03-21|14:13:55.248] Error calculating L2 block range err="failed to get sync status: Post \"http://localhost:8547\": context deadline exceeded"
WARN [03-21|14:13:57.328] Error calculating L2 block range err="failed to get sync status: Post \"http://localhost:8547\": context deadline exceeded"
```
This means that `op-node` is not yet synchronized up to the present time.
Just wait until it is.
:::
If `op-geth` aborts (for example, because the computer it is running on crashes), you will get these errors on `op-node`:
### Errors
#### Corrupt data directory
If `op-geth` aborts (for example, because the computer it is running on crashes), you might get these errors on `op-node`:
``` ```
WARN [02-16|21:22:02.868] Derivation process temporary error attempts=14 err="stage 0 failed resetting: temp: failed to find the L2 Heads to start from: failed to fetch L2 block by hash 0x0000000000000000000000000000000000000000000000000000000000000000: failed to determine block-hash of hash 0x0000000000000000000000000000000000000000000000000000000000000000, could not get payload: not found" WARN [02-16|21:22:02.868] Derivation process temporary error attempts=14 err="stage 0 failed resetting: temp: failed to find the L2 Heads to start from: failed to fetch L2 block by hash 0x0000000000000000000000000000000000000000000000000000000000000000: failed to determine block-hash of hash 0x0000000000000000000000000000000000000000000000000000000000000000, could not get payload: not found"
``` ```
In that case, you need to remove `datadir`, reinitialize it: This means that the data directory is corrupt and you need to reinitialize it:
```bash ```bash
cd ~/op-geth cd ~/op-geth
...@@ -536,17 +586,23 @@ echo "<SEQUENCER KEY HERE>" > datadir/block-signer-key ...@@ -536,17 +586,23 @@ echo "<SEQUENCER KEY HERE>" > datadir/block-signer-key
./build/bin/geth init --datadir=./datadir ./genesis.json ./build/bin/geth init --datadir=./datadir ./genesis.json
``` ```
### Starting your Rollup
To restart the blockchain, use the same order of components you did when you initialized it. #### Batcher out of ETH
1. `op-geth` If `op-batcher` runs out of ETH, it cannot submit write new transaction batches to L1.
2. `op-node` You will get error messages similar to this one:
3. `op-batcher`
```
INFO [03-21|14:22:32.754] publishing transaction service=batcher txHash=2ace6d..7eb248 nonce=2516 gasTipCap=2,340,741 gasFeeCap=172,028,434,515
ERROR[03-21|14:22:32.844] unable to publish transaction service=batcher txHash=2ace6d..7eb248 nonce=2516 gasTipCap=2,340,741 gasFeeCap=172,028,434,515 err="insufficient funds for gas * price + value"
```
Just send more ETH and to the batcher, and the problem will be resolved.
## Adding nodes ## Adding nodes
To add nodes to the rollup, you need to initialize `op-node` and `op-geth`, similar to what you did for the first node: To add nodes to the rollup, you need to initialize `op-node` and `op-geth`, similar to what you did for the first node.
You should *not* add an `op-bathcer`, there should be only one.
1. Configure the OS and prerequisites as you did for the first node. 1. Configure the OS and prerequisites as you did for the first node.
1. Build the Optimism monorepo and `op-geth` as you did for the first node. 1. Build the Optimism monorepo and `op-geth` as you did for the first node.
...@@ -574,7 +630,7 @@ To add nodes to the rollup, you need to initialize `op-node` and `op-geth`, simi ...@@ -574,7 +630,7 @@ To add nodes to the rollup, you need to initialize `op-node` and `op-geth`, simi
1. Start `op-geth` (using the same command line you used on the initial node) 1. Start `op-geth` (using the same command line you used on the initial node)
1. Start `op-node` (using the same command line you used on the initial node) 1. Start `op-node` (using the same command line you used on the initial node)
1. Wait while the node synchronizes
## What’s next? ## What’s next?
......
...@@ -9,7 +9,7 @@ require ( ...@@ -9,7 +9,7 @@ require (
github.com/docker/docker v20.10.21+incompatible github.com/docker/docker v20.10.21+incompatible
github.com/docker/go-connections v0.4.0 github.com/docker/go-connections v0.4.0
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3
github.com/ethereum/go-ethereum v1.11.4 github.com/ethereum/go-ethereum v1.11.5
github.com/fsnotify/fsnotify v1.6.0 github.com/fsnotify/fsnotify v1.6.0
github.com/golang/snappy v0.0.4 github.com/golang/snappy v0.0.4
github.com/google/go-cmp v0.5.9 github.com/google/go-cmp v0.5.9
...@@ -34,6 +34,7 @@ require ( ...@@ -34,6 +34,7 @@ require (
golang.org/x/crypto v0.6.0 golang.org/x/crypto v0.6.0
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb
golang.org/x/term v0.5.0 golang.org/x/term v0.5.0
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af
) )
require ( require (
...@@ -178,7 +179,6 @@ require ( ...@@ -178,7 +179,6 @@ require (
golang.org/x/sync v0.1.0 // indirect golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.5.0 // indirect golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect golang.org/x/text v0.7.0 // indirect
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
golang.org/x/tools v0.6.0 // indirect golang.org/x/tools v0.6.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
...@@ -189,6 +189,6 @@ require ( ...@@ -189,6 +189,6 @@ require (
nhooyr.io/websocket v1.8.7 // indirect nhooyr.io/websocket v1.8.7 // indirect
) )
replace github.com/ethereum/go-ethereum v1.11.4 => github.com/ethereum-optimism/op-geth v1.11.2-de8c5df46.0.20230321002540-11f0554a4313 replace github.com/ethereum/go-ethereum v1.11.5 => github.com/ethereum-optimism/op-geth v1.11.2-de8c5df46.0.20230324105532-555b76f39878
//replace github.com/ethereum/go-ethereum v1.11.4 => ../go-ethereum //replace github.com/ethereum/go-ethereum v1.11.5 => ../go-ethereum
...@@ -184,8 +184,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 ...@@ -184,8 +184,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs=
github.com/ethereum-optimism/op-geth v1.11.2-de8c5df46.0.20230321002540-11f0554a4313 h1:dBPc4CEzqmHUeU/Awk7Lw2mAaTc59T5W8CvAr+4YuzU= github.com/ethereum-optimism/op-geth v1.11.2-de8c5df46.0.20230324105532-555b76f39878 h1:pk3lFrP6zay7+jT+yoFAWxvGbP1Z/5lsorimXGrQoxE=
github.com/ethereum-optimism/op-geth v1.11.2-de8c5df46.0.20230321002540-11f0554a4313/go.mod h1:SGLXBOtu2JlKrNoUG76EatI2uJX/WZRY4nmEyvE9Q38= github.com/ethereum-optimism/op-geth v1.11.2-de8c5df46.0.20230324105532-555b76f39878/go.mod h1:SGLXBOtu2JlKrNoUG76EatI2uJX/WZRY4nmEyvE9Q38=
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ= github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ=
......
...@@ -51,7 +51,7 @@ func Main(version string, cliCtx *cli.Context) error { ...@@ -51,7 +51,7 @@ func Main(version string, cliCtx *cli.Context) error {
return err return err
} }
} }
defer batchSubmitter.StopIfRunning() defer batchSubmitter.StopIfRunning(context.Background())
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
...@@ -73,7 +73,7 @@ func Main(version string, cliCtx *cli.Context) error { ...@@ -73,7 +73,7 @@ func Main(version string, cliCtx *cli.Context) error {
l.Error("error starting metrics server", err) l.Error("error starting metrics server", err)
} }
}() }()
m.StartBalanceMetrics(ctx, l, batchSubmitter.L1Client, batchSubmitter.From) m.StartBalanceMetrics(ctx, l, batchSubmitter.L1Client, batchSubmitter.TxManager.From())
} }
rpcCfg := cfg.RPCConfig rpcCfg := cfg.RPCConfig
......
...@@ -18,6 +18,7 @@ var ( ...@@ -18,6 +18,7 @@ var (
ErrMaxDurationReached = errors.New("max channel duration reached") ErrMaxDurationReached = errors.New("max channel duration reached")
ErrChannelTimeoutClose = errors.New("close to channel timeout") ErrChannelTimeoutClose = errors.New("close to channel timeout")
ErrSeqWindowClose = errors.New("close to sequencer window timeout") ErrSeqWindowClose = errors.New("close to sequencer window timeout")
ErrTerminated = errors.New("channel terminated")
) )
type ChannelFullError struct { type ChannelFullError struct {
...@@ -188,7 +189,7 @@ func (c *channelBuilder) Reset() error { ...@@ -188,7 +189,7 @@ func (c *channelBuilder) Reset() error {
} }
// AddBlock adds a block to the channel compression pipeline. IsFull should be // AddBlock adds a block to the channel compression pipeline. IsFull should be
// called aftewards to test whether the channel is full. If full, a new channel // called afterwards to test whether the channel is full. If full, a new channel
// must be started. // must be started.
// //
// AddBlock returns a ChannelFullError if called even though the channel is // AddBlock returns a ChannelFullError if called even though the channel is
...@@ -307,16 +308,17 @@ func (c *channelBuilder) IsFull() bool { ...@@ -307,16 +308,17 @@ func (c *channelBuilder) IsFull() bool {
// FullErr returns the reason why the channel is full. If not full yet, it // FullErr returns the reason why the channel is full. If not full yet, it
// returns nil. // returns nil.
// //
// It returns a ChannelFullError wrapping one of six possible reasons for the // It returns a ChannelFullError wrapping one of the following possible reasons
// channel being full: // for the channel being full:
// - ErrInputTargetReached if the target amount of input data has been reached, // - ErrInputTargetReached if the target amount of input data has been reached,
// - derive.MaxRLPBytesPerChannel if the general maximum amount of input data // - derive.MaxRLPBytesPerChannel if the general maximum amount of input data
// would have been exceeded by the latest AddBlock call, // would have been exceeded by the latest AddBlock call,
// - ErrMaxFrameIndex if the maximum number of frames has been generated // - ErrMaxFrameIndex if the maximum number of frames has been generated
// (uint16), // (uint16),
// - ErrMaxDurationReached if the max channel duration got reached. // - ErrMaxDurationReached if the max channel duration got reached,
// - ErrChannelTimeoutClose if the consensus channel timeout got too close. // - ErrChannelTimeoutClose if the consensus channel timeout got too close,
// - ErrSeqWindowClose if the end of the sequencer window got too close. // - ErrSeqWindowClose if the end of the sequencer window got too close,
// - ErrTerminated if the channel was explicitly terminated.
func (c *channelBuilder) FullErr() error { func (c *channelBuilder) FullErr() error {
return c.fullErr return c.fullErr
} }
...@@ -402,6 +404,14 @@ func (c *channelBuilder) outputFrame() error { ...@@ -402,6 +404,14 @@ func (c *channelBuilder) outputFrame() error {
return err // possibly io.EOF (last frame) return err // possibly io.EOF (last frame)
} }
// Close immediately marks the channel as full with an ErrTerminated
// if the channel is not already full.
func (c *channelBuilder) Close() {
if !c.IsFull() {
c.setFullErr(ErrTerminated)
}
}
// HasFrame returns whether there's any available frame. If true, it can be // HasFrame returns whether there's any available frame. If true, it can be
// popped using NextFrame(). // popped using NextFrame().
// //
......
...@@ -41,6 +41,9 @@ type channelManager struct { ...@@ -41,6 +41,9 @@ type channelManager struct {
pendingTransactions map[txID]txData pendingTransactions map[txID]txData
// Set of confirmed txID -> inclusion block. For determining if the channel is timed out // Set of confirmed txID -> inclusion block. For determining if the channel is timed out
confirmedTransactions map[txID]eth.BlockID confirmedTransactions map[txID]eth.BlockID
// if set to true, prevents production of any new channel frames
closed bool
} }
func NewChannelManager(log log.Logger, metr metrics.Metricer, cfg ChannelConfig) *channelManager { func NewChannelManager(log log.Logger, metr metrics.Metricer, cfg ChannelConfig) *channelManager {
...@@ -60,6 +63,7 @@ func (s *channelManager) Clear() { ...@@ -60,6 +63,7 @@ func (s *channelManager) Clear() {
s.log.Trace("clearing channel manager state") s.log.Trace("clearing channel manager state")
s.blocks = s.blocks[:0] s.blocks = s.blocks[:0]
s.tip = common.Hash{} s.tip = common.Hash{}
s.closed = false
s.clearPendingChannel() s.clearPendingChannel()
} }
...@@ -78,6 +82,10 @@ func (s *channelManager) TxFailed(id txID) { ...@@ -78,6 +82,10 @@ func (s *channelManager) TxFailed(id txID) {
} }
s.metr.RecordBatchTxFailed() s.metr.RecordBatchTxFailed()
if s.closed && len(s.confirmedTransactions) == 0 && len(s.pendingTransactions) == 0 {
s.log.Info("Channel has no submitted transactions, clearing for shutdown", "chID", s.pendingChannel.ID())
s.clearPendingChannel()
}
} }
// TxConfirmed marks a transaction as confirmed on L1. Unfortunately even if all frames in // TxConfirmed marks a transaction as confirmed on L1. Unfortunately even if all frames in
...@@ -179,8 +187,8 @@ func (s *channelManager) TxData(l1Head eth.BlockID) (txData, error) { ...@@ -179,8 +187,8 @@ func (s *channelManager) TxData(l1Head eth.BlockID) (txData, error) {
dataPending := s.pendingChannel != nil && s.pendingChannel.HasFrame() dataPending := s.pendingChannel != nil && s.pendingChannel.HasFrame()
s.log.Debug("Requested tx data", "l1Head", l1Head, "data_pending", dataPending, "blocks_pending", len(s.blocks)) s.log.Debug("Requested tx data", "l1Head", l1Head, "data_pending", dataPending, "blocks_pending", len(s.blocks))
// Short circuit if there is a pending frame. // Short circuit if there is a pending frame or the channel manager is closed.
if dataPending { if dataPending || s.closed {
return s.nextTxData() return s.nextTxData()
} }
...@@ -344,3 +352,27 @@ func l2BlockRefFromBlockAndL1Info(block *types.Block, l1info derive.L1BlockInfo) ...@@ -344,3 +352,27 @@ func l2BlockRefFromBlockAndL1Info(block *types.Block, l1info derive.L1BlockInfo)
SequenceNumber: l1info.SequenceNumber, SequenceNumber: l1info.SequenceNumber,
} }
} }
// Close closes the current pending channel, if one exists, outputs any remaining frames,
// and prevents the creation of any new channels.
// Any outputted frames still need to be published.
func (s *channelManager) Close() error {
if s.closed {
return nil
}
s.closed = true
// Any pending state can be proactively cleared if there are no submitted transactions
if len(s.confirmedTransactions) == 0 && len(s.pendingTransactions) == 0 {
s.clearPendingChannel()
}
if s.pendingChannel == nil {
return nil
}
s.pendingChannel.Close()
return s.outputFrames()
}
...@@ -363,3 +363,145 @@ func TestChannelManager_TxResend(t *testing.T) { ...@@ -363,3 +363,145 @@ func TestChannelManager_TxResend(t *testing.T) {
require.NoError(err) require.NoError(err)
require.Len(fs, 1) require.Len(fs, 1)
} }
// TestChannelManagerCloseBeforeFirstUse ensures that the channel manager
// will not produce any frames if closed immediately.
func TestChannelManagerCloseBeforeFirstUse(t *testing.T) {
require := require.New(t)
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
log := testlog.Logger(t, log.LvlCrit)
m := NewChannelManager(log, metrics.NoopMetrics,
ChannelConfig{
TargetFrameSize: 0,
MaxFrameSize: 100,
ApproxComprRatio: 1.0,
ChannelTimeout: 1000,
})
a, _ := derivetest.RandomL2Block(rng, 4)
m.Close()
err := m.AddL2Block(a)
require.NoError(err, "Failed to add L2 block")
_, err = m.TxData(eth.BlockID{})
require.ErrorIs(err, io.EOF, "Expected closed channel manager to contain no tx data")
}
// TestChannelManagerCloseNoPendingChannel ensures that the channel manager
// can gracefully close with no pending channels, and will not emit any new
// channel frames.
func TestChannelManagerCloseNoPendingChannel(t *testing.T) {
require := require.New(t)
log := testlog.Logger(t, log.LvlCrit)
m := NewChannelManager(log, metrics.NoopMetrics,
ChannelConfig{
TargetFrameSize: 0,
MaxFrameSize: 100,
ApproxComprRatio: 1.0,
ChannelTimeout: 1000,
})
a := newMiniL2Block(0)
b := newMiniL2BlockWithNumberParent(0, big.NewInt(1), a.Hash())
err := m.AddL2Block(a)
require.NoError(err, "Failed to add L2 block")
txdata, err := m.TxData(eth.BlockID{})
require.NoError(err, "Expected channel manager to return valid tx data")
m.TxConfirmed(txdata.ID(), eth.BlockID{})
_, err = m.TxData(eth.BlockID{})
require.ErrorIs(err, io.EOF, "Expected channel manager to EOF")
m.Close()
err = m.AddL2Block(b)
require.NoError(err, "Failed to add L2 block")
_, err = m.TxData(eth.BlockID{})
require.ErrorIs(err, io.EOF, "Expected closed channel manager to return no new tx data")
}
// TestChannelManagerCloseNoPendingChannel ensures that the channel manager
// can gracefully close with a pending channel, and will not produce any
// new channel frames after this point.
func TestChannelManagerClosePendingChannel(t *testing.T) {
require := require.New(t)
log := testlog.Logger(t, log.LvlCrit)
m := NewChannelManager(log, metrics.NoopMetrics,
ChannelConfig{
TargetNumFrames: 100,
TargetFrameSize: 1000,
MaxFrameSize: 1000,
ApproxComprRatio: 1.0,
ChannelTimeout: 1000,
})
a := newMiniL2Block(50_000)
b := newMiniL2BlockWithNumberParent(10, big.NewInt(1), a.Hash())
err := m.AddL2Block(a)
require.NoError(err, "Failed to add L2 block")
txdata, err := m.TxData(eth.BlockID{})
require.NoError(err, "Expected channel manager to produce valid tx data")
m.TxConfirmed(txdata.ID(), eth.BlockID{})
m.Close()
txdata, err = m.TxData(eth.BlockID{})
require.NoError(err, "Expected channel manager to produce tx data from remaining L2 block data")
m.TxConfirmed(txdata.ID(), eth.BlockID{})
_, err = m.TxData(eth.BlockID{})
require.ErrorIs(err, io.EOF, "Expected channel manager to have no more tx data")
err = m.AddL2Block(b)
require.NoError(err, "Failed to add L2 block")
_, err = m.TxData(eth.BlockID{})
require.ErrorIs(err, io.EOF, "Expected closed channel manager to produce no more tx data")
}
// TestChannelManagerCloseAllTxsFailed ensures that the channel manager
// can gracefully close after producing transaction frames if none of these
// have successfully landed on chain.
func TestChannelManagerCloseAllTxsFailed(t *testing.T) {
require := require.New(t)
log := testlog.Logger(t, log.LvlCrit)
m := NewChannelManager(log, metrics.NoopMetrics,
ChannelConfig{
TargetNumFrames: 100,
TargetFrameSize: 1000,
MaxFrameSize: 1000,
ApproxComprRatio: 1.0,
ChannelTimeout: 1000,
})
a := newMiniL2Block(50_000)
err := m.AddL2Block(a)
require.NoError(err, "Failed to add L2 block")
txdata, err := m.TxData(eth.BlockID{})
require.NoError(err, "Expected channel manager to produce valid tx data")
m.TxFailed(txdata.ID())
// Show that this data will continue to be emitted as long as the transaction
// fails and the channel manager is not closed
txdata, err = m.TxData(eth.BlockID{})
require.NoError(err, "Expected channel manager to re-attempt the failed transaction")
m.TxFailed(txdata.ID())
m.Close()
_, err = m.TxData(eth.BlockID{})
require.ErrorIs(err, io.EOF, "Expected closed channel manager to produce no more tx data")
}
...@@ -3,7 +3,6 @@ package batcher ...@@ -3,7 +3,6 @@ package batcher
import ( import (
"time" "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -17,7 +16,6 @@ import ( ...@@ -17,7 +16,6 @@ import (
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof" oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
opsigner "github.com/ethereum-optimism/optimism/op-signer/client"
) )
type Config struct { type Config struct {
...@@ -26,11 +24,9 @@ type Config struct { ...@@ -26,11 +24,9 @@ type Config struct {
L1Client *ethclient.Client L1Client *ethclient.Client
L2Client *ethclient.Client L2Client *ethclient.Client
RollupNode *sources.RollupClient RollupNode *sources.RollupClient
TxManager txmgr.TxManager
PollInterval time.Duration PollInterval time.Duration
From common.Address
TxManagerConfig txmgr.Config
// RollupConfig is queried at startup // RollupConfig is queried at startup
Rollup *rollup.Config Rollup *rollup.Config
...@@ -51,8 +47,6 @@ func (c *Config) Check() error { ...@@ -51,8 +47,6 @@ func (c *Config) Check() error {
} }
type CLIConfig struct { type CLIConfig struct {
/* Required Params */
// L1EthRpc is the HTTP provider URL for L1. // L1EthRpc is the HTTP provider URL for L1.
L1EthRpc string L1EthRpc string
...@@ -81,35 +75,6 @@ type CLIConfig struct { ...@@ -81,35 +75,6 @@ type CLIConfig struct {
// and creating a new batch. // and creating a new batch.
PollInterval time.Duration PollInterval time.Duration
// NumConfirmations is the number of confirmations which we will wait after
// appending new batches.
NumConfirmations uint64
// SafeAbortNonceTooLowCount is the number of ErrNonceTooLowObservations
// required to give up on a tx at a particular nonce without receiving
// confirmation.
SafeAbortNonceTooLowCount uint64
// ResubmissionTimeout is time we will wait before resubmitting a
// transaction.
ResubmissionTimeout time.Duration
// Mnemonic is the HD seed used to derive the wallet private keys for both
// the sequence and proposer. Must be used in conjunction with
// SequencerHDPath and ProposerHDPath.
Mnemonic string
// SequencerHDPath is the derivation path used to obtain the private key for
// batched submission of sequencer transactions.
SequencerHDPath string
// PrivateKey is the private key used to submit sequencer transactions.
PrivateKey string
RPCConfig rpc.CLIConfig
/* Optional Params */
// MaxL1TxSize is the maximum size of a batch tx submitted to L1. // MaxL1TxSize is the maximum size of a batch tx submitted to L1.
MaxL1TxSize uint64 MaxL1TxSize uint64
...@@ -125,14 +90,11 @@ type CLIConfig struct { ...@@ -125,14 +90,11 @@ type CLIConfig struct {
Stopped bool Stopped bool
TxMgrConfig txmgr.CLIConfig
RPCConfig rpc.CLIConfig
LogConfig oplog.CLIConfig LogConfig oplog.CLIConfig
MetricsConfig opmetrics.CLIConfig MetricsConfig opmetrics.CLIConfig
PprofConfig oppprof.CLIConfig PprofConfig oppprof.CLIConfig
// SignerConfig contains the client config for op-signer service
SignerConfig opsigner.CLIConfig
} }
func (c CLIConfig) Check() error { func (c CLIConfig) Check() error {
...@@ -148,7 +110,7 @@ func (c CLIConfig) Check() error { ...@@ -148,7 +110,7 @@ func (c CLIConfig) Check() error {
if err := c.PprofConfig.Check(); err != nil { if err := c.PprofConfig.Check(); err != nil {
return err return err
} }
if err := c.SignerConfig.Check(); err != nil { if err := c.TxMgrConfig.Check(); err != nil {
return err return err
} }
return nil return nil
...@@ -163,9 +125,6 @@ func NewConfig(ctx *cli.Context) CLIConfig { ...@@ -163,9 +125,6 @@ func NewConfig(ctx *cli.Context) CLIConfig {
RollupRpc: ctx.GlobalString(flags.RollupRpcFlag.Name), RollupRpc: ctx.GlobalString(flags.RollupRpcFlag.Name),
SubSafetyMargin: ctx.GlobalUint64(flags.SubSafetyMarginFlag.Name), SubSafetyMargin: ctx.GlobalUint64(flags.SubSafetyMarginFlag.Name),
PollInterval: ctx.GlobalDuration(flags.PollIntervalFlag.Name), PollInterval: ctx.GlobalDuration(flags.PollIntervalFlag.Name),
NumConfirmations: ctx.GlobalUint64(flags.NumConfirmationsFlag.Name),
SafeAbortNonceTooLowCount: ctx.GlobalUint64(flags.SafeAbortNonceTooLowCountFlag.Name),
ResubmissionTimeout: ctx.GlobalDuration(flags.ResubmissionTimeoutFlag.Name),
/* Optional Flags */ /* Optional Flags */
MaxChannelDuration: ctx.GlobalUint64(flags.MaxChannelDurationFlag.Name), MaxChannelDuration: ctx.GlobalUint64(flags.MaxChannelDurationFlag.Name),
...@@ -174,13 +133,10 @@ func NewConfig(ctx *cli.Context) CLIConfig { ...@@ -174,13 +133,10 @@ func NewConfig(ctx *cli.Context) CLIConfig {
TargetNumFrames: ctx.GlobalInt(flags.TargetNumFramesFlag.Name), TargetNumFrames: ctx.GlobalInt(flags.TargetNumFramesFlag.Name),
ApproxComprRatio: ctx.GlobalFloat64(flags.ApproxComprRatioFlag.Name), ApproxComprRatio: ctx.GlobalFloat64(flags.ApproxComprRatioFlag.Name),
Stopped: ctx.GlobalBool(flags.StoppedFlag.Name), Stopped: ctx.GlobalBool(flags.StoppedFlag.Name),
Mnemonic: ctx.GlobalString(flags.MnemonicFlag.Name), TxMgrConfig: txmgr.ReadCLIConfig(ctx),
SequencerHDPath: ctx.GlobalString(flags.SequencerHDPathFlag.Name),
PrivateKey: ctx.GlobalString(flags.PrivateKeyFlag.Name),
RPCConfig: rpc.ReadCLIConfig(ctx), RPCConfig: rpc.ReadCLIConfig(ctx),
LogConfig: oplog.ReadCLIConfig(ctx), LogConfig: oplog.ReadCLIConfig(ctx),
MetricsConfig: opmetrics.ReadCLIConfig(ctx), MetricsConfig: opmetrics.ReadCLIConfig(ctx),
PprofConfig: oppprof.ReadCLIConfig(ctx), PprofConfig: oppprof.ReadCLIConfig(ctx),
SignerConfig: opsigner.ReadCLIConfig(ctx),
} }
} }
...@@ -13,8 +13,8 @@ import ( ...@@ -13,8 +13,8 @@ import (
"github.com/ethereum-optimism/optimism/op-batcher/metrics" "github.com/ethereum-optimism/optimism/op-batcher/metrics"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/derive"
opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
...@@ -24,12 +24,13 @@ import ( ...@@ -24,12 +24,13 @@ import (
type BatchSubmitter struct { type BatchSubmitter struct {
Config // directly embed the config + sources Config // directly embed the config + sources
txMgr *TransactionManager txMgr txmgr.TxManager
wg sync.WaitGroup wg sync.WaitGroup
done chan struct{}
ctx context.Context shutdownCtx context.Context
cancel context.CancelFunc cancelShutdownCtx context.CancelFunc
killCtx context.Context
cancelKillCtx context.CancelFunc
mutex sync.Mutex mutex sync.Mutex
running bool running bool
...@@ -46,11 +47,6 @@ type BatchSubmitter struct { ...@@ -46,11 +47,6 @@ type BatchSubmitter struct {
func NewBatchSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger, m metrics.Metricer) (*BatchSubmitter, error) { func NewBatchSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger, m metrics.Metricer) (*BatchSubmitter, error) {
ctx := context.Background() ctx := context.Background()
signer, fromAddress, err := opcrypto.SignerFactoryFromConfig(l, cfg.PrivateKey, cfg.Mnemonic, cfg.SequencerHDPath, cfg.SignerConfig)
if err != nil {
return nil, err
}
// Connect to L1 and L2 providers. Perform these last since they are the // Connect to L1 and L2 providers. Perform these last since they are the
// most expensive. // most expensive.
l1Client, err := dialEthClientWithTimeout(ctx, cfg.L1EthRpc) l1Client, err := dialEthClientWithTimeout(ctx, cfg.L1EthRpc)
...@@ -73,22 +69,18 @@ func NewBatchSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger, m metrics.Metri ...@@ -73,22 +69,18 @@ func NewBatchSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger, m metrics.Metri
return nil, fmt.Errorf("querying rollup config: %w", err) return nil, fmt.Errorf("querying rollup config: %w", err)
} }
txManagerConfig := txmgr.Config{ txManagerConfig, err := txmgr.NewConfig(cfg.TxMgrConfig, l)
ResubmissionTimeout: cfg.ResubmissionTimeout, if err != nil {
ReceiptQueryInterval: time.Second, return nil, err
NumConfirmations: cfg.NumConfirmations,
SafeAbortNonceTooLowCount: cfg.SafeAbortNonceTooLowCount,
From: fromAddress,
Signer: signer(rcfg.L1ChainID),
} }
txManager := txmgr.NewSimpleTxManager("batcher", l, txManagerConfig)
batcherCfg := Config{ batcherCfg := Config{
L1Client: l1Client, L1Client: l1Client,
L2Client: l2Client, L2Client: l2Client,
RollupNode: rollupClient, RollupNode: rollupClient,
PollInterval: cfg.PollInterval, PollInterval: cfg.PollInterval,
TxManagerConfig: txManagerConfig, TxManager: txManager,
From: fromAddress,
Rollup: rcfg, Rollup: rcfg,
Channel: ChannelConfig{ Channel: ChannelConfig{
SeqWindowSize: rcfg.SeqWindowSize, SeqWindowSize: rcfg.SeqWindowSize,
...@@ -113,21 +105,19 @@ func NewBatchSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger, m metrics.Metri ...@@ -113,21 +105,19 @@ func NewBatchSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger, m metrics.Metri
// NewBatchSubmitter initializes the BatchSubmitter, gathering any resources // NewBatchSubmitter initializes the BatchSubmitter, gathering any resources
// that will be needed during operation. // that will be needed during operation.
func NewBatchSubmitter(ctx context.Context, cfg Config, l log.Logger, m metrics.Metricer) (*BatchSubmitter, error) { func NewBatchSubmitter(ctx context.Context, cfg Config, l log.Logger, m metrics.Metricer) (*BatchSubmitter, error) {
balance, err := cfg.L1Client.BalanceAt(ctx, cfg.From, nil) balance, err := cfg.L1Client.BalanceAt(ctx, cfg.TxManager.From(), nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.log = l cfg.log = l
cfg.log.Info("creating batch submitter", "submitter_addr", cfg.From, "submitter_bal", balance) cfg.log.Info("creating batch submitter", "submitter_addr", cfg.TxManager.From(), "submitter_bal", balance)
cfg.metr = m cfg.metr = m
return &BatchSubmitter{ return &BatchSubmitter{
Config: cfg, Config: cfg,
txMgr: NewTransactionManager(l, txMgr: cfg.TxManager,
cfg.TxManagerConfig, cfg.Rollup.BatchInboxAddress, cfg.Rollup.L1ChainID,
cfg.From, cfg.L1Client),
state: NewChannelManager(l, m, cfg.Channel), state: NewChannelManager(l, m, cfg.Channel),
}, nil }, nil
...@@ -144,10 +134,8 @@ func (l *BatchSubmitter) Start() error { ...@@ -144,10 +134,8 @@ func (l *BatchSubmitter) Start() error {
} }
l.running = true l.running = true
l.done = make(chan struct{}) l.shutdownCtx, l.cancelShutdownCtx = context.WithCancel(context.Background())
// TODO: this context only exists because the event loop doesn't reach done l.killCtx, l.cancelKillCtx = context.WithCancel(context.Background())
// if the tx manager is blocking forever due to e.g. insufficient balance.
l.ctx, l.cancel = context.WithCancel(context.Background())
l.state.Clear() l.state.Clear()
l.lastStoredBlock = eth.BlockID{} l.lastStoredBlock = eth.BlockID{}
...@@ -159,11 +147,11 @@ func (l *BatchSubmitter) Start() error { ...@@ -159,11 +147,11 @@ func (l *BatchSubmitter) Start() error {
return nil return nil
} }
func (l *BatchSubmitter) StopIfRunning() { func (l *BatchSubmitter) StopIfRunning(ctx context.Context) {
_ = l.Stop() _ = l.Stop(ctx)
} }
func (l *BatchSubmitter) Stop() error { func (l *BatchSubmitter) Stop(ctx context.Context) error {
l.log.Info("Stopping Batch Submitter") l.log.Info("Stopping Batch Submitter")
l.mutex.Lock() l.mutex.Lock()
...@@ -174,9 +162,18 @@ func (l *BatchSubmitter) Stop() error { ...@@ -174,9 +162,18 @@ func (l *BatchSubmitter) Stop() error {
} }
l.running = false l.running = false
l.cancel() // go routine will call cancelKill() if the passed in ctx is ever Done
close(l.done) cancelKill := l.cancelKillCtx
wrapped, cancel := context.WithCancel(ctx)
defer cancel()
go func() {
<-wrapped.Done()
cancelKill()
}()
l.cancelShutdownCtx()
l.wg.Wait() l.wg.Wait()
l.cancelKillCtx()
l.log.Info("Batch Submitter stopped") l.log.Info("Batch Submitter stopped")
...@@ -226,7 +223,7 @@ func (l *BatchSubmitter) loadBlocksIntoState(ctx context.Context) { ...@@ -226,7 +223,7 @@ func (l *BatchSubmitter) loadBlocksIntoState(ctx context.Context) {
// loadBlockIntoState fetches & stores a single block into `state`. It returns the block it loaded. // loadBlockIntoState fetches & stores a single block into `state`. It returns the block it loaded.
func (l *BatchSubmitter) loadBlockIntoState(ctx context.Context, blockNumber uint64) (*types.Block, error) { func (l *BatchSubmitter) loadBlockIntoState(ctx context.Context, blockNumber uint64) (*types.Block, error) {
ctx, cancel := context.WithTimeout(ctx, networkTimeout) ctx, cancel := context.WithTimeout(ctx, txManagerTimeout)
defer cancel() defer cancel()
block, err := l.L2Client.BlockByNumber(ctx, new(big.Int).SetUint64(blockNumber)) block, err := l.L2Client.BlockByNumber(ctx, new(big.Int).SetUint64(blockNumber))
if err != nil { if err != nil {
...@@ -244,7 +241,7 @@ func (l *BatchSubmitter) loadBlockIntoState(ctx context.Context, blockNumber uin ...@@ -244,7 +241,7 @@ func (l *BatchSubmitter) loadBlockIntoState(ctx context.Context, blockNumber uin
// calculateL2BlockRangeToStore determines the range (start,end] that should be loaded into the local state. // calculateL2BlockRangeToStore determines the range (start,end] that should be loaded into the local state.
// It also takes care of initializing some local state (i.e. will modify l.lastStoredBlock in certain conditions) // It also takes care of initializing some local state (i.e. will modify l.lastStoredBlock in certain conditions)
func (l *BatchSubmitter) calculateL2BlockRangeToStore(ctx context.Context) (eth.BlockID, eth.BlockID, error) { func (l *BatchSubmitter) calculateL2BlockRangeToStore(ctx context.Context) (eth.BlockID, eth.BlockID, error) {
childCtx, cancel := context.WithTimeout(ctx, networkTimeout) childCtx, cancel := context.WithTimeout(ctx, txManagerTimeout)
defer cancel() defer cancel()
syncStatus, err := l.RollupNode.SyncStatus(childCtx) syncStatus, err := l.RollupNode.SyncStatus(childCtx)
// Ensure that we have the sync status // Ensure that we have the sync status
...@@ -292,14 +289,39 @@ func (l *BatchSubmitter) loop() { ...@@ -292,14 +289,39 @@ func (l *BatchSubmitter) loop() {
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
l.loadBlocksIntoState(l.ctx) l.loadBlocksIntoState(l.shutdownCtx)
l.publishStateToL1(l.killCtx)
case <-l.shutdownCtx.Done():
l.publishStateToL1(l.killCtx)
return
}
}
}
blockLoop: // publishStateToL1 loops through the block data loaded into `state` and
// submits the associated data to the L1 in the form of channel frames.
func (l *BatchSubmitter) publishStateToL1(ctx context.Context) {
for { for {
l1tip, err := l.l1Tip(l.ctx) // Attempt to gracefully terminate the current channel, ensuring that no new frames will be
// produced. Any remaining frames must still be published to the L1 to prevent stalling.
select {
case <-ctx.Done():
err := l.state.Close()
if err != nil {
l.log.Error("error closing the channel manager", "err", err)
}
case <-l.shutdownCtx.Done():
err := l.state.Close()
if err != nil {
l.log.Error("error closing the channel manager", "err", err)
}
default:
}
l1tip, err := l.l1Tip(ctx)
if err != nil { if err != nil {
l.log.Error("Failed to query L1 tip", "error", err) l.log.Error("Failed to query L1 tip", "error", err)
break return
} }
l.recordL1Tip(l1tip) l.recordL1Tip(l1tip)
...@@ -307,31 +329,51 @@ func (l *BatchSubmitter) loop() { ...@@ -307,31 +329,51 @@ func (l *BatchSubmitter) loop() {
txdata, err := l.state.TxData(l1tip.ID()) txdata, err := l.state.TxData(l1tip.ID())
if err == io.EOF { if err == io.EOF {
l.log.Trace("no transaction data available") l.log.Trace("no transaction data available")
break // local for loop break
} else if err != nil { } else if err != nil {
l.log.Error("unable to get tx data", "err", err) l.log.Error("unable to get tx data", "err", err)
break break
} }
// Record TX Status // Record TX Status
if receipt, err := l.txMgr.SendTransaction(l.ctx, txdata.Bytes()); err != nil { if receipt, err := l.sendTransaction(ctx, txdata.Bytes()); err != nil {
l.recordFailedTx(txdata.ID(), err) l.recordFailedTx(txdata.ID(), err)
} else { } else {
l.recordConfirmedTx(txdata.ID(), receipt) l.recordConfirmedTx(txdata.ID(), receipt)
} }
// hack to exit this loop. Proper fix is to do request another send tx or parallel tx sending
// from the channel manager rather than sending the channel in a loop. This stalls b/c if the
// context is cancelled while sending, it will never fully clear the pending txns.
select {
case <-l.ctx.Done():
break blockLoop
default:
}
} }
}
case <-l.done: const networkTimeout = 2 * time.Second // How long a single network request can take. TODO: put in a config somewhere
return
// fix(refcell):
// combined with above, these config variables should also be replicated in the op-proposer
// along with op-proposer changes to include the updated tx manager
const txManagerTimeout = 2 * time.Minute // How long the tx manager can take to send a transaction.
// sendTransaction creates & submits a transaction to the batch inbox address with the given `data`.
// It currently uses the underlying `txmgr` to handle transaction sending & price management.
// This is a blocking method. It should not be called concurrently.
func (l *BatchSubmitter) sendTransaction(ctx context.Context, data []byte) (*types.Receipt, error) {
// Do the gas estimation offline. A value of 0 will cause the [txmgr] to estimate the gas limit.
intrinsicGas, err := core.IntrinsicGas(data, nil, false, true, true, false)
if err != nil {
return nil, fmt.Errorf("failed to calculate intrinsic gas: %w", err)
} }
// Send the transaction through the txmgr
ctx, cancel := context.WithTimeout(ctx, txManagerTimeout)
defer cancel()
if receipt, err := l.txMgr.Send(ctx, txmgr.TxCandidate{
To: l.Rollup.BatchInboxAddress,
TxData: data,
From: l.txMgr.From(),
GasLimit: intrinsicGas,
}); err != nil {
l.log.Warn("unable to publish tx", "err", err, "data_size", len(data))
return nil, err
} else {
l.log.Info("tx successfully published", "tx_hash", receipt.TxHash, "data_size", len(data))
return receipt, nil
} }
} }
......
package batcher
import (
"context"
"fmt"
"math/big"
"time"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
)
const networkTimeout = 2 * time.Second // How long a single network request can take. TODO: put in a config somewhere
// TransactionManager wraps the simple txmgr package to make it easy to send & wait for transactions
type TransactionManager struct {
// Config
batchInboxAddress common.Address
senderAddress common.Address
chainID *big.Int
// Outside world
txMgr txmgr.TxManager
l1Client *ethclient.Client
signerFn opcrypto.SignerFn
log log.Logger
}
func NewTransactionManager(log log.Logger, txMgrConfg txmgr.Config, batchInboxAddress common.Address, chainID *big.Int, senderAddress common.Address, l1Client *ethclient.Client) *TransactionManager {
t := &TransactionManager{
batchInboxAddress: batchInboxAddress,
senderAddress: senderAddress,
chainID: chainID,
txMgr: txmgr.NewSimpleTxManager("batcher", log, txMgrConfg, l1Client),
l1Client: l1Client,
signerFn: txMgrConfg.Signer,
log: log,
}
return t
}
// SendTransaction creates & submits a transaction to the batch inbox address with the given `data`.
// It currently uses the underlying `txmgr` to handle transaction sending & price management.
// This is a blocking method. It should not be called concurrently.
// TODO: where to put concurrent transaction handling logic.
func (t *TransactionManager) SendTransaction(ctx context.Context, data []byte) (*types.Receipt, error) {
tx, err := t.CraftTx(ctx, data)
if err != nil {
return nil, fmt.Errorf("failed to create tx: %w", err)
}
ctx, cancel := context.WithTimeout(ctx, 10*time.Minute) // TODO: Select a timeout that makes sense here.
defer cancel()
if receipt, err := t.txMgr.Send(ctx, tx); err != nil {
t.log.Warn("unable to publish tx", "err", err, "data_size", len(data))
return nil, err
} else {
t.log.Info("tx successfully published", "tx_hash", receipt.TxHash, "data_size", len(data))
return receipt, nil
}
}
// calcGasTipAndFeeCap queries L1 to determine what a suitable miner tip & basefee limit would be for timely inclusion
func (t *TransactionManager) calcGasTipAndFeeCap(ctx context.Context) (gasTipCap *big.Int, gasFeeCap *big.Int, err error) {
childCtx, cancel := context.WithTimeout(ctx, networkTimeout)
gasTipCap, err = t.l1Client.SuggestGasTipCap(childCtx)
cancel()
if err != nil {
return nil, nil, fmt.Errorf("failed to get suggested gas tip cap: %w", err)
}
if gasTipCap == nil {
t.log.Warn("unexpected unset gasTipCap, using default 2 gwei")
gasTipCap = new(big.Int).SetUint64(params.GWei * 2)
}
childCtx, cancel = context.WithTimeout(ctx, networkTimeout)
head, err := t.l1Client.HeaderByNumber(childCtx, nil)
cancel()
if err != nil || head == nil {
return nil, nil, fmt.Errorf("failed to get L1 head block for fee cap: %w", err)
}
if head.BaseFee == nil {
return nil, nil, fmt.Errorf("failed to get L1 basefee in block %d for fee cap", head.Number)
}
gasFeeCap = txmgr.CalcGasFeeCap(head.BaseFee, gasTipCap)
return gasTipCap, gasFeeCap, nil
}
// CraftTx creates the signed transaction to the batchInboxAddress.
// It queries L1 for the current fee market conditions as well as for the nonce.
// NOTE: This method SHOULD NOT publish the resulting transaction.
func (t *TransactionManager) CraftTx(ctx context.Context, data []byte) (*types.Transaction, error) {
gasTipCap, gasFeeCap, err := t.calcGasTipAndFeeCap(ctx)
if err != nil {
return nil, err
}
childCtx, cancel := context.WithTimeout(ctx, networkTimeout)
nonce, err := t.l1Client.NonceAt(childCtx, t.senderAddress, nil)
cancel()
if err != nil {
return nil, fmt.Errorf("failed to get nonce: %w", err)
}
rawTx := &types.DynamicFeeTx{
ChainID: t.chainID,
Nonce: nonce,
To: &t.batchInboxAddress,
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Data: data,
}
t.log.Info("creating tx", "to", rawTx.To, "from", t.senderAddress)
gas, err := core.IntrinsicGas(rawTx.Data, nil, false, true, true, false)
if err != nil {
return nil, fmt.Errorf("failed to calculate intrinsic gas: %w", err)
}
rawTx.Gas = gas
ctx, cancel = context.WithTimeout(ctx, networkTimeout)
defer cancel()
tx := types.NewTx(rawTx)
return t.signerFn(ctx, t.senderAddress, tx)
}
...@@ -9,14 +9,13 @@ import ( ...@@ -9,14 +9,13 @@ import (
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof" oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
opsigner "github.com/ethereum-optimism/optimism/op-signer/client" "github.com/ethereum-optimism/optimism/op-service/txmgr"
) )
const envVarPrefix = "OP_BATCHER" const envVarPrefix = "OP_BATCHER"
var ( var (
/* Required flags */ // Required flags
L1EthRpcFlag = cli.StringFlag{ L1EthRpcFlag = cli.StringFlag{
Name: "l1-eth-rpc", Name: "l1-eth-rpc",
Usage: "HTTP provider URL for L1", Usage: "HTTP provider URL for L1",
...@@ -50,31 +49,8 @@ var ( ...@@ -50,31 +49,8 @@ var (
Required: true, Required: true,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "POLL_INTERVAL"), EnvVar: opservice.PrefixEnvVar(envVarPrefix, "POLL_INTERVAL"),
} }
NumConfirmationsFlag = cli.Uint64Flag{
Name: "num-confirmations",
Usage: "Number of confirmations which we will wait after " +
"appending a new batch",
Required: true,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "NUM_CONFIRMATIONS"),
}
SafeAbortNonceTooLowCountFlag = cli.Uint64Flag{
Name: "safe-abort-nonce-too-low-count",
Usage: "Number of ErrNonceTooLow observations required to " +
"give up on a tx at a particular nonce without receiving " +
"confirmation",
Required: true,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "SAFE_ABORT_NONCE_TOO_LOW_COUNT"),
}
ResubmissionTimeoutFlag = cli.DurationFlag{
Name: "resubmission-timeout",
Usage: "Duration we will wait before resubmitting a " +
"transaction to L1",
Required: true,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "RESUBMISSION_TIMEOUT"),
}
/* Optional flags */
// Optional flags
MaxChannelDurationFlag = cli.Uint64Flag{ MaxChannelDurationFlag = cli.Uint64Flag{
Name: "max-channel-duration", Name: "max-channel-duration",
Usage: "The maximum duration of L1-blocks to keep a channel open. 0 to disable.", Usage: "The maximum duration of L1-blocks to keep a channel open. 0 to disable.",
...@@ -110,23 +86,8 @@ var ( ...@@ -110,23 +86,8 @@ var (
Usage: "Initialize the batcher in a stopped state. The batcher can be started using the admin_startBatcher RPC", Usage: "Initialize the batcher in a stopped state. The batcher can be started using the admin_startBatcher RPC",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "STOPPED"), EnvVar: opservice.PrefixEnvVar(envVarPrefix, "STOPPED"),
} }
MnemonicFlag = cli.StringFlag{ // Legacy Flags
Name: "mnemonic", SequencerHDPathFlag = txmgr.SequencerHDPathFlag
Usage: "The mnemonic used to derive the wallets for either the " +
"sequencer or the l2output",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "MNEMONIC"),
}
SequencerHDPathFlag = cli.StringFlag{
Name: "sequencer-hd-path",
Usage: "The HD path used to derive the sequencer wallet from the " +
"mnemonic. The mnemonic flag must also be set.",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "SEQUENCER_HD_PATH"),
}
PrivateKeyFlag = cli.StringFlag{
Name: "private-key",
Usage: "The private key to use with the l2output wallet. Must not be used with mnemonic.",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "PRIVATE_KEY"),
}
) )
var requiredFlags = []cli.Flag{ var requiredFlags = []cli.Flag{
...@@ -135,9 +96,6 @@ var requiredFlags = []cli.Flag{ ...@@ -135,9 +96,6 @@ var requiredFlags = []cli.Flag{
RollupRpcFlag, RollupRpcFlag,
SubSafetyMarginFlag, SubSafetyMarginFlag,
PollIntervalFlag, PollIntervalFlag,
NumConfirmationsFlag,
SafeAbortNonceTooLowCountFlag,
ResubmissionTimeoutFlag,
} }
var optionalFlags = []cli.Flag{ var optionalFlags = []cli.Flag{
...@@ -147,9 +105,6 @@ var optionalFlags = []cli.Flag{ ...@@ -147,9 +105,6 @@ var optionalFlags = []cli.Flag{
TargetNumFramesFlag, TargetNumFramesFlag,
ApproxComprRatioFlag, ApproxComprRatioFlag,
StoppedFlag, StoppedFlag,
MnemonicFlag,
SequencerHDPathFlag,
PrivateKeyFlag,
} }
func init() { func init() {
...@@ -158,8 +113,8 @@ func init() { ...@@ -158,8 +113,8 @@ func init() {
optionalFlags = append(optionalFlags, oplog.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, oplog.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, opmetrics.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, opmetrics.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, oppprof.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, oppprof.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, opsigner.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, rpc.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, rpc.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, txmgr.CLIFlags(envVarPrefix)...)
Flags = append(requiredFlags, optionalFlags...) Flags = append(requiredFlags, optionalFlags...)
} }
......
...@@ -185,7 +185,7 @@ func (m *Metrics) RecordLatestL1Block(l1ref eth.L1BlockRef) { ...@@ -185,7 +185,7 @@ func (m *Metrics) RecordLatestL1Block(l1ref eth.L1BlockRef) {
m.RecordL1Ref("latest", l1ref) m.RecordL1Ref("latest", l1ref)
} }
// RecordL2BlockLoaded should be called when a new L2 block was loaded into the // RecordL2BlocksLoaded should be called when a new L2 block was loaded into the
// channel manager (but not processed yet). // channel manager (but not processed yet).
func (m *Metrics) RecordL2BlocksLoaded(l2ref eth.L2BlockRef) { func (m *Metrics) RecordL2BlocksLoaded(l2ref eth.L2BlockRef) {
m.RecordL2Ref(StageLoaded, l2ref) m.RecordL2Ref(StageLoaded, l2ref)
......
...@@ -6,7 +6,7 @@ import ( ...@@ -6,7 +6,7 @@ import (
type batcherClient interface { type batcherClient interface {
Start() error Start() error
Stop() error Stop(ctx context.Context) error
} }
type adminAPI struct { type adminAPI struct {
...@@ -23,6 +23,6 @@ func (a *adminAPI) StartBatcher(_ context.Context) error { ...@@ -23,6 +23,6 @@ func (a *adminAPI) StartBatcher(_ context.Context) error {
return a.b.Start() return a.b.Start()
} }
func (a *adminAPI) StopBatcher(_ context.Context) error { func (a *adminAPI) StopBatcher(ctx context.Context) error {
return a.b.Stop() return a.b.Stop(ctx)
} }
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -13,7 +13,7 @@ const OptimismPortalStorageLayoutJSON = "{\"storage\":[{\"astId\":1000,\"contrac ...@@ -13,7 +13,7 @@ const OptimismPortalStorageLayoutJSON = "{\"storage\":[{\"astId\":1000,\"contrac
var OptimismPortalStorageLayout = new(solc.StorageLayout) var OptimismPortalStorageLayout = new(solc.StorageLayout)
var OptimismPortalDeployedBin = "0x6080604052600436106101835760003560e01c80638456cb59116100d6578063ca3e99ba1161007f578063d53a822f11610059578063d53a822f146104ef578063e965084c1461050f578063e9e05c421461059b57600080fd5b8063ca3e99ba14610424578063cd7c978914610439578063cff0ab961461044e57600080fd5b80638c3152e9116100b05780638c3152e9146103a75780639bf62d82146103c7578063a14238e7146103f457600080fd5b80638456cb591461037a578063867ead131461038f5780638b4c40b0146101a857600080fd5b80635c1f2827116101385780636bb0291e116101125780636bb0291e146103115780636dbffb7814610326578063724c184c1461034657600080fd5b80635c1f28271461029d5780635c975abb146102cf57806364b79208146102f957600080fd5b80633f4ba83a116101695780633f4ba83a146102465780634870496f1461025b57806354fd4d501461027b57600080fd5b80621c2ff6146101af57806313620abd1461020d57600080fd5b366101aa576101a83334620186a06000604051806020016040528060008152506105a9565b005b600080fd5b3480156101bb57600080fd5b506101e37f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561021957600080fd5b50610225633b9aca0081565b6040516fffffffffffffffffffffffffffffffff9091168152602001610204565b34801561025257600080fd5b506101a86107c5565b34801561026757600080fd5b506101a8610276366004614c3e565b6108e8565b34801561028757600080fd5b50610290610f4e565b6040516102049190614d94565b3480156102a957600080fd5b506102c16fffffffffffffffffffffffffffffffff81565b604051908152602001610204565b3480156102db57600080fd5b506035546102e99060ff1681565b6040519015158152602001610204565b34801561030557600080fd5b506102c16301312d0081565b34801561031d57600080fd5b506102c1600a81565b34801561033257600080fd5b506102e9610341366004614da7565b610ff1565b34801561035257600080fd5b506101e37f000000000000000000000000000000000000000000000000000000000000000081565b34801561038657600080fd5b506101a86110c8565b34801561039b57600080fd5b506102c1633b9aca0081565b3480156103b357600080fd5b506101a86103c2366004614dc0565b6111e8565b3480156103d357600080fd5b506032546101e39073ffffffffffffffffffffffffffffffffffffffff1681565b34801561040057600080fd5b506102e961040f366004614da7565b60336020526000908152604090205460ff1681565b34801561043057600080fd5b506102c1611ac3565b34801561044557600080fd5b506102c1600881565b34801561045a57600080fd5b506001546104b6906fffffffffffffffffffffffffffffffff81169067ffffffffffffffff7001000000000000000000000000000000008204811691780100000000000000000000000000000000000000000000000090041683565b604080516fffffffffffffffffffffffffffffffff909416845267ffffffffffffffff9283166020850152911690820152606001610204565b3480156104fb57600080fd5b506101a861050a366004614e05565b611ad5565b34801561051b57600080fd5b5061056d61052a366004614da7565b603460205260009081526040902080546001909101546fffffffffffffffffffffffffffffffff8082169170010000000000000000000000000000000090041683565b604080519384526fffffffffffffffffffffffffffffffff9283166020850152911690820152606001610204565b6101a86105a9366004614e20565b8260005a905083156106605773ffffffffffffffffffffffffffffffffffffffff87161561066057604080517f08c379a00000000000000000000000000000000000000000000000000000000081526020600482015260248101919091527f4f7074696d69736d506f7274616c3a206d7573742073656e6420746f2061646460448201527f72657373283029207768656e206372656174696e67206120636f6e747261637460648201526084015b60405180910390fd5b6152088567ffffffffffffffff1610156106fc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f4f7074696d69736d506f7274616c3a20676173206c696d6974206d757374206360448201527f6f76657220696e737472696e7369632067617320636f737400000000000000006064820152608401610657565b3332811461071d575033731111000000000000000000000000000000001111015b60003488888888604051602001610738959493929190614ea5565b604051602081830303815290604052905060008973ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fb3813568d9991fc951961fcb4c784893574240a28925604d09fc577c55bb7c32846040516107a89190614d94565b60405180910390a450506107bc8282611cde565b50505050505050565b3373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000161461088a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602960248201527f4f7074696d69736d506f7274616c3a206f6e6c7920677561726469616e20636160448201527f6e20756e706175736500000000000000000000000000000000000000000000006064820152608401610657565b603580547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa906020015b60405180910390a1565b60355460ff1615610955576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f7074696d69736d506f7274616c3a20706175736564000000000000000000006044820152606401610657565b3073ffffffffffffffffffffffffffffffffffffffff16856040015173ffffffffffffffffffffffffffffffffffffffff1603610a14576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603f60248201527f4f7074696d69736d506f7274616c3a20796f752063616e6e6f742073656e642060448201527f6d6573736167657320746f2074686520706f7274616c20636f6e7472616374006064820152608401610657565b6040517fa25ae557000000000000000000000000000000000000000000000000000000008152600481018590526000907f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff169063a25ae55790602401606060405180830381865afa158015610aa2573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ac69190614f2a565b519050610ae0610adb36869003860186614f8f565b611fd7565b8114610b6e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602960248201527f4f7074696d69736d506f7274616c3a20696e76616c6964206f7574707574207260448201527f6f6f742070726f6f6600000000000000000000000000000000000000000000006064820152608401610657565b6000610b7987612033565b6000818152603460209081526040918290208251606081018452815481526001909101546fffffffffffffffffffffffffffffffff8082169383018490527001000000000000000000000000000000009091041692810192909252919250901580610cab5750805160408083015190517fa25ae5570000000000000000000000000000000000000000000000000000000081526fffffffffffffffffffffffffffffffff90911660048201527f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff169063a25ae55790602401606060405180830381865afa158015610c83573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ca79190614f2a565b5114155b610d37576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603760248201527f4f7074696d69736d506f7274616c3a207769746864726177616c20686173682060448201527f68617320616c7265616479206265656e2070726f76656e0000000000000000006064820152608401610657565b60408051602081018490526000918101829052606001604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815282825280516020918201209083018190529250610e009101604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152828201909152600182527f0100000000000000000000000000000000000000000000000000000000000000602083015290610df6888a614ff5565b8a60400135612063565b610e8c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4f7074696d69736d506f7274616c3a20696e76616c696420776974686472617760448201527f616c20696e636c7573696f6e2070726f6f6600000000000000000000000000006064820152608401610657565b604080516060810182528581526fffffffffffffffffffffffffffffffff42811660208084019182528c831684860190815260008981526034835286812095518655925190518416700100000000000000000000000000000000029316929092176001909301929092558b830151908c0151925173ffffffffffffffffffffffffffffffffffffffff918216939091169186917f67a6208cfcc0801d50f6cbe764733f4fddf66ac0b04442061a8a8c0cb6b63f629190a4505050505050505050565b6060610f797f0000000000000000000000000000000000000000000000000000000000000000612087565b610fa27f0000000000000000000000000000000000000000000000000000000000000000612087565b610fcb7f0000000000000000000000000000000000000000000000000000000000000000612087565b604051602001610fdd93929190615079565b604051602081830303815290604052905090565b6040517fa25ae557000000000000000000000000000000000000000000000000000000008152600481018290526000906110c29073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000169063a25ae55790602401606060405180830381865afa158015611083573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110a79190614f2a565b602001516fffffffffffffffffffffffffffffffff166121c4565b92915050565b3373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000161461118d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602760248201527f4f7074696d69736d506f7274616c3a206f6e6c7920677561726469616e20636160448201527f6e207061757365000000000000000000000000000000000000000000000000006064820152608401610657565b603580547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258906020016108de565b60355460ff1615611255576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f7074696d69736d506f7274616c3a20706175736564000000000000000000006044820152606401610657565b60325473ffffffffffffffffffffffffffffffffffffffff1661dead146112fe576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603f60248201527f4f7074696d69736d506f7274616c3a2063616e206f6e6c79207472696767657260448201527f206f6e65207769746864726177616c20706572207472616e73616374696f6e006064820152608401610657565b600061130982612033565b60008181526034602090815260408083208151606081018352815481526001909101546fffffffffffffffffffffffffffffffff808216948301859052700100000000000000000000000000000000909104169181019190915292935090036113f4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4f7074696d69736d506f7274616c3a207769746864726177616c20686173206e60448201527f6f74206265656e2070726f76656e2079657400000000000000000000000000006064820152608401610657565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663887862726040518163ffffffff1660e01b8152600401602060405180830381865afa15801561145f573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061148391906150ef565b81602001516fffffffffffffffffffffffffffffffff16101561154e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604b60248201527f4f7074696d69736d506f7274616c3a207769746864726177616c2074696d657360448201527f74616d70206c657373207468616e204c32204f7261636c65207374617274696e60648201527f672074696d657374616d70000000000000000000000000000000000000000000608482015260a401610657565b61156d81602001516fffffffffffffffffffffffffffffffff166121c4565b61161f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604560248201527f4f7074696d69736d506f7274616c3a2070726f76656e2077697468647261776160448201527f6c2066696e616c697a6174696f6e20706572696f6420686173206e6f7420656c60648201527f6170736564000000000000000000000000000000000000000000000000000000608482015260a401610657565b60408181015190517fa25ae5570000000000000000000000000000000000000000000000000000000081526fffffffffffffffffffffffffffffffff90911660048201526000907f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff169063a25ae55790602401606060405180830381865afa1580156116c4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116e89190614f2a565b82518151919250146117a2576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604960248201527f4f7074696d69736d506f7274616c3a206f757470757420726f6f742070726f7660448201527f656e206973206e6f74207468652073616d652061732063757272656e74206f7560648201527f7470757420726f6f740000000000000000000000000000000000000000000000608482015260a401610657565b6117c181602001516fffffffffffffffffffffffffffffffff166121c4565b611873576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604360248201527f4f7074696d69736d506f7274616c3a206f75747075742070726f706f73616c2060448201527f66696e616c697a6174696f6e20706572696f6420686173206e6f7420656c617060648201527f7365640000000000000000000000000000000000000000000000000000000000608482015260a401610657565b60008381526033602052604090205460ff1615611912576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603560248201527f4f7074696d69736d506f7274616c3a207769746864726177616c20686173206160448201527f6c7265616479206265656e2066696e616c697a656400000000000000000000006064820152608401610657565b600083815260336020908152604080832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055908601516032805473ffffffffffffffffffffffffffffffffffffffff9092167fffffffffffffffffffffffff00000000000000000000000000000000000000009092169190911790558501516080860151606087015160a08801516119b493929190612267565b603280547fffffffffffffffffffffffff00000000000000000000000000000000000000001661dead17905560405190915084907fdb5c7652857aa163daadd670e116628fb42e869d8ac4251ef8971d9e5727df1b90611a1990841515815260200190565b60405180910390a280158015611a2f5750326001145b15611abc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f4f7074696d69736d506f7274616c3a207769746864726177616c206661696c6560448201527f64000000000000000000000000000000000000000000000000000000000000006064820152608401610657565b5050505050565b611ad2600a6301312d00615166565b81565b600054610100900460ff1615808015611af55750600054600160ff909116105b80611b0f5750303b158015611b0f575060005460ff166001145b611b9b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a65640000000000000000000000000000000000006064820152608401610657565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790558015611bf957600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b603280547fffffffffffffffffffffffff00000000000000000000000000000000000000001661dead179055603580548315157fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00909116179055611c5b6122c1565b8015611cbe57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b73ffffffffffffffffffffffffffffffffffffffff163b151590565b600154600090611d14907801000000000000000000000000000000000000000000000000900467ffffffffffffffff16436151ce565b90508015611e4c576000611d2d600a6301312d00615166565b600154611d589190700100000000000000000000000000000000900467ffffffffffffffff166151e5565b905060006008611d6d600a6301312d00615166565b611d779190615259565b600154611d979084906fffffffffffffffffffffffffffffffff16615259565b611da19190615166565b600154909150600090611de690611dcb9084906fffffffffffffffffffffffffffffffff16615315565b633b9aca006fffffffffffffffffffffffffffffffff6123a4565b90506001841115611e0d57611e0a611dcb826008611e056001896151ce565b6123c3565b90505b6fffffffffffffffffffffffffffffffff16780100000000000000000000000000000000000000000000000067ffffffffffffffff4316021760015550505b60018054849190601090611e7f908490700100000000000000000000000000000000900467ffffffffffffffff16615389565b92506101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506301312d00600160000160109054906101000a900467ffffffffffffffff1667ffffffffffffffff161315611f5c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603e60248201527f5265736f757263654d65746572696e673a2063616e6e6f7420627579206d6f7260448201527f6520676173207468616e20617661696c61626c6520676173206c696d697400006064820152608401610657565b600154600090611f88906fffffffffffffffffffffffffffffffff1667ffffffffffffffff86166153b5565b90506000611f9a48633b9aca00612418565b611fa490836153f2565b905060005a611fb390866151ce565b905080821115611fcf57611fcf611fca82846151ce565b61242f565b505050505050565b60008160000151826020015183604001518460600151604051602001612016949392919093845260208401929092526040830152606082015260800190565b604051602081830303815290604052805190602001209050919050565b80516020808301516040808501516060860151608087015160a08801519351600097612016979096959101615406565b60008061206f8661245d565b905061207d8186868661248f565b9695505050505050565b6060816000036120ca57505060408051808201909152600181527f3000000000000000000000000000000000000000000000000000000000000000602082015290565b8160005b81156120f457806120de8161545d565b91506120ed9050600a836153f2565b91506120ce565b60008167ffffffffffffffff81111561210f5761210f614a64565b6040519080825280601f01601f191660200182016040528015612139576020820181803683370190505b5090505b84156121bc5761214e6001836151ce565b915061215b600a86615495565b6121669060306154a9565b60f81b81838151811061217b5761217b6154c1565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053506121b5600a866153f2565b945061213d565b949350505050565b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663f4daa2916040518163ffffffff1660e01b8152600401602060405180830381865afa158015612231573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061225591906150ef565b61225f90836154a9565b421192915050565b600080603f60c88601604002045a10156122aa576308c379a06000526020805278185361666543616c6c3a204e6f7420656e6f756768206761736058526064601cfd5b600080845160208601878a5af19695505050505050565b600054610100900460ff16612358576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152608401610657565b60408051606081018252633b9aca00808252600060208301524367ffffffffffffffff169190920181905278010000000000000000000000000000000000000000000000000217600155565b60006123b96123b385856124bf565b836124cf565b90505b9392505050565b6000670de0b6b3a76400006124046123db8583615166565b6123ed90670de0b6b3a76400006151e5565b6123ff85670de0b6b3a7640000615259565b6124de565b61240e9086615259565b6123b99190615166565b60008183101561242857816123bc565b5090919050565b6000805a90505b825a61244290836151ce565b1015612458576124518261545d565b9150612436565b505050565b6060818051906020012060405160200161247991815260200190565b6040516020818303038152906040529050919050565b60006124b6846124a087868661250f565b8051602091820120825192909101919091201490565b95945050505050565b60008183121561242857816123bc565b600081831261242857816123bc565b60006123bc670de0b6b3a7640000836124f686612f97565b6125009190615259565b61250a9190615166565b6131db565b6060600084511161257c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f4d65726b6c65547269653a20656d707479206b657900000000000000000000006044820152606401610657565b60006125878461341a565b9050600061259486613509565b90506000846040516020016125ab91815260200190565b60405160208183030381529060405290506000805b8451811015612f0e5760008582815181106125dd576125dd6154c1565b602002602001015190508451831115612678576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f4d65726b6c65547269653a206b657920696e646578206578636565647320746f60448201527f74616c206b6579206c656e6774680000000000000000000000000000000000006064820152608401610657565b8260000361273157805180516020918201206040516126c6926126a092910190815260200190565b604051602081830303815290604052858051602091820120825192909101919091201490565b61272c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f4d65726b6c65547269653a20696e76616c696420726f6f7420686173680000006044820152606401610657565b612888565b8051516020116127e7578051805160209182012060405161275b926126a092910190815260200190565b61272c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602760248201527f4d65726b6c65547269653a20696e76616c6964206c6172676520696e7465726e60448201527f616c2068617368000000000000000000000000000000000000000000000000006064820152608401610657565b805184516020808701919091208251919092012014612888576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4d65726b6c65547269653a20696e76616c696420696e7465726e616c206e6f6460448201527f65206861736800000000000000000000000000000000000000000000000000006064820152608401610657565b612894601060016154a9565b81602001515103612a755784518303612a0d5760006128d082602001516010815181106128c3576128c36154c1565b60200260200101516136a4565b90506000815111612963576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603b60248201527f4d65726b6c65547269653a2076616c7565206c656e677468206d75737420626560448201527f2067726561746572207468616e207a65726f20286272616e63682900000000006064820152608401610657565b6001875161297191906151ce565b83146129ff576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f4d65726b6c65547269653a2076616c7565206e6f6465206d757374206265206c60448201527f617374206e6f646520696e2070726f6f6620286272616e6368290000000000006064820152608401610657565b96506123bc95505050505050565b6000858481518110612a2157612a216154c1565b602001015160f81c60f81b60f81c9050600082602001518260ff1681518110612a4c57612a4c6154c1565b60200260200101519050612a5f81613804565b9550612a6c6001866154a9565b94505050612efb565b600281602001515103612e73576000612a8d82613829565b9050600081600081518110612aa457612aa46154c1565b016020015160f81c90506000612abb6002836154f0565b612ac6906002615512565b90506000612ad7848360ff1661384d565b90506000612ae58a8961384d565b90506000612af38383613883565b905080835114612b85576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f4d65726b6c65547269653a20706174682072656d61696e646572206d7573742060448201527f736861726520616c6c206e6962626c65732077697468206b65790000000000006064820152608401610657565b60ff851660021480612b9a575060ff85166003145b15612d8e5780825114612c2f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603d60248201527f4d65726b6c65547269653a206b65792072656d61696e646572206d757374206260448201527f65206964656e746963616c20746f20706174682072656d61696e6465720000006064820152608401610657565b6000612c4b88602001516001815181106128c3576128c36154c1565b90506000815111612cde576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f4d65726b6c65547269653a2076616c7565206c656e677468206d75737420626560448201527f2067726561746572207468616e207a65726f20286c65616629000000000000006064820152608401610657565b60018d51612cec91906151ce565b8914612d7a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f4d65726b6c65547269653a2076616c7565206e6f6465206d757374206265206c60448201527f617374206e6f646520696e2070726f6f6620286c6561662900000000000000006064820152608401610657565b9c506123bc9b505050505050505050505050565b60ff85161580612da1575060ff85166001145b15612de057612dcd8760200151600181518110612dc057612dc06154c1565b6020026020010151613804565b9950612dd9818a6154a9565b9850612e68565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4d65726b6c65547269653a2072656365697665642061206e6f6465207769746860448201527f20616e20756e6b6e6f776e2070726566697800000000000000000000000000006064820152608401610657565b505050505050612efb565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602860248201527f4d65726b6c65547269653a20726563656976656420616e20756e70617273656160448201527f626c65206e6f64650000000000000000000000000000000000000000000000006064820152608401610657565b5080612f068161545d565b9150506125c0565b506040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f4d65726b6c65547269653a2072616e206f7574206f662070726f6f6620656c6560448201527f6d656e74730000000000000000000000000000000000000000000000000000006064820152608401610657565b6000808213613002576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600960248201527f554e444546494e454400000000000000000000000000000000000000000000006044820152606401610657565b6000606061300f84613932565b03609f8181039490941b90931c6c465772b2bbbb5f824b15207a3081018102606090811d6d0388eaa27412d5aca026815d636e018202811d6d0df99ac502031bf953eff472fdcc018202811d6d13cdffb29d51d99322bdff5f2211018202811d6d0a0f742023def783a307a986912e018202811d6d01920d8043ca89b5239253284e42018202811d6c0b7a86d7375468fac667a0a527016c29508e458543d8aa4df2abee7883018302821d6d0139601a2efabe717e604cbb4894018302821d6d02247f7a7b6594320649aa03aba1018302821d7fffffffffffffffffffffffffffffffffffffff73c0c716a594e00d54e3c4cbc9018302821d7ffffffffffffffffffffffffffffffffffffffdc7b88c420e53a9890533129f6f01830290911d7fffffffffffffffffffffffffffffffffffffff465fda27eb4d63ded474e5f832019091027ffffffffffffffff5f6af8f7b3396644f18e157960000000000000000000000000105711340daa0d5f769dba1915cef59f0815a5506027d0267a36c0c95b3975ab3ee5b203a7614a3f75373f047d803ae7b6687f2b393909302929092017d57115e47018c7177eebf7cd370a3356a1b7863008a5ae8028c72b88642840160ae1d92915050565b60007ffffffffffffffffffffffffffffffffffffffffffffffffdb731c958f34d94c1821361320c57506000919050565b680755bf798b4a1bf1e5821261327e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f4558505f4f564552464c4f5700000000000000000000000000000000000000006044820152606401610657565b6503782dace9d9604e83901b059150600060606bb17217f7d1cf79abc9e3b39884821b056b80000000000000000000000001901d6bb17217f7d1cf79abc9e3b39881029093037fffffffffffffffffffffffffffffffffffffffdbf3ccf1604d263450f02a550481018102606090811d6d0277594991cfc85f6e2461837cd9018202811d7fffffffffffffffffffffffffffffffffffffe5adedaa1cb095af9e4da10e363c018202811d6db1bbb201f443cf962f1a1d3db4a5018202811d7ffffffffffffffffffffffffffffffffffffd38dc772608b0ae56cce01296c0eb018202811d6e05180bb14799ab47a8a8cb2a527d57016d02d16720577bd19bf614176fe9ea6c10fe68e7fd37d0007b713f765084018402831d9081019084017ffffffffffffffffffffffffffffffffffffffe2c69812cf03b0763fd454a8f7e010290911d6e0587f503bb6ea29d25fcb7401964500190910279d835ebba824c98fb31b83b2ca45c000000000000000000000000010574029d9dc38563c32e5c2f6dc192ee70ef65f9978af30260c3939093039290921c92915050565b805160609060008167ffffffffffffffff81111561343a5761343a614a64565b60405190808252806020026020018201604052801561347f57816020015b60408051808201909152606080825260208201528152602001906001900390816134585790505b50905060005b828110156135015760405180604001604052808683815181106134aa576134aa6154c1565b602002602001015181526020016134d98784815181106134cc576134cc6154c1565b6020026020010151613a08565b8152508282815181106134ee576134ee6154c1565b6020908102919091010152600101613485565b509392505050565b8051606090600061351b8260026153b5565b67ffffffffffffffff81111561353357613533614a64565b6040519080825280601f01601f19166020018201604052801561355d576020820181803683370190505b5090506000805b8381101561369a5785818151811061357e5761357e6154c1565b6020910101517fff000000000000000000000000000000000000000000000000000000000000008116925060041c7f0ff000000000000000000000000000000000000000000000000000000000000016836135da8360026153b5565b815181106135ea576135ea6154c1565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053507f0f000000000000000000000000000000000000000000000000000000000000008216836136488360026153b5565b6136539060016154a9565b81518110613663576136636154c1565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350600101613564565b5090949350505050565b606060008060006136b485613a1b565b9194509250905060008160018111156136cf576136cf615535565b1461375c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f524c505265616465723a206465636f646564206974656d207479706520666f7260448201527f206279746573206973206e6f7420612064617461206974656d000000000000006064820152608401610657565b61376682846154a9565b8551146137f5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603460248201527f524c505265616465723a2062797465732076616c756520636f6e7461696e732060448201527f616e20696e76616c69642072656d61696e6465720000000000000000000000006064820152608401610657565b6124b685602001518484614488565b606060208260000151106138205761381b826136a4565b6110c2565b6110c282614529565b60606110c261384883602001516000815181106128c3576128c36154c1565b613509565b60608251821061386c57506040805160208101909152600081526110c2565b6123bc838384865161387e91906151ce565b61453f565b6000806000835185511061389857835161389b565b84515b90505b808210801561392257508382815181106138ba576138ba6154c1565b602001015160f81c60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168583815181106138f9576138f96154c1565b01602001517fff0000000000000000000000000000000000000000000000000000000000000016145b156135015781600101915061389e565b600080821161399d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600960248201527f554e444546494e454400000000000000000000000000000000000000000000006044820152606401610657565b5060016fffffffffffffffffffffffffffffffff821160071b82811c67ffffffffffffffff1060061b1782811c63ffffffff1060051b1782811c61ffff1060041b1782811c60ff10600390811b90911783811c600f1060021b1783811c909110821b1791821c111790565b60606110c2613a1683614717565b614800565b600080600080846000015111613ad9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f524c505265616465723a206c656e677468206f6620616e20524c50206974656d60448201527f206d7573742062652067726561746572207468616e207a65726f20746f20626560648201527f206465636f6461626c6500000000000000000000000000000000000000000000608482015260a401610657565b6020840151805160001a607f8111613afe576000600160009450945094505050614481565b60b78111613d0c576000613b136080836151ce565b905080876000015111613bce576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604e60248201527f524c505265616465723a206c656e677468206f6620636f6e74656e74206d757360448201527f742062652067726561746572207468616e20737472696e67206c656e6774682060648201527f2873686f727420737472696e6729000000000000000000000000000000000000608482015260a401610657565b6001838101517fff00000000000000000000000000000000000000000000000000000000000000169082141580613c4757507f80000000000000000000000000000000000000000000000000000000000000007fff00000000000000000000000000000000000000000000000000000000000000821610155b613cf9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604d60248201527f524c505265616465723a20696e76616c6964207072656669782c2073696e676c60448201527f652062797465203c203078383020617265206e6f74207072656669786564202860648201527f73686f727420737472696e672900000000000000000000000000000000000000608482015260a401610657565b5060019550935060009250614481915050565b60bf811161405a576000613d2160b7836151ce565b905080876000015111613ddc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152605160248201527f524c505265616465723a206c656e677468206f6620636f6e74656e74206d757360448201527f74206265203e207468616e206c656e677468206f6620737472696e67206c656e60648201527f67746820286c6f6e6720737472696e6729000000000000000000000000000000608482015260a401610657565b60018301517fff00000000000000000000000000000000000000000000000000000000000000166000819003613eba576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f524c505265616465723a206c656e677468206f6620636f6e74656e74206d757360448201527f74206e6f74206861766520616e79206c656164696e67207a65726f7320286c6f60648201527f6e6720737472696e672900000000000000000000000000000000000000000000608482015260a401610657565b600184015160088302610100031c60378111613f7e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604860248201527f524c505265616465723a206c656e677468206f6620636f6e74656e74206d757360448201527f742062652067726561746572207468616e20353520627974657320286c6f6e6760648201527f20737472696e6729000000000000000000000000000000000000000000000000608482015260a401610657565b613f8881846154a9565b89511161403d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604c60248201527f524c505265616465723a206c656e677468206f6620636f6e74656e74206d757360448201527f742062652067726561746572207468616e20746f74616c206c656e677468202860648201527f6c6f6e6720737472696e67290000000000000000000000000000000000000000608482015260a401610657565b6140488360016154a9565b97509550600094506144819350505050565b60f7811161413b57600061406f60c0836151ce565b90508087600001511161412a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f524c505265616465723a206c656e677468206f6620636f6e74656e74206d757360448201527f742062652067726561746572207468616e206c697374206c656e67746820287360648201527f686f7274206c6973742900000000000000000000000000000000000000000000608482015260a401610657565b600195509350849250614481915050565b600061414860f7836151ce565b905080876000015111614203576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604d60248201527f524c505265616465723a206c656e677468206f6620636f6e74656e74206d757360448201527f74206265203e207468616e206c656e677468206f66206c697374206c656e677460648201527f6820286c6f6e67206c6973742900000000000000000000000000000000000000608482015260a401610657565b60018301517fff000000000000000000000000000000000000000000000000000000000000001660008190036142e1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604860248201527f524c505265616465723a206c656e677468206f6620636f6e74656e74206d757360448201527f74206e6f74206861766520616e79206c656164696e67207a65726f7320286c6f60648201527f6e67206c69737429000000000000000000000000000000000000000000000000608482015260a401610657565b600184015160088302610100031c603781116143a5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604660248201527f524c505265616465723a206c656e677468206f6620636f6e74656e74206d757360448201527f742062652067726561746572207468616e20353520627974657320286c6f6e6760648201527f206c697374290000000000000000000000000000000000000000000000000000608482015260a401610657565b6143af81846154a9565b895111614464576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f524c505265616465723a206c656e677468206f6620636f6e74656e74206d757360448201527f742062652067726561746572207468616e20746f74616c206c656e677468202860648201527f6c6f6e67206c6973742900000000000000000000000000000000000000000000608482015260a401610657565b61446f8360016154a9565b97509550600194506144819350505050565b9193909250565b606060008267ffffffffffffffff8111156144a5576144a5614a64565b6040519080825280601f01601f1916602001820160405280156144cf576020820181803683370190505b509050826000036144e15790506123bc565b60006144ed85876154a9565b90506020820160005b8581101561450e5782810151828201526020016144f6565b8581111561451d576000868301525b50919695505050505050565b60606110c2826020015160008460000151614488565b60608182601f0110156145ae576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f736c6963655f6f766572666c6f770000000000000000000000000000000000006044820152606401610657565b82828401101561461a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f736c6963655f6f766572666c6f770000000000000000000000000000000000006044820152606401610657565b81830184511015614687576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f736c6963655f6f75744f66426f756e64730000000000000000000000000000006044820152606401610657565b6060821580156146a6576040519150600082526020820160405261470e565b6040519150601f8416801560200281840101858101878315602002848b0101015b818310156146df5780518352602092830192016146c7565b5050858452601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016604052505b50949350505050565b604080518082019091526000808252602082015260008251116147e2576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f524c505265616465723a206c656e677468206f6620616e20524c50206974656d60448201527f206d7573742062652067726561746572207468616e207a65726f20746f20626560648201527f206465636f6461626c6500000000000000000000000000000000000000000000608482015260a401610657565b50604080518082019091528151815260209182019181019190915290565b6060600080600061481085613a1b565b91945092509050600181600181111561482b5761482b615535565b146148b8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f524c505265616465723a206465636f646564206974656d207479706520666f7260448201527f206c697374206973206e6f742061206c697374206974656d00000000000000006064820152608401610657565b84516148c483856154a9565b14614951576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f524c505265616465723a206c697374206974656d2068617320616e20696e766160448201527f6c696420646174612072656d61696e64657200000000000000000000000000006064820152608401610657565b6040805160208082526104208201909252600091816020015b604080518082019091526000808252602082015281526020019060019003908161496a5790505090506000845b8751811015614a58576000806149dd6040518060400160405280858d600001516149c191906151ce565b8152602001858d602001516149d691906154a9565b9052613a1b565b5091509150604051806040016040528083836149f991906154a9565b8152602001848c60200151614a0e91906154a9565b815250858581518110614a2357614a236154c1565b6020908102919091010152614a396001856154a9565b9350614a4581836154a9565b614a4f90846154a9565b92505050614997565b50815295945050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715614ada57614ada614a64565b604052919050565b803573ffffffffffffffffffffffffffffffffffffffff81168114614b0657600080fd5b919050565b600082601f830112614b1c57600080fd5b813567ffffffffffffffff811115614b3657614b36614a64565b614b6760207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601614a93565b818152846020838601011115614b7c57600080fd5b816020850160208301376000918101602001919091529392505050565b600060c08284031215614bab57600080fd5b60405160c0810167ffffffffffffffff8282108183111715614bcf57614bcf614a64565b8160405282935084358352614be660208601614ae2565b6020840152614bf760408601614ae2565b6040840152606085013560608401526080850135608084015260a0850135915080821115614c2457600080fd5b50614c3185828601614b0b565b60a0830152505092915050565b600080600080600085870360e0811215614c5757600080fd5b863567ffffffffffffffff80821115614c6f57600080fd5b614c7b8a838b01614b99565b97506020890135965060807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc084011215614cb457600080fd5b60408901955060c0890135925080831115614cce57600080fd5b828901925089601f840112614ce257600080fd5b8235915080821115614cf357600080fd5b508860208260051b8401011115614d0957600080fd5b959894975092955050506020019190565b60005b83811015614d35578181015183820152602001614d1d565b83811115614d44576000848401525b50505050565b60008151808452614d62816020860160208601614d1a565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006123bc6020830184614d4a565b600060208284031215614db957600080fd5b5035919050565b600060208284031215614dd257600080fd5b813567ffffffffffffffff811115614de957600080fd5b6121bc84828501614b99565b80358015158114614b0657600080fd5b600060208284031215614e1757600080fd5b6123bc82614df5565b600080600080600060a08688031215614e3857600080fd5b614e4186614ae2565b945060208601359350604086013567ffffffffffffffff8082168214614e6657600080fd5b819450614e7560608901614df5565b93506080880135915080821115614e8b57600080fd5b50614e9888828901614b0b565b9150509295509295909350565b8581528460208201527fffffffffffffffff0000000000000000000000000000000000000000000000008460c01b16604082015282151560f81b604882015260008251614ef9816049850160208701614d1a565b919091016049019695505050505050565b80516fffffffffffffffffffffffffffffffff81168114614b0657600080fd5b600060608284031215614f3c57600080fd5b6040516060810181811067ffffffffffffffff82111715614f5f57614f5f614a64565b60405282518152614f7260208401614f0a565b6020820152614f8360408401614f0a565b60408201529392505050565b600060808284031215614fa157600080fd5b6040516080810181811067ffffffffffffffff82111715614fc457614fc4614a64565b8060405250823581526020830135602082015260408301356040820152606083013560608201528091505092915050565b600067ffffffffffffffff8084111561501057615010614a64565b8360051b6020615021818301614a93565b86815291850191818101903684111561503957600080fd5b865b8481101561506d578035868111156150535760008081fd5b61505f36828b01614b0b565b84525091830191830161503b565b50979650505050505050565b6000845161508b818460208901614d1a565b80830190507f2e0000000000000000000000000000000000000000000000000000000000000080825285516150c7816001850160208a01614d1a565b600192019182015283516150e2816002840160208801614d1a565b0160020195945050505050565b60006020828403121561510157600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60008261517557615175615108565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83147f8000000000000000000000000000000000000000000000000000000000000000831416156151c9576151c9615137565b500590565b6000828210156151e0576151e0615137565b500390565b6000808312837f80000000000000000000000000000000000000000000000000000000000000000183128115161561521f5761521f615137565b837f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01831381161561525357615253615137565b50500390565b60007f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60008413600084138583048511828216161561529a5761529a615137565b7f800000000000000000000000000000000000000000000000000000000000000060008712868205881281841616156152d5576152d5615137565b600087129250878205871284841616156152f1576152f1615137565b8785058712818416161561530757615307615137565b505050929093029392505050565b6000808212827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0384138115161561534f5761534f615137565b827f800000000000000000000000000000000000000000000000000000000000000003841281161561538357615383615137565b50500190565b600067ffffffffffffffff8083168185168083038211156153ac576153ac615137565b01949350505050565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04831182151516156153ed576153ed615137565b500290565b60008261540157615401615108565b500490565b868152600073ffffffffffffffffffffffffffffffffffffffff808816602084015280871660408401525084606083015283608083015260c060a083015261545160c0830184614d4a565b98975050505050505050565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361548e5761548e615137565b5060010190565b6000826154a4576154a4615108565b500690565b600082198211156154bc576154bc615137565b500190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600060ff83168061550357615503615108565b8060ff84160691505092915050565b600060ff821660ff84168082101561552c5761552c615137565b90039392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fdfea164736f6c634300080f000a" var OptimismPortalDeployedBin = "0x6080604052600436106101115760003560e01c80638b4c40b0116100a5578063cff0ab9611610074578063e965084c11610059578063e965084c146103c3578063e9e05c421461044f578063f04987501461046257600080fd5b8063cff0ab9614610302578063d53a822f146103a357600080fd5b80638b4c40b0146101365780638c3152e9146102855780639bf62d82146102a5578063a14238e7146102d257600080fd5b80635c975abb116100e15780635c975abb146101f25780636dbffb781461021c578063724c184c1461023c5780638456cb591461027057600080fd5b80621c2ff61461013d5780633f4ba83a1461019b5780634870496f146101b057806354fd4d50146101d057600080fd5b36610138576101363334620186a0600060405180602001604052806000815250610496565b005b600080fd5b34801561014957600080fd5b506101717f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b3480156101a757600080fd5b506101366106b2565b3480156101bc57600080fd5b506101366101cb366004614c13565b6107d5565b3480156101dc57600080fd5b506101e5610e3b565b6040516101929190614d69565b3480156101fe57600080fd5b5060355461020c9060ff1681565b6040519015158152602001610192565b34801561022857600080fd5b5061020c610237366004614d7c565b610ede565b34801561024857600080fd5b506101717f000000000000000000000000000000000000000000000000000000000000000081565b34801561027c57600080fd5b50610136610fb5565b34801561029157600080fd5b506101366102a0366004614d95565b6110d5565b3480156102b157600080fd5b506032546101719073ffffffffffffffffffffffffffffffffffffffff1681565b3480156102de57600080fd5b5061020c6102ed366004614d7c565b60336020526000908152604090205460ff1681565b34801561030e57600080fd5b5060015461036a906fffffffffffffffffffffffffffffffff81169067ffffffffffffffff7001000000000000000000000000000000008204811691780100000000000000000000000000000000000000000000000090041683565b604080516fffffffffffffffffffffffffffffffff909416845267ffffffffffffffff9283166020850152911690820152606001610192565b3480156103af57600080fd5b506101366103be366004614dda565b6119b0565b3480156103cf57600080fd5b506104216103de366004614d7c565b603460205260009081526040902080546001909101546fffffffffffffffffffffffffffffffff8082169170010000000000000000000000000000000090041683565b604080519384526fffffffffffffffffffffffffffffffff9283166020850152911690820152606001610192565b61013661045d366004614df5565b610496565b34801561046e57600080fd5b506101717f000000000000000000000000000000000000000000000000000000000000000081565b8260005a9050831561054d5773ffffffffffffffffffffffffffffffffffffffff87161561054d57604080517f08c379a00000000000000000000000000000000000000000000000000000000081526020600482015260248101919091527f4f7074696d69736d506f7274616c3a206d7573742073656e6420746f2061646460448201527f72657373283029207768656e206372656174696e67206120636f6e747261637460648201526084015b60405180910390fd5b6152088567ffffffffffffffff1610156105e9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f4f7074696d69736d506f7274616c3a20676173206c696d6974206d757374206360448201527f6f76657220696e737472696e7369632067617320636f737400000000000000006064820152608401610544565b3332811461060a575033731111000000000000000000000000000000001111015b60003488888888604051602001610625959493929190614e7a565b604051602081830303815290604052905060008973ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fb3813568d9991fc951961fcb4c784893574240a28925604d09fc577c55bb7c32846040516106959190614d69565b60405180910390a450506106a98282611bb9565b50505050505050565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610777576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602960248201527f4f7074696d69736d506f7274616c3a206f6e6c7920677561726469616e20636160448201527f6e20756e706175736500000000000000000000000000000000000000000000006064820152608401610544565b603580547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa906020015b60405180910390a1565b60355460ff1615610842576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f7074696d69736d506f7274616c3a20706175736564000000000000000000006044820152606401610544565b3073ffffffffffffffffffffffffffffffffffffffff16856040015173ffffffffffffffffffffffffffffffffffffffff1603610901576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603f60248201527f4f7074696d69736d506f7274616c3a20796f752063616e6e6f742073656e642060448201527f6d6573736167657320746f2074686520706f7274616c20636f6e7472616374006064820152608401610544565b6040517fa25ae557000000000000000000000000000000000000000000000000000000008152600481018590526000907f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff169063a25ae55790602401606060405180830381865afa15801561098f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109b39190614eff565b5190506109cd6109c836869003860186614f64565b611ee6565b8114610a5b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602960248201527f4f7074696d69736d506f7274616c3a20696e76616c6964206f7574707574207260448201527f6f6f742070726f6f6600000000000000000000000000000000000000000000006064820152608401610544565b6000610a6687611f42565b6000818152603460209081526040918290208251606081018452815481526001909101546fffffffffffffffffffffffffffffffff8082169383018490527001000000000000000000000000000000009091041692810192909252919250901580610b985750805160408083015190517fa25ae5570000000000000000000000000000000000000000000000000000000081526fffffffffffffffffffffffffffffffff90911660048201527f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff169063a25ae55790602401606060405180830381865afa158015610b70573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b949190614eff565b5114155b610c24576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603760248201527f4f7074696d69736d506f7274616c3a207769746864726177616c20686173682060448201527f68617320616c7265616479206265656e2070726f76656e0000000000000000006064820152608401610544565b60408051602081018490526000918101829052606001604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815282825280516020918201209083018190529250610ced9101604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152828201909152600182527f0100000000000000000000000000000000000000000000000000000000000000602083015290610ce3888a614fca565b8a60400135611f72565b610d79576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4f7074696d69736d506f7274616c3a20696e76616c696420776974686472617760448201527f616c20696e636c7573696f6e2070726f6f6600000000000000000000000000006064820152608401610544565b604080516060810182528581526fffffffffffffffffffffffffffffffff42811660208084019182528c831684860190815260008981526034835286812095518655925190518416700100000000000000000000000000000000029316929092176001909301929092558b830151908c0151925173ffffffffffffffffffffffffffffffffffffffff918216939091169186917f67a6208cfcc0801d50f6cbe764733f4fddf66ac0b04442061a8a8c0cb6b63f629190a4505050505050505050565b6060610e667f0000000000000000000000000000000000000000000000000000000000000000611f96565b610e8f7f0000000000000000000000000000000000000000000000000000000000000000611f96565b610eb87f0000000000000000000000000000000000000000000000000000000000000000611f96565b604051602001610eca9392919061504e565b604051602081830303815290604052905090565b6040517fa25ae55700000000000000000000000000000000000000000000000000000000815260048101829052600090610faf9073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000169063a25ae55790602401606060405180830381865afa158015610f70573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f949190614eff565b602001516fffffffffffffffffffffffffffffffff166120d3565b92915050565b3373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000161461107a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602760248201527f4f7074696d69736d506f7274616c3a206f6e6c7920677561726469616e20636160448201527f6e207061757365000000000000000000000000000000000000000000000000006064820152608401610544565b603580547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258906020016107cb565b60355460ff1615611142576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f7074696d69736d506f7274616c3a20706175736564000000000000000000006044820152606401610544565b60325473ffffffffffffffffffffffffffffffffffffffff1661dead146111eb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603f60248201527f4f7074696d69736d506f7274616c3a2063616e206f6e6c79207472696767657260448201527f206f6e65207769746864726177616c20706572207472616e73616374696f6e006064820152608401610544565b60006111f682611f42565b60008181526034602090815260408083208151606081018352815481526001909101546fffffffffffffffffffffffffffffffff808216948301859052700100000000000000000000000000000000909104169181019190915292935090036112e1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4f7074696d69736d506f7274616c3a207769746864726177616c20686173206e60448201527f6f74206265656e2070726f76656e2079657400000000000000000000000000006064820152608401610544565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663887862726040518163ffffffff1660e01b8152600401602060405180830381865afa15801561134c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061137091906150c4565b81602001516fffffffffffffffffffffffffffffffff16101561143b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604b60248201527f4f7074696d69736d506f7274616c3a207769746864726177616c2074696d657360448201527f74616d70206c657373207468616e204c32204f7261636c65207374617274696e60648201527f672074696d657374616d70000000000000000000000000000000000000000000608482015260a401610544565b61145a81602001516fffffffffffffffffffffffffffffffff166120d3565b61150c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604560248201527f4f7074696d69736d506f7274616c3a2070726f76656e2077697468647261776160448201527f6c2066696e616c697a6174696f6e20706572696f6420686173206e6f7420656c60648201527f6170736564000000000000000000000000000000000000000000000000000000608482015260a401610544565b60408181015190517fa25ae5570000000000000000000000000000000000000000000000000000000081526fffffffffffffffffffffffffffffffff90911660048201526000907f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff169063a25ae55790602401606060405180830381865afa1580156115b1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115d59190614eff565b825181519192501461168f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604960248201527f4f7074696d69736d506f7274616c3a206f757470757420726f6f742070726f7660448201527f656e206973206e6f74207468652073616d652061732063757272656e74206f7560648201527f7470757420726f6f740000000000000000000000000000000000000000000000608482015260a401610544565b6116ae81602001516fffffffffffffffffffffffffffffffff166120d3565b611760576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604360248201527f4f7074696d69736d506f7274616c3a206f75747075742070726f706f73616c2060448201527f66696e616c697a6174696f6e20706572696f6420686173206e6f7420656c617060648201527f7365640000000000000000000000000000000000000000000000000000000000608482015260a401610544565b60008381526033602052604090205460ff16156117ff576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603560248201527f4f7074696d69736d506f7274616c3a207769746864726177616c20686173206160448201527f6c7265616479206265656e2066696e616c697a656400000000000000000000006064820152608401610544565b600083815260336020908152604080832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055908601516032805473ffffffffffffffffffffffffffffffffffffffff9092167fffffffffffffffffffffffff00000000000000000000000000000000000000009092169190911790558501516080860151606087015160a08801516118a193929190612176565b603280547fffffffffffffffffffffffff00000000000000000000000000000000000000001661dead17905560405190915084907fdb5c7652857aa163daadd670e116628fb42e869d8ac4251ef8971d9e5727df1b9061190690841515815260200190565b60405180910390a28015801561191c5750326001145b156119a9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f4f7074696d69736d506f7274616c3a207769746864726177616c206661696c6560448201527f64000000000000000000000000000000000000000000000000000000000000006064820152608401610544565b5050505050565b600054610100900460ff16158080156119d05750600054600160ff909116105b806119ea5750303b1580156119ea575060005460ff166001145b611a76576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a65640000000000000000000000000000000000006064820152608401610544565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790558015611ad457600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b603280547fffffffffffffffffffffffff00000000000000000000000000000000000000001661dead179055603580548315157fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00909116179055611b366121d0565b8015611b9957600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b73ffffffffffffffffffffffffffffffffffffffff163b151590565b600154600090611bef907801000000000000000000000000000000000000000000000000900467ffffffffffffffff164361510c565b90506000611bfb6122b3565b90506000816020015160ff16826000015163ffffffff16611c1c9190615152565b90508215611d5357600154600090611c53908390700100000000000000000000000000000000900467ffffffffffffffff166151ba565b90506000836040015160ff1683611c6a919061522e565b600154611c8a9084906fffffffffffffffffffffffffffffffff1661522e565b611c949190615152565b600154909150600090611ce590611cbe9084906fffffffffffffffffffffffffffffffff166152ea565b866060015163ffffffff168760a001516fffffffffffffffffffffffffffffffff16612379565b90506001861115611d1457611d11611cbe82876040015160ff1660018a611d0c919061510c565b612398565b90505b6fffffffffffffffffffffffffffffffff16780100000000000000000000000000000000000000000000000067ffffffffffffffff4316021760015550505b60018054869190601090611d86908490700100000000000000000000000000000000900467ffffffffffffffff1661535e565b92506101000a81548167ffffffffffffffff021916908367ffffffffffffffff160217905550816000015163ffffffff16600160000160109054906101000a900467ffffffffffffffff1667ffffffffffffffff161315611e69576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603e60248201527f5265736f757263654d65746572696e673a2063616e6e6f7420627579206d6f7260448201527f6520676173207468616e20617661696c61626c6520676173206c696d697400006064820152608401610544565b600154600090611e95906fffffffffffffffffffffffffffffffff1667ffffffffffffffff881661538a565b90506000611ea748633b9aca006123ed565b611eb190836153c7565b905060005a611ec0908861510c565b905080821115611edc57611edc611ed7828461510c565b612404565b5050505050505050565b60008160000151826020015183604001518460600151604051602001611f25949392919093845260208401929092526040830152606082015260800190565b604051602081830303815290604052805190602001209050919050565b80516020808301516040808501516060860151608087015160a08801519351600097611f259790969591016153db565b600080611f7e86612432565b9050611f8c81868686612464565b9695505050505050565b606081600003611fd957505060408051808201909152600181527f3000000000000000000000000000000000000000000000000000000000000000602082015290565b8160005b81156120035780611fed81615432565b9150611ffc9050600a836153c7565b9150611fdd565b60008167ffffffffffffffff81111561201e5761201e614a39565b6040519080825280601f01601f191660200182016040528015612048576020820181803683370190505b5090505b84156120cb5761205d60018361510c565b915061206a600a8661546a565b61207590603061547e565b60f81b81838151811061208a5761208a615496565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053506120c4600a866153c7565b945061204c565b949350505050565b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663f4daa2916040518163ffffffff1660e01b8152600401602060405180830381865afa158015612140573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061216491906150c4565b61216e908361547e565b421192915050565b600080603f60c88601604002045a10156121b9576308c379a06000526020805278185361666543616c6c3a204e6f7420656e6f756768206761736058526064601cfd5b600080845160208601878a5af19695505050505050565b600054610100900460ff16612267576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152608401610544565b60408051606081018252633b9aca00808252600060208301524367ffffffffffffffff169190920181905278010000000000000000000000000000000000000000000000000217600155565b6040805160c081018252600080825260208201819052918101829052606081018290526080810182905260a08101919091527f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663cc731b026040518163ffffffff1660e01b815260040160c060405180830381865afa158015612350573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061237491906154ea565b905090565b600061238e6123888585612494565b836124a4565b90505b9392505050565b6000670de0b6b3a76400006123d96123b08583615152565b6123c290670de0b6b3a76400006151ba565b6123d485670de0b6b3a764000061522e565b6124b3565b6123e3908661522e565b61238e9190615152565b6000818310156123fd5781612391565b5090919050565b6000805a90505b825a612417908361510c565b101561242d5761242682615432565b915061240b565b505050565b6060818051906020012060405160200161244e91815260200190565b6040516020818303038152906040529050919050565b600061248b846124758786866124e4565b8051602091820120825192909101919091201490565b95945050505050565b6000818312156123fd5781612391565b60008183126123fd5781612391565b6000612391670de0b6b3a7640000836124cb86612f6c565b6124d5919061522e565b6124df9190615152565b6131b0565b60606000845111612551576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f4d65726b6c65547269653a20656d707479206b657900000000000000000000006044820152606401610544565b600061255c846133ef565b90506000612569866134de565b905060008460405160200161258091815260200190565b60405160208183030381529060405290506000805b8451811015612ee35760008582815181106125b2576125b2615496565b60200260200101519050845183111561264d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f4d65726b6c65547269653a206b657920696e646578206578636565647320746f60448201527f74616c206b6579206c656e6774680000000000000000000000000000000000006064820152608401610544565b82600003612706578051805160209182012060405161269b9261267592910190815260200190565b604051602081830303815290604052858051602091820120825192909101919091201490565b612701576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f4d65726b6c65547269653a20696e76616c696420726f6f7420686173680000006044820152606401610544565b61285d565b8051516020116127bc57805180516020918201206040516127309261267592910190815260200190565b612701576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602760248201527f4d65726b6c65547269653a20696e76616c6964206c6172676520696e7465726e60448201527f616c2068617368000000000000000000000000000000000000000000000000006064820152608401610544565b80518451602080870191909120825191909201201461285d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4d65726b6c65547269653a20696e76616c696420696e7465726e616c206e6f6460448201527f65206861736800000000000000000000000000000000000000000000000000006064820152608401610544565b6128696010600161547e565b81602001515103612a4a57845183036129e25760006128a5826020015160108151811061289857612898615496565b6020026020010151613679565b90506000815111612938576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603b60248201527f4d65726b6c65547269653a2076616c7565206c656e677468206d75737420626560448201527f2067726561746572207468616e207a65726f20286272616e63682900000000006064820152608401610544565b60018751612946919061510c565b83146129d4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f4d65726b6c65547269653a2076616c7565206e6f6465206d757374206265206c60448201527f617374206e6f646520696e2070726f6f6620286272616e6368290000000000006064820152608401610544565b965061239195505050505050565b60008584815181106129f6576129f6615496565b602001015160f81c60f81b60f81c9050600082602001518260ff1681518110612a2157612a21615496565b60200260200101519050612a34816137d9565b9550612a4160018661547e565b94505050612ed0565b600281602001515103612e48576000612a62826137fe565b9050600081600081518110612a7957612a79615496565b016020015160f81c90506000612a90600283615589565b612a9b9060026155ab565b90506000612aac848360ff16613822565b90506000612aba8a89613822565b90506000612ac88383613858565b905080835114612b5a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f4d65726b6c65547269653a20706174682072656d61696e646572206d7573742060448201527f736861726520616c6c206e6962626c65732077697468206b65790000000000006064820152608401610544565b60ff851660021480612b6f575060ff85166003145b15612d635780825114612c04576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603d60248201527f4d65726b6c65547269653a206b65792072656d61696e646572206d757374206260448201527f65206964656e746963616c20746f20706174682072656d61696e6465720000006064820152608401610544565b6000612c20886020015160018151811061289857612898615496565b90506000815111612cb3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f4d65726b6c65547269653a2076616c7565206c656e677468206d75737420626560448201527f2067726561746572207468616e207a65726f20286c65616629000000000000006064820152608401610544565b60018d51612cc1919061510c565b8914612d4f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f4d65726b6c65547269653a2076616c7565206e6f6465206d757374206265206c60448201527f617374206e6f646520696e2070726f6f6620286c6561662900000000000000006064820152608401610544565b9c506123919b505050505050505050505050565b60ff85161580612d76575060ff85166001145b15612db557612da28760200151600181518110612d9557612d95615496565b60200260200101516137d9565b9950612dae818a61547e565b9850612e3d565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4d65726b6c65547269653a2072656365697665642061206e6f6465207769746860448201527f20616e20756e6b6e6f776e2070726566697800000000000000000000000000006064820152608401610544565b505050505050612ed0565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602860248201527f4d65726b6c65547269653a20726563656976656420616e20756e70617273656160448201527f626c65206e6f64650000000000000000000000000000000000000000000000006064820152608401610544565b5080612edb81615432565b915050612595565b506040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f4d65726b6c65547269653a2072616e206f7574206f662070726f6f6620656c6560448201527f6d656e74730000000000000000000000000000000000000000000000000000006064820152608401610544565b6000808213612fd7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600960248201527f554e444546494e454400000000000000000000000000000000000000000000006044820152606401610544565b60006060612fe484613907565b03609f8181039490941b90931c6c465772b2bbbb5f824b15207a3081018102606090811d6d0388eaa27412d5aca026815d636e018202811d6d0df99ac502031bf953eff472fdcc018202811d6d13cdffb29d51d99322bdff5f2211018202811d6d0a0f742023def783a307a986912e018202811d6d01920d8043ca89b5239253284e42018202811d6c0b7a86d7375468fac667a0a527016c29508e458543d8aa4df2abee7883018302821d6d0139601a2efabe717e604cbb4894018302821d6d02247f7a7b6594320649aa03aba1018302821d7fffffffffffffffffffffffffffffffffffffff73c0c716a594e00d54e3c4cbc9018302821d7ffffffffffffffffffffffffffffffffffffffdc7b88c420e53a9890533129f6f01830290911d7fffffffffffffffffffffffffffffffffffffff465fda27eb4d63ded474e5f832019091027ffffffffffffffff5f6af8f7b3396644f18e157960000000000000000000000000105711340daa0d5f769dba1915cef59f0815a5506027d0267a36c0c95b3975ab3ee5b203a7614a3f75373f047d803ae7b6687f2b393909302929092017d57115e47018c7177eebf7cd370a3356a1b7863008a5ae8028c72b88642840160ae1d92915050565b60007ffffffffffffffffffffffffffffffffffffffffffffffffdb731c958f34d94c182136131e157506000919050565b680755bf798b4a1bf1e58212613253576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f4558505f4f564552464c4f5700000000000000000000000000000000000000006044820152606401610544565b6503782dace9d9604e83901b059150600060606bb17217f7d1cf79abc9e3b39884821b056b80000000000000000000000001901d6bb17217f7d1cf79abc9e3b39881029093037fffffffffffffffffffffffffffffffffffffffdbf3ccf1604d263450f02a550481018102606090811d6d0277594991cfc85f6e2461837cd9018202811d7fffffffffffffffffffffffffffffffffffffe5adedaa1cb095af9e4da10e363c018202811d6db1bbb201f443cf962f1a1d3db4a5018202811d7ffffffffffffffffffffffffffffffffffffd38dc772608b0ae56cce01296c0eb018202811d6e05180bb14799ab47a8a8cb2a527d57016d02d16720577bd19bf614176fe9ea6c10fe68e7fd37d0007b713f765084018402831d9081019084017ffffffffffffffffffffffffffffffffffffffe2c69812cf03b0763fd454a8f7e010290911d6e0587f503bb6ea29d25fcb7401964500190910279d835ebba824c98fb31b83b2ca45c000000000000000000000000010574029d9dc38563c32e5c2f6dc192ee70ef65f9978af30260c3939093039290921c92915050565b805160609060008167ffffffffffffffff81111561340f5761340f614a39565b60405190808252806020026020018201604052801561345457816020015b604080518082019091526060808252602082015281526020019060019003908161342d5790505b50905060005b828110156134d657604051806040016040528086838151811061347f5761347f615496565b602002602001015181526020016134ae8784815181106134a1576134a1615496565b60200260200101516139dd565b8152508282815181106134c3576134c3615496565b602090810291909101015260010161345a565b509392505050565b805160609060006134f082600261538a565b67ffffffffffffffff81111561350857613508614a39565b6040519080825280601f01601f191660200182016040528015613532576020820181803683370190505b5090506000805b8381101561366f5785818151811061355357613553615496565b6020910101517fff000000000000000000000000000000000000000000000000000000000000008116925060041c7f0ff000000000000000000000000000000000000000000000000000000000000016836135af83600261538a565b815181106135bf576135bf615496565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053507f0f0000000000000000000000000000000000000000000000000000000000000082168361361d83600261538a565b61362890600161547e565b8151811061363857613638615496565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350600101613539565b5090949350505050565b60606000806000613689856139f0565b9194509250905060008160018111156136a4576136a46155ce565b14613731576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f524c505265616465723a206465636f646564206974656d207479706520666f7260448201527f206279746573206973206e6f7420612064617461206974656d000000000000006064820152608401610544565b61373b828461547e565b8551146137ca576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603460248201527f524c505265616465723a2062797465732076616c756520636f6e7461696e732060448201527f616e20696e76616c69642072656d61696e6465720000000000000000000000006064820152608401610544565b61248b8560200151848461445d565b606060208260000151106137f5576137f082613679565b610faf565b610faf826144fe565b6060610faf61381d836020015160008151811061289857612898615496565b6134de565b6060825182106138415750604080516020810190915260008152610faf565b6123918383848651613853919061510c565b614514565b6000806000835185511061386d578351613870565b84515b90505b80821080156138f7575083828151811061388f5761388f615496565b602001015160f81c60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168583815181106138ce576138ce615496565b01602001517fff0000000000000000000000000000000000000000000000000000000000000016145b156134d657816001019150613873565b6000808211613972576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600960248201527f554e444546494e454400000000000000000000000000000000000000000000006044820152606401610544565b5060016fffffffffffffffffffffffffffffffff821160071b82811c67ffffffffffffffff1060061b1782811c63ffffffff1060051b1782811c61ffff1060041b1782811c60ff10600390811b90911783811c600f1060021b1783811c909110821b1791821c111790565b6060610faf6139eb836146ec565b6147d5565b600080600080846000015111613aae576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f524c505265616465723a206c656e677468206f6620616e20524c50206974656d60448201527f206d7573742062652067726561746572207468616e207a65726f20746f20626560648201527f206465636f6461626c6500000000000000000000000000000000000000000000608482015260a401610544565b6020840151805160001a607f8111613ad3576000600160009450945094505050614456565b60b78111613ce1576000613ae860808361510c565b905080876000015111613ba3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604e60248201527f524c505265616465723a206c656e677468206f6620636f6e74656e74206d757360448201527f742062652067726561746572207468616e20737472696e67206c656e6774682060648201527f2873686f727420737472696e6729000000000000000000000000000000000000608482015260a401610544565b6001838101517fff00000000000000000000000000000000000000000000000000000000000000169082141580613c1c57507f80000000000000000000000000000000000000000000000000000000000000007fff00000000000000000000000000000000000000000000000000000000000000821610155b613cce576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604d60248201527f524c505265616465723a20696e76616c6964207072656669782c2073696e676c60448201527f652062797465203c203078383020617265206e6f74207072656669786564202860648201527f73686f727420737472696e672900000000000000000000000000000000000000608482015260a401610544565b5060019550935060009250614456915050565b60bf811161402f576000613cf660b78361510c565b905080876000015111613db1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152605160248201527f524c505265616465723a206c656e677468206f6620636f6e74656e74206d757360448201527f74206265203e207468616e206c656e677468206f6620737472696e67206c656e60648201527f67746820286c6f6e6720737472696e6729000000000000000000000000000000608482015260a401610544565b60018301517fff00000000000000000000000000000000000000000000000000000000000000166000819003613e8f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f524c505265616465723a206c656e677468206f6620636f6e74656e74206d757360448201527f74206e6f74206861766520616e79206c656164696e67207a65726f7320286c6f60648201527f6e6720737472696e672900000000000000000000000000000000000000000000608482015260a401610544565b600184015160088302610100031c60378111613f53576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604860248201527f524c505265616465723a206c656e677468206f6620636f6e74656e74206d757360448201527f742062652067726561746572207468616e20353520627974657320286c6f6e6760648201527f20737472696e6729000000000000000000000000000000000000000000000000608482015260a401610544565b613f5d818461547e565b895111614012576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604c60248201527f524c505265616465723a206c656e677468206f6620636f6e74656e74206d757360448201527f742062652067726561746572207468616e20746f74616c206c656e677468202860648201527f6c6f6e6720737472696e67290000000000000000000000000000000000000000608482015260a401610544565b61401d83600161547e565b97509550600094506144569350505050565b60f7811161411057600061404460c08361510c565b9050808760000151116140ff576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f524c505265616465723a206c656e677468206f6620636f6e74656e74206d757360448201527f742062652067726561746572207468616e206c697374206c656e67746820287360648201527f686f7274206c6973742900000000000000000000000000000000000000000000608482015260a401610544565b600195509350849250614456915050565b600061411d60f78361510c565b9050808760000151116141d8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604d60248201527f524c505265616465723a206c656e677468206f6620636f6e74656e74206d757360448201527f74206265203e207468616e206c656e677468206f66206c697374206c656e677460648201527f6820286c6f6e67206c6973742900000000000000000000000000000000000000608482015260a401610544565b60018301517fff000000000000000000000000000000000000000000000000000000000000001660008190036142b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604860248201527f524c505265616465723a206c656e677468206f6620636f6e74656e74206d757360448201527f74206e6f74206861766520616e79206c656164696e67207a65726f7320286c6f60648201527f6e67206c69737429000000000000000000000000000000000000000000000000608482015260a401610544565b600184015160088302610100031c6037811161437a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604660248201527f524c505265616465723a206c656e677468206f6620636f6e74656e74206d757360448201527f742062652067726561746572207468616e20353520627974657320286c6f6e6760648201527f206c697374290000000000000000000000000000000000000000000000000000608482015260a401610544565b614384818461547e565b895111614439576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f524c505265616465723a206c656e677468206f6620636f6e74656e74206d757360448201527f742062652067726561746572207468616e20746f74616c206c656e677468202860648201527f6c6f6e67206c6973742900000000000000000000000000000000000000000000608482015260a401610544565b61444483600161547e565b97509550600194506144569350505050565b9193909250565b606060008267ffffffffffffffff81111561447a5761447a614a39565b6040519080825280601f01601f1916602001820160405280156144a4576020820181803683370190505b509050826000036144b6579050612391565b60006144c2858761547e565b90506020820160005b858110156144e35782810151828201526020016144cb565b858111156144f2576000868301525b50919695505050505050565b6060610faf82602001516000846000015161445d565b60608182601f011015614583576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f736c6963655f6f766572666c6f770000000000000000000000000000000000006044820152606401610544565b8282840110156145ef576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f736c6963655f6f766572666c6f770000000000000000000000000000000000006044820152606401610544565b8183018451101561465c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f736c6963655f6f75744f66426f756e64730000000000000000000000000000006044820152606401610544565b60608215801561467b57604051915060008252602082016040526146e3565b6040519150601f8416801560200281840101858101878315602002848b0101015b818310156146b457805183526020928301920161469c565b5050858452601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016604052505b50949350505050565b604080518082019091526000808252602082015260008251116147b7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f524c505265616465723a206c656e677468206f6620616e20524c50206974656d60448201527f206d7573742062652067726561746572207468616e207a65726f20746f20626560648201527f206465636f6461626c6500000000000000000000000000000000000000000000608482015260a401610544565b50604080518082019091528151815260209182019181019190915290565b606060008060006147e5856139f0565b919450925090506001816001811115614800576148006155ce565b1461488d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f524c505265616465723a206465636f646564206974656d207479706520666f7260448201527f206c697374206973206e6f742061206c697374206974656d00000000000000006064820152608401610544565b8451614899838561547e565b14614926576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f524c505265616465723a206c697374206974656d2068617320616e20696e766160448201527f6c696420646174612072656d61696e64657200000000000000000000000000006064820152608401610544565b6040805160208082526104208201909252600091816020015b604080518082019091526000808252602082015281526020019060019003908161493f5790505090506000845b8751811015614a2d576000806149b26040518060400160405280858d60000151614996919061510c565b8152602001858d602001516149ab919061547e565b90526139f0565b5091509150604051806040016040528083836149ce919061547e565b8152602001848c602001516149e3919061547e565b8152508585815181106149f8576149f8615496565b6020908102919091010152614a0e60018561547e565b9350614a1a818361547e565b614a24908461547e565b9250505061496c565b50815295945050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715614aaf57614aaf614a39565b604052919050565b803573ffffffffffffffffffffffffffffffffffffffff81168114614adb57600080fd5b919050565b600082601f830112614af157600080fd5b813567ffffffffffffffff811115614b0b57614b0b614a39565b614b3c60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601614a68565b818152846020838601011115614b5157600080fd5b816020850160208301376000918101602001919091529392505050565b600060c08284031215614b8057600080fd5b60405160c0810167ffffffffffffffff8282108183111715614ba457614ba4614a39565b8160405282935084358352614bbb60208601614ab7565b6020840152614bcc60408601614ab7565b6040840152606085013560608401526080850135608084015260a0850135915080821115614bf957600080fd5b50614c0685828601614ae0565b60a0830152505092915050565b600080600080600085870360e0811215614c2c57600080fd5b863567ffffffffffffffff80821115614c4457600080fd5b614c508a838b01614b6e565b97506020890135965060807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc084011215614c8957600080fd5b60408901955060c0890135925080831115614ca357600080fd5b828901925089601f840112614cb757600080fd5b8235915080821115614cc857600080fd5b508860208260051b8401011115614cde57600080fd5b959894975092955050506020019190565b60005b83811015614d0a578181015183820152602001614cf2565b83811115614d19576000848401525b50505050565b60008151808452614d37816020860160208601614cef565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006123916020830184614d1f565b600060208284031215614d8e57600080fd5b5035919050565b600060208284031215614da757600080fd5b813567ffffffffffffffff811115614dbe57600080fd5b6120cb84828501614b6e565b80358015158114614adb57600080fd5b600060208284031215614dec57600080fd5b61239182614dca565b600080600080600060a08688031215614e0d57600080fd5b614e1686614ab7565b945060208601359350604086013567ffffffffffffffff8082168214614e3b57600080fd5b819450614e4a60608901614dca565b93506080880135915080821115614e6057600080fd5b50614e6d88828901614ae0565b9150509295509295909350565b8581528460208201527fffffffffffffffff0000000000000000000000000000000000000000000000008460c01b16604082015282151560f81b604882015260008251614ece816049850160208701614cef565b919091016049019695505050505050565b80516fffffffffffffffffffffffffffffffff81168114614adb57600080fd5b600060608284031215614f1157600080fd5b6040516060810181811067ffffffffffffffff82111715614f3457614f34614a39565b60405282518152614f4760208401614edf565b6020820152614f5860408401614edf565b60408201529392505050565b600060808284031215614f7657600080fd5b6040516080810181811067ffffffffffffffff82111715614f9957614f99614a39565b8060405250823581526020830135602082015260408301356040820152606083013560608201528091505092915050565b600067ffffffffffffffff80841115614fe557614fe5614a39565b8360051b6020614ff6818301614a68565b86815291850191818101903684111561500e57600080fd5b865b84811015615042578035868111156150285760008081fd5b61503436828b01614ae0565b845250918301918301615010565b50979650505050505050565b60008451615060818460208901614cef565b80830190507f2e00000000000000000000000000000000000000000000000000000000000000808252855161509c816001850160208a01614cef565b600192019182015283516150b7816002840160208801614cef565b0160020195945050505050565b6000602082840312156150d657600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60008282101561511e5761511e6150dd565b500390565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b60008261516157615161615123565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83147f8000000000000000000000000000000000000000000000000000000000000000831416156151b5576151b56150dd565b500590565b6000808312837f8000000000000000000000000000000000000000000000000000000000000000018312811516156151f4576151f46150dd565b837f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff018313811615615228576152286150dd565b50500390565b60007f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60008413600084138583048511828216161561526f5761526f6150dd565b7f800000000000000000000000000000000000000000000000000000000000000060008712868205881281841616156152aa576152aa6150dd565b600087129250878205871284841616156152c6576152c66150dd565b878505871281841616156152dc576152dc6150dd565b505050929093029392505050565b6000808212827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03841381151615615324576153246150dd565b827f8000000000000000000000000000000000000000000000000000000000000000038412811615615358576153586150dd565b50500190565b600067ffffffffffffffff808316818516808303821115615381576153816150dd565b01949350505050565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04831182151516156153c2576153c26150dd565b500290565b6000826153d6576153d6615123565b500490565b868152600073ffffffffffffffffffffffffffffffffffffffff808816602084015280871660408401525084606083015283608083015260c060a083015261542660c0830184614d1f565b98975050505050505050565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203615463576154636150dd565b5060010190565b60008261547957615479615123565b500690565b60008219821115615491576154916150dd565b500190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b805163ffffffff81168114614adb57600080fd5b805160ff81168114614adb57600080fd5b600060c082840312156154fc57600080fd5b60405160c0810181811067ffffffffffffffff8211171561551f5761551f614a39565b60405261552b836154c5565b8152615539602084016154d9565b602082015261554a604084016154d9565b604082015261555b606084016154c5565b606082015261556c608084016154c5565b608082015261557d60a08401614edf565b60a08201529392505050565b600060ff83168061559c5761559c615123565b8060ff84160691505092915050565b600060ff821660ff8416808210156155c5576155c56150dd565b90039392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fdfea164736f6c634300080f000a"
func init() { func init() {
if err := json.Unmarshal([]byte(OptimismPortalStorageLayoutJSON), OptimismPortalStorageLayout); err != nil { if err := json.Unmarshal([]byte(OptimismPortalStorageLayoutJSON), OptimismPortalStorageLayout); err != nil {
......
...@@ -28,10 +28,20 @@ var ( ...@@ -28,10 +28,20 @@ var (
_ = event.NewSubscription _ = event.NewSubscription
) )
// ResourceMeteringResourceConfig is an auto generated low-level Go binding around an user-defined struct.
type ResourceMeteringResourceConfig struct {
MaxResourceLimit uint32
ElasticityMultiplier uint8
BaseFeeMaxChangeDenominator uint8
MinimumBaseFee uint32
SystemTxMaxGas uint32
MaximumBaseFee *big.Int
}
// SystemConfigMetaData contains all meta data concerning the SystemConfig contract. // SystemConfigMetaData contains all meta data concerning the SystemConfig contract.
var SystemConfigMetaData = &bind.MetaData{ var SystemConfigMetaData = &bind.MetaData{
ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_overhead\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_scalar\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"_batcherHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"_gasLimit\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"_unsafeBlockSigner\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"enumSystemConfig.UpdateType\",\"name\":\"updateType\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"ConfigUpdate\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"}],\"name\":\"Initialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"MINIMUM_GAS_LIMIT\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"UNSAFE_BLOCK_SIGNER_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"VERSION\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"batcherHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"gasLimit\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_overhead\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_scalar\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"_batcherHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"_gasLimit\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"_unsafeBlockSigner\",\"type\":\"address\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"overhead\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"scalar\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_batcherHash\",\"type\":\"bytes32\"}],\"name\":\"setBatcherHash\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_overhead\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_scalar\",\"type\":\"uint256\"}],\"name\":\"setGasConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"_gasLimit\",\"type\":\"uint64\"}],\"name\":\"setGasLimit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_unsafeBlockSigner\",\"type\":\"address\"}],\"name\":\"setUnsafeBlockSigner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"unsafeBlockSigner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_overhead\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_scalar\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"_batcherHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"_gasLimit\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"_unsafeBlockSigner\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"uint32\",\"name\":\"maxResourceLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint8\",\"name\":\"elasticityMultiplier\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"baseFeeMaxChangeDenominator\",\"type\":\"uint8\"},{\"internalType\":\"uint32\",\"name\":\"minimumBaseFee\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"systemTxMaxGas\",\"type\":\"uint32\"},{\"internalType\":\"uint128\",\"name\":\"maximumBaseFee\",\"type\":\"uint128\"}],\"internalType\":\"structResourceMetering.ResourceConfig\",\"name\":\"_config\",\"type\":\"tuple\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"enumSystemConfig.UpdateType\",\"name\":\"updateType\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"ConfigUpdate\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"}],\"name\":\"Initialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"MINIMUM_GAS_LIMIT\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"UNSAFE_BLOCK_SIGNER_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"VERSION\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"batcherHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"gasLimit\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_overhead\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_scalar\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"_batcherHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"_gasLimit\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"_unsafeBlockSigner\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"uint32\",\"name\":\"maxResourceLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint8\",\"name\":\"elasticityMultiplier\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"baseFeeMaxChangeDenominator\",\"type\":\"uint8\"},{\"internalType\":\"uint32\",\"name\":\"minimumBaseFee\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"systemTxMaxGas\",\"type\":\"uint32\"},{\"internalType\":\"uint128\",\"name\":\"maximumBaseFee\",\"type\":\"uint128\"}],\"internalType\":\"structResourceMetering.ResourceConfig\",\"name\":\"_config\",\"type\":\"tuple\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"minimumGasLimit\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"overhead\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"resourceConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"maxResourceLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint8\",\"name\":\"elasticityMultiplier\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"baseFeeMaxChangeDenominator\",\"type\":\"uint8\"},{\"internalType\":\"uint32\",\"name\":\"minimumBaseFee\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"systemTxMaxGas\",\"type\":\"uint32\"},{\"internalType\":\"uint128\",\"name\":\"maximumBaseFee\",\"type\":\"uint128\"}],\"internalType\":\"structResourceMetering.ResourceConfig\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"scalar\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_batcherHash\",\"type\":\"bytes32\"}],\"name\":\"setBatcherHash\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_overhead\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_scalar\",\"type\":\"uint256\"}],\"name\":\"setGasConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"_gasLimit\",\"type\":\"uint64\"}],\"name\":\"setGasLimit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"maxResourceLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint8\",\"name\":\"elasticityMultiplier\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"baseFeeMaxChangeDenominator\",\"type\":\"uint8\"},{\"internalType\":\"uint32\",\"name\":\"minimumBaseFee\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"systemTxMaxGas\",\"type\":\"uint32\"},{\"internalType\":\"uint128\",\"name\":\"maximumBaseFee\",\"type\":\"uint128\"}],\"internalType\":\"structResourceMetering.ResourceConfig\",\"name\":\"_config\",\"type\":\"tuple\"}],\"name\":\"setResourceConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_unsafeBlockSigner\",\"type\":\"address\"}],\"name\":\"setUnsafeBlockSigner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"unsafeBlockSigner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]",
Bin: "0x60e06040523480156200001157600080fd5b50604051620015403803806200154083398101604081905262000034916200047b565b60016080819052600060a05260c052620000538686868686866200005f565b505050505050620004f0565b600054610100900460ff1615808015620000805750600054600160ff909116105b80620000b057506200009d306200025360201b620009021760201c565b158015620000b0575060005460ff166001145b620001195760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084015b60405180910390fd5b6000805460ff1916600117905580156200013d576000805461ff0019166101001790555b627a12006001600160401b03841610156200019b5760405162461bcd60e51b815260206004820152601f60248201527f53797374656d436f6e6669673a20676173206c696d697420746f6f206c6f7700604482015260640162000110565b620001a562000262565b620001b087620002ca565b606586905560668590556067849055606880546001600160401b0319166001600160401b03851617905562000203827f65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c0855565b80156200024a576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b50505050505050565b6001600160a01b03163b151590565b600054610100900460ff16620002be5760405162461bcd60e51b815260206004820152602b60248201526000805160206200152083398151915260448201526a6e697469616c697a696e6760a81b606482015260840162000110565b620002c862000349565b565b620002d4620003b0565b6001600160a01b0381166200033b5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b606482015260840162000110565b62000346816200040c565b50565b600054610100900460ff16620003a55760405162461bcd60e51b815260206004820152602b60248201526000805160206200152083398151915260448201526a6e697469616c697a696e6760a81b606482015260840162000110565b620002c8336200040c565b6033546001600160a01b03163314620002c85760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640162000110565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b80516001600160a01b03811681146200047657600080fd5b919050565b60008060008060008060c087890312156200049557600080fd5b620004a0876200045e565b6020880151604089015160608a015160808b0151939950919750955093506001600160401b0381168114620004d457600080fd5b9150620004e460a088016200045e565b90509295509295509295565b60805160a05160c0516110006200052060003960006103c80152600061039f0152600061037601526110006000f3fe608060405234801561001057600080fd5b506004361061011b5760003560e01c80638f974d7f116100b2578063e81b2c6d11610081578063f45e65d811610066578063f45e65d814610286578063f68016b71461028f578063ffa1ad74146102a357600080fd5b8063e81b2c6d1461026a578063f2fde38b1461027357600080fd5b80638f974d7f1461021e578063935f029e14610231578063b40a817c14610244578063c9b26f611461025757600080fd5b80634f16540b116100ee5780634f16540b146101bc57806354fd4d50146101e3578063715018a6146101f85780638da5cb5b1461020057600080fd5b80630c18c1621461012057806318d139181461013c5780631fd19ee11461015157806329477e8614610199575b600080fd5b61012960655481565b6040519081526020015b60405180910390f35b61014f61014a366004610cb6565b6102ab565b005b7f65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c08545b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610133565b6101a3627a120081565b60405167ffffffffffffffff9091168152602001610133565b6101297f65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c0881565b6101eb61036f565b6040516101339190610d52565b61014f610412565b60335473ffffffffffffffffffffffffffffffffffffffff16610174565b61014f61022c366004610d7d565b610426565b61014f61023f366004610ddc565b6106aa565b61014f610252366004610dfe565b610743565b61014f610265366004610e19565b61081b565b61012960675481565b61014f610281366004610cb6565b61084b565b61012960665481565b6068546101a39067ffffffffffffffff1681565b610129600081565b6102b361091e565b6102db817f65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c0855565b6040805173ffffffffffffffffffffffffffffffffffffffff8316602082015260009101604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152919052905060035b60007f1d2b0bda21d56b8bd12d4f94ebacffdfb35f5e226f84b461103bb8beab6353be836040516103639190610d52565b60405180910390a35050565b606061039a7f000000000000000000000000000000000000000000000000000000000000000061099f565b6103c37f000000000000000000000000000000000000000000000000000000000000000061099f565b6103ec7f000000000000000000000000000000000000000000000000000000000000000061099f565b6040516020016103fe93929190610e32565b604051602081830303815290604052905090565b61041a61091e565b6104246000610adc565b565b600054610100900460ff16158080156104465750600054600160ff909116105b806104605750303b158015610460575060005460ff166001145b6104f1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084015b60405180910390fd5b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055801561054f57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b627a120067ffffffffffffffff841610156105c6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f53797374656d436f6e6669673a20676173206c696d697420746f6f206c6f770060448201526064016104e8565b6105ce610b53565b6105d78761084b565b606586905560668590556067849055606880547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff85161790557f65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c0882905580156106a157600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b50505050505050565b6106b261091e565b606582905560668190556040805160208101849052908101829052600090606001604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190529050600160007f1d2b0bda21d56b8bd12d4f94ebacffdfb35f5e226f84b461103bb8beab6353be836040516107369190610d52565b60405180910390a3505050565b61074b61091e565b627a120067ffffffffffffffff821610156107c2576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f53797374656d436f6e6669673a20676173206c696d697420746f6f206c6f770060448201526064016104e8565b606880547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff83169081179091556040805160208082019390935281518082039093018352810190526002610332565b61082361091e565b6067819055604080516020808201849052825180830390910181529082019091526000610332565b61085361091e565b73ffffffffffffffffffffffffffffffffffffffff81166108f6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016104e8565b6108ff81610adc565b50565b73ffffffffffffffffffffffffffffffffffffffff163b151590565b60335473ffffffffffffffffffffffffffffffffffffffff163314610424576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016104e8565b6060816000036109e257505060408051808201909152600181527f3000000000000000000000000000000000000000000000000000000000000000602082015290565b8160005b8115610a0c57806109f681610ed7565b9150610a059050600a83610f3e565b91506109e6565b60008167ffffffffffffffff811115610a2757610a27610f52565b6040519080825280601f01601f191660200182016040528015610a51576020820181803683370190505b5090505b8415610ad457610a66600183610f81565b9150610a73600a86610f98565b610a7e906030610fac565b60f81b818381518110610a9357610a93610fc4565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350610acd600a86610f3e565b9450610a55565b949350505050565b6033805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b600054610100900460ff16610bea576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016104e8565b610424600054610100900460ff16610c84576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016104e8565b61042433610adc565b803573ffffffffffffffffffffffffffffffffffffffff81168114610cb157600080fd5b919050565b600060208284031215610cc857600080fd5b610cd182610c8d565b9392505050565b60005b83811015610cf3578181015183820152602001610cdb565b83811115610d02576000848401525b50505050565b60008151808452610d20816020860160208601610cd8565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000610cd16020830184610d08565b803567ffffffffffffffff81168114610cb157600080fd5b60008060008060008060c08789031215610d9657600080fd5b610d9f87610c8d565b9550602087013594506040870135935060608701359250610dc260808801610d65565b9150610dd060a08801610c8d565b90509295509295509295565b60008060408385031215610def57600080fd5b50508035926020909101359150565b600060208284031215610e1057600080fd5b610cd182610d65565b600060208284031215610e2b57600080fd5b5035919050565b60008451610e44818460208901610cd8565b80830190507f2e000000000000000000000000000000000000000000000000000000000000008082528551610e80816001850160208a01610cd8565b60019201918201528351610e9b816002840160208801610cd8565b0160020195945050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610f0857610f08610ea8565b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600082610f4d57610f4d610f0f565b500490565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082821015610f9357610f93610ea8565b500390565b600082610fa757610fa7610f0f565b500690565b60008219821115610fbf57610fbf610ea8565b500190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fdfea164736f6c634300080f000a496e697469616c697a61626c653a20636f6e7472616374206973206e6f742069", Bin: "0x60e06040523480156200001157600080fd5b50604051620022ed380380620022ed83398101604081905262000034916200084f565b6001608081905260a052600060c052620000548787878787878762000061565b5050505050505062000a4f565b600054610100900460ff1615808015620000825750600054600160ff909116105b80620000b257506200009f306200027060201b62000b041760201c565b158015620000b2575060005460ff166001145b6200011b5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084015b60405180910390fd5b6000805460ff1916600117905580156200013f576000805461ff0019166101001790555b620001496200027f565b6200015488620002e7565b606587905560668690556067859055606880546001600160401b0319166001600160401b038616179055620001a7837f65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c0855565b620001b28262000366565b620001bc620006b1565b6001600160401b0316846001600160401b031610156200021f5760405162461bcd60e51b815260206004820152601f60248201527f53797374656d436f6e6669673a20676173206c696d697420746f6f206c6f7700604482015260640162000112565b801562000266576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050505050565b6001600160a01b03163b151590565b600054610100900460ff16620002db5760405162461bcd60e51b815260206004820152602b6024820152600080516020620022cd83398151915260448201526a6e697469616c697a696e6760a81b606482015260840162000112565b620002e5620006de565b565b620002f162000745565b6001600160a01b038116620003585760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b606482015260840162000112565b6200036381620007a1565b50565b8060a001516001600160801b0316816060015163ffffffff161115620003f55760405162461bcd60e51b815260206004820152603560248201527f53797374656d436f6e6669673a206d696e206261736520666565206d7573742060448201527f6265206c657373207468616e206d617820626173650000000000000000000000606482015260840162000112565b6000816040015160ff16116200045c5760405162461bcd60e51b815260206004820152602560248201527f53797374656d436f6e6669673a2064656e6f6d696e61746f722063616e6e6f74604482015264020626520360dc1b606482015260840162000112565b606854608082015182516001600160401b03909216916200047e91906200099e565b63ffffffff161115620004d45760405162461bcd60e51b815260206004820152601f60248201527f53797374656d436f6e6669673a20676173206c696d697420746f6f206c6f7700604482015260640162000112565b6000816020015160ff1611620005455760405162461bcd60e51b815260206004820152602f60248201527f53797374656d436f6e6669673a20656c6173746963697479206d756c7469706c60448201526e06965722063616e6e6f74206265203608c1b606482015260840162000112565b8051602082015163ffffffff82169160ff9091169062000567908290620009c9565b620005739190620009fb565b63ffffffff1614620005ee5760405162461bcd60e51b815260206004820152603760248201527f53797374656d436f6e6669673a20707265636973696f6e206c6f73732077697460448201527f6820746172676574207265736f75726365206c696d6974000000000000000000606482015260840162000112565b805160698054602084015160408501516060860151608087015160a09097015163ffffffff96871664ffffffffff199095169490941764010000000060ff948516021764ffffffffff60281b191665010000000000939092169290920263ffffffff60301b19161766010000000000009185169190910217600160501b600160f01b0319166a01000000000000000000009390941692909202600160701b600160f01b03191692909217600160701b6001600160801b0390921691909102179055565b606954600090620006d99063ffffffff6a010000000000000000000082048116911662000a2a565b905090565b600054610100900460ff166200073a5760405162461bcd60e51b815260206004820152602b6024820152600080516020620022cd83398151915260448201526a6e697469616c697a696e6760a81b606482015260840162000112565b620002e533620007a1565b6033546001600160a01b03163314620002e55760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640162000112565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b80516001600160a01b03811681146200080b57600080fd5b919050565b805163ffffffff811681146200080b57600080fd5b805160ff811681146200080b57600080fd5b80516001600160801b03811681146200080b57600080fd5b60008060008060008060008789036101808112156200086d57600080fd5b6200087889620007f3565b60208a015160408b015160608c015160808d0151939b50919950975095506001600160401b038082168214620008ad57600080fd5b819550620008be60a08c01620007f3565b945060c060bf1984011215620008d357600080fd5b604051925060c08301915082821081831117156200090157634e487b7160e01b600052604160045260246000fd5b506040526200091360c08a0162000810565b81526200092360e08a0162000825565b6020820152620009376101008a0162000825565b60408201526200094b6101208a0162000810565b60608201526200095f6101408a0162000810565b6080820152620009736101608a0162000837565b60a08201528091505092959891949750929550565b634e487b7160e01b600052601160045260246000fd5b600063ffffffff808316818516808303821115620009c057620009c062000988565b01949350505050565b600063ffffffff80841680620009ef57634e487b7160e01b600052601260045260246000fd5b92169190910492915050565b600063ffffffff8083168185168183048111821515161562000a215762000a2162000988565b02949350505050565b60006001600160401b03828116848216808303821115620009c057620009c062000988565b60805160a05160c05161184e62000a7f60003960006105930152600061056a01526000610541015261184e6000f3fe608060405234801561001057600080fd5b506004361061016c5760003560e01c8063b40a817c116100cd578063f2fde38b11610081578063f68016b711610066578063f68016b71461041c578063f975e92514610430578063ffa1ad741461044357600080fd5b8063f2fde38b14610400578063f45e65d81461041357600080fd5b8063c9b26f61116100b2578063c9b26f61146102b0578063cc731b02146102c3578063e81b2c6d146103f757600080fd5b8063b40a817c1461028a578063c71973f61461029d57600080fd5b80634f16540b11610124578063715018a611610109578063715018a6146102515780638da5cb5b14610259578063935f029e1461027757600080fd5b80634f16540b1461021557806354fd4d501461023c57600080fd5b80631fd19ee1116101555780631fd19ee1146101a257806329477e86146101ea5780634add321d1461020d57600080fd5b80630c18c1621461017157806318d139181461018d575b600080fd5b61017a60655481565b6040519081526020015b60405180910390f35b6101a061019b36600461132c565b61044b565b005b7f65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c08545b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610184565b6101f4627a120081565b60405167ffffffffffffffff9091168152602001610184565b6101f461050f565b61017a7f65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c0881565b61024461053a565b60405161018491906113c8565b6101a06105dd565b60335473ffffffffffffffffffffffffffffffffffffffff166101c5565b6101a06102853660046113db565b6105f1565b6101a0610298366004611415565b61068a565b6101a06102ab36600461156d565b610775565b6101a06102be366004611589565b610789565b6103876040805160c081018252600080825260208201819052918101829052606081018290526080810182905260a0810191909152506040805160c08101825260695463ffffffff8082168352640100000000820460ff9081166020850152650100000000008304169383019390935266010000000000008104831660608301526a0100000000000000000000810490921660808201526e0100000000000000000000000000009091046fffffffffffffffffffffffffffffffff1660a082015290565b6040516101849190600060c08201905063ffffffff80845116835260ff602085015116602084015260ff6040850151166040840152806060850151166060840152806080850151166080840152506fffffffffffffffffffffffffffffffff60a08401511660a083015292915050565b61017a60675481565b6101a061040e36600461132c565b6107b9565b61017a60665481565b6068546101f49067ffffffffffffffff1681565b6101a061043e3660046115a2565b61086d565b61017a600081565b610453610b20565b61047b817f65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c0855565b6040805173ffffffffffffffffffffffffffffffffffffffff8316602082015260009101604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152919052905060035b60007f1d2b0bda21d56b8bd12d4f94ebacffdfb35f5e226f84b461103bb8beab6353be8360405161050391906113c8565b60405180910390a35050565b6069546000906105359063ffffffff6a0100000000000000000000820481169116611644565b905090565b60606105657f0000000000000000000000000000000000000000000000000000000000000000610ba1565b61058e7f0000000000000000000000000000000000000000000000000000000000000000610ba1565b6105b77f0000000000000000000000000000000000000000000000000000000000000000610ba1565b6040516020016105c993929190611670565b604051602081830303815290604052905090565b6105e5610b20565b6105ef6000610cde565b565b6105f9610b20565b606582905560668190556040805160208101849052908101829052600090606001604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190529050600160007f1d2b0bda21d56b8bd12d4f94ebacffdfb35f5e226f84b461103bb8beab6353be8360405161067d91906113c8565b60405180910390a3505050565b610692610b20565b61069a61050f565b67ffffffffffffffff168167ffffffffffffffff16101561071c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f53797374656d436f6e6669673a20676173206c696d697420746f6f206c6f770060448201526064015b60405180910390fd5b606880547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff831690811790915560408051602080820193909352815180820390930183528101905260026104d2565b61077d610b20565b61078681610d55565b50565b610791610b20565b60678190556040805160208082018490528251808303909101815290820190915260006104d2565b6107c1610b20565b73ffffffffffffffffffffffffffffffffffffffff8116610864576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152608401610713565b61078681610cde565b600054610100900460ff161580801561088d5750600054600160ff909116105b806108a75750303b1580156108a7575060005460ff166001145b610933576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a65640000000000000000000000000000000000006064820152608401610713565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055801561099157600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b6109996111c9565b6109a2886107b9565b606587905560668690556067859055606880547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff86161790557f65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c08839055610a1282610d55565b610a1a61050f565b67ffffffffffffffff168467ffffffffffffffff161015610a97576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f53797374656d436f6e6669673a20676173206c696d697420746f6f206c6f77006044820152606401610713565b8015610afa57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050505050565b73ffffffffffffffffffffffffffffffffffffffff163b151590565b60335473ffffffffffffffffffffffffffffffffffffffff1633146105ef576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610713565b606081600003610be457505060408051808201909152600181527f3000000000000000000000000000000000000000000000000000000000000000602082015290565b8160005b8115610c0e5780610bf8816116e6565b9150610c079050600a8361174d565b9150610be8565b60008167ffffffffffffffff811115610c2957610c29611430565b6040519080825280601f01601f191660200182016040528015610c53576020820181803683370190505b5090505b8415610cd657610c68600183611761565b9150610c75600a86611778565b610c8090603061178c565b60f81b818381518110610c9557610c956117a4565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350610ccf600a8661174d565b9450610c57565b949350505050565b6033805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b8060a001516fffffffffffffffffffffffffffffffff16816060015163ffffffff161115610e05576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603560248201527f53797374656d436f6e6669673a206d696e206261736520666565206d7573742060448201527f6265206c657373207468616e206d6178206261736500000000000000000000006064820152608401610713565b6000816040015160ff1611610e9c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f53797374656d436f6e6669673a2064656e6f6d696e61746f722063616e6e6f7460448201527f20626520300000000000000000000000000000000000000000000000000000006064820152608401610713565b6068546080820151825167ffffffffffffffff90921691610ebd91906117d3565b63ffffffff161115610f2b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f53797374656d436f6e6669673a20676173206c696d697420746f6f206c6f77006044820152606401610713565b6000816020015160ff1611610fc2576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602f60248201527f53797374656d436f6e6669673a20656c6173746963697479206d756c7469706c60448201527f6965722063616e6e6f74206265203000000000000000000000000000000000006064820152608401610713565b8051602082015163ffffffff82169160ff90911690610fe29082906117f2565b610fec9190611815565b63ffffffff161461107f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603760248201527f53797374656d436f6e6669673a20707265636973696f6e206c6f73732077697460448201527f6820746172676574207265736f75726365206c696d69740000000000000000006064820152608401610713565b805160698054602084015160408501516060860151608087015160a09097015163ffffffff9687167fffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000009095169490941764010000000060ff94851602177fffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffffff166501000000000093909216929092027fffffffffffffffffffffffffffffffffffffffffffff00000000ffffffffffff1617660100000000000091851691909102177fffff0000000000000000000000000000000000000000ffffffffffffffffffff166a010000000000000000000093909416929092027fffff00000000000000000000000000000000ffffffffffffffffffffffffffff16929092176e0100000000000000000000000000006fffffffffffffffffffffffffffffffff90921691909102179055565b600054610100900460ff16611260576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152608401610713565b6105ef600054610100900460ff166112fa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152608401610713565b6105ef33610cde565b803573ffffffffffffffffffffffffffffffffffffffff8116811461132757600080fd5b919050565b60006020828403121561133e57600080fd5b61134782611303565b9392505050565b60005b83811015611369578181015183820152602001611351565b83811115611378576000848401525b50505050565b6000815180845261139681602086016020860161134e565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000611347602083018461137e565b600080604083850312156113ee57600080fd5b50508035926020909101359150565b803567ffffffffffffffff8116811461132757600080fd5b60006020828403121561142757600080fd5b611347826113fd565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b803563ffffffff8116811461132757600080fd5b803560ff8116811461132757600080fd5b80356fffffffffffffffffffffffffffffffff8116811461132757600080fd5b600060c082840312156114b657600080fd5b60405160c0810181811067ffffffffffffffff82111715611500577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60405290508061150f8361145f565b815261151d60208401611473565b602082015261152e60408401611473565b604082015261153f6060840161145f565b60608201526115506080840161145f565b608082015261156160a08401611484565b60a08201525092915050565b600060c0828403121561157f57600080fd5b61134783836114a4565b60006020828403121561159b57600080fd5b5035919050565b6000806000806000806000610180888a0312156115be57600080fd5b6115c788611303565b96506020880135955060408801359450606088013593506115ea608089016113fd565b92506115f860a08901611303565b91506116078960c08a016114a4565b905092959891949750929550565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600067ffffffffffffffff80831681851680830382111561166757611667611615565b01949350505050565b6000845161168281846020890161134e565b80830190507f2e0000000000000000000000000000000000000000000000000000000000000080825285516116be816001850160208a0161134e565b600192019182015283516116d981600284016020880161134e565b0160020195945050505050565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361171757611717611615565b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b60008261175c5761175c61171e565b500490565b60008282101561177357611773611615565b500390565b6000826117875761178761171e565b500690565b6000821982111561179f5761179f611615565b500190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600063ffffffff80831681851680830382111561166757611667611615565b600063ffffffff808416806118095761180961171e565b92169190910492915050565b600063ffffffff8083168185168183048111821515161561183857611838611615565b0294935050505056fea164736f6c634300080f000a496e697469616c697a61626c653a20636f6e7472616374206973206e6f742069",
} }
// SystemConfigABI is the input ABI used to generate the binding from. // SystemConfigABI is the input ABI used to generate the binding from.
...@@ -43,7 +53,7 @@ var SystemConfigABI = SystemConfigMetaData.ABI ...@@ -43,7 +53,7 @@ var SystemConfigABI = SystemConfigMetaData.ABI
var SystemConfigBin = SystemConfigMetaData.Bin var SystemConfigBin = SystemConfigMetaData.Bin
// DeploySystemConfig deploys a new Ethereum contract, binding an instance of SystemConfig to it. // DeploySystemConfig deploys a new Ethereum contract, binding an instance of SystemConfig to it.
func DeploySystemConfig(auth *bind.TransactOpts, backend bind.ContractBackend, _owner common.Address, _overhead *big.Int, _scalar *big.Int, _batcherHash [32]byte, _gasLimit uint64, _unsafeBlockSigner common.Address) (common.Address, *types.Transaction, *SystemConfig, error) { func DeploySystemConfig(auth *bind.TransactOpts, backend bind.ContractBackend, _owner common.Address, _overhead *big.Int, _scalar *big.Int, _batcherHash [32]byte, _gasLimit uint64, _unsafeBlockSigner common.Address, _config ResourceMeteringResourceConfig) (common.Address, *types.Transaction, *SystemConfig, error) {
parsed, err := SystemConfigMetaData.GetAbi() parsed, err := SystemConfigMetaData.GetAbi()
if err != nil { if err != nil {
return common.Address{}, nil, nil, err return common.Address{}, nil, nil, err
...@@ -52,7 +62,7 @@ func DeploySystemConfig(auth *bind.TransactOpts, backend bind.ContractBackend, _ ...@@ -52,7 +62,7 @@ func DeploySystemConfig(auth *bind.TransactOpts, backend bind.ContractBackend, _
return common.Address{}, nil, nil, errors.New("GetABI returned nil") return common.Address{}, nil, nil, errors.New("GetABI returned nil")
} }
address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(SystemConfigBin), backend, _owner, _overhead, _scalar, _batcherHash, _gasLimit, _unsafeBlockSigner) address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(SystemConfigBin), backend, _owner, _overhead, _scalar, _batcherHash, _gasLimit, _unsafeBlockSigner, _config)
if err != nil { if err != nil {
return common.Address{}, nil, nil, err return common.Address{}, nil, nil, err
} }
...@@ -356,6 +366,37 @@ func (_SystemConfig *SystemConfigCallerSession) GasLimit() (uint64, error) { ...@@ -356,6 +366,37 @@ func (_SystemConfig *SystemConfigCallerSession) GasLimit() (uint64, error) {
return _SystemConfig.Contract.GasLimit(&_SystemConfig.CallOpts) return _SystemConfig.Contract.GasLimit(&_SystemConfig.CallOpts)
} }
// MinimumGasLimit is a free data retrieval call binding the contract method 0x4add321d.
//
// Solidity: function minimumGasLimit() view returns(uint64)
func (_SystemConfig *SystemConfigCaller) MinimumGasLimit(opts *bind.CallOpts) (uint64, error) {
var out []interface{}
err := _SystemConfig.contract.Call(opts, &out, "minimumGasLimit")
if err != nil {
return *new(uint64), err
}
out0 := *abi.ConvertType(out[0], new(uint64)).(*uint64)
return out0, err
}
// MinimumGasLimit is a free data retrieval call binding the contract method 0x4add321d.
//
// Solidity: function minimumGasLimit() view returns(uint64)
func (_SystemConfig *SystemConfigSession) MinimumGasLimit() (uint64, error) {
return _SystemConfig.Contract.MinimumGasLimit(&_SystemConfig.CallOpts)
}
// MinimumGasLimit is a free data retrieval call binding the contract method 0x4add321d.
//
// Solidity: function minimumGasLimit() view returns(uint64)
func (_SystemConfig *SystemConfigCallerSession) MinimumGasLimit() (uint64, error) {
return _SystemConfig.Contract.MinimumGasLimit(&_SystemConfig.CallOpts)
}
// Overhead is a free data retrieval call binding the contract method 0x0c18c162. // Overhead is a free data retrieval call binding the contract method 0x0c18c162.
// //
// Solidity: function overhead() view returns(uint256) // Solidity: function overhead() view returns(uint256)
...@@ -418,6 +459,37 @@ func (_SystemConfig *SystemConfigCallerSession) Owner() (common.Address, error) ...@@ -418,6 +459,37 @@ func (_SystemConfig *SystemConfigCallerSession) Owner() (common.Address, error)
return _SystemConfig.Contract.Owner(&_SystemConfig.CallOpts) return _SystemConfig.Contract.Owner(&_SystemConfig.CallOpts)
} }
// ResourceConfig is a free data retrieval call binding the contract method 0xcc731b02.
//
// Solidity: function resourceConfig() view returns((uint32,uint8,uint8,uint32,uint32,uint128))
func (_SystemConfig *SystemConfigCaller) ResourceConfig(opts *bind.CallOpts) (ResourceMeteringResourceConfig, error) {
var out []interface{}
err := _SystemConfig.contract.Call(opts, &out, "resourceConfig")
if err != nil {
return *new(ResourceMeteringResourceConfig), err
}
out0 := *abi.ConvertType(out[0], new(ResourceMeteringResourceConfig)).(*ResourceMeteringResourceConfig)
return out0, err
}
// ResourceConfig is a free data retrieval call binding the contract method 0xcc731b02.
//
// Solidity: function resourceConfig() view returns((uint32,uint8,uint8,uint32,uint32,uint128))
func (_SystemConfig *SystemConfigSession) ResourceConfig() (ResourceMeteringResourceConfig, error) {
return _SystemConfig.Contract.ResourceConfig(&_SystemConfig.CallOpts)
}
// ResourceConfig is a free data retrieval call binding the contract method 0xcc731b02.
//
// Solidity: function resourceConfig() view returns((uint32,uint8,uint8,uint32,uint32,uint128))
func (_SystemConfig *SystemConfigCallerSession) ResourceConfig() (ResourceMeteringResourceConfig, error) {
return _SystemConfig.Contract.ResourceConfig(&_SystemConfig.CallOpts)
}
// Scalar is a free data retrieval call binding the contract method 0xf45e65d8. // Scalar is a free data retrieval call binding the contract method 0xf45e65d8.
// //
// Solidity: function scalar() view returns(uint256) // Solidity: function scalar() view returns(uint256)
...@@ -511,25 +583,25 @@ func (_SystemConfig *SystemConfigCallerSession) Version() (string, error) { ...@@ -511,25 +583,25 @@ func (_SystemConfig *SystemConfigCallerSession) Version() (string, error) {
return _SystemConfig.Contract.Version(&_SystemConfig.CallOpts) return _SystemConfig.Contract.Version(&_SystemConfig.CallOpts)
} }
// Initialize is a paid mutator transaction binding the contract method 0x8f974d7f. // Initialize is a paid mutator transaction binding the contract method 0xf975e925.
// //
// Solidity: function initialize(address _owner, uint256 _overhead, uint256 _scalar, bytes32 _batcherHash, uint64 _gasLimit, address _unsafeBlockSigner) returns() // Solidity: function initialize(address _owner, uint256 _overhead, uint256 _scalar, bytes32 _batcherHash, uint64 _gasLimit, address _unsafeBlockSigner, (uint32,uint8,uint8,uint32,uint32,uint128) _config) returns()
func (_SystemConfig *SystemConfigTransactor) Initialize(opts *bind.TransactOpts, _owner common.Address, _overhead *big.Int, _scalar *big.Int, _batcherHash [32]byte, _gasLimit uint64, _unsafeBlockSigner common.Address) (*types.Transaction, error) { func (_SystemConfig *SystemConfigTransactor) Initialize(opts *bind.TransactOpts, _owner common.Address, _overhead *big.Int, _scalar *big.Int, _batcherHash [32]byte, _gasLimit uint64, _unsafeBlockSigner common.Address, _config ResourceMeteringResourceConfig) (*types.Transaction, error) {
return _SystemConfig.contract.Transact(opts, "initialize", _owner, _overhead, _scalar, _batcherHash, _gasLimit, _unsafeBlockSigner) return _SystemConfig.contract.Transact(opts, "initialize", _owner, _overhead, _scalar, _batcherHash, _gasLimit, _unsafeBlockSigner, _config)
} }
// Initialize is a paid mutator transaction binding the contract method 0x8f974d7f. // Initialize is a paid mutator transaction binding the contract method 0xf975e925.
// //
// Solidity: function initialize(address _owner, uint256 _overhead, uint256 _scalar, bytes32 _batcherHash, uint64 _gasLimit, address _unsafeBlockSigner) returns() // Solidity: function initialize(address _owner, uint256 _overhead, uint256 _scalar, bytes32 _batcherHash, uint64 _gasLimit, address _unsafeBlockSigner, (uint32,uint8,uint8,uint32,uint32,uint128) _config) returns()
func (_SystemConfig *SystemConfigSession) Initialize(_owner common.Address, _overhead *big.Int, _scalar *big.Int, _batcherHash [32]byte, _gasLimit uint64, _unsafeBlockSigner common.Address) (*types.Transaction, error) { func (_SystemConfig *SystemConfigSession) Initialize(_owner common.Address, _overhead *big.Int, _scalar *big.Int, _batcherHash [32]byte, _gasLimit uint64, _unsafeBlockSigner common.Address, _config ResourceMeteringResourceConfig) (*types.Transaction, error) {
return _SystemConfig.Contract.Initialize(&_SystemConfig.TransactOpts, _owner, _overhead, _scalar, _batcherHash, _gasLimit, _unsafeBlockSigner) return _SystemConfig.Contract.Initialize(&_SystemConfig.TransactOpts, _owner, _overhead, _scalar, _batcherHash, _gasLimit, _unsafeBlockSigner, _config)
} }
// Initialize is a paid mutator transaction binding the contract method 0x8f974d7f. // Initialize is a paid mutator transaction binding the contract method 0xf975e925.
// //
// Solidity: function initialize(address _owner, uint256 _overhead, uint256 _scalar, bytes32 _batcherHash, uint64 _gasLimit, address _unsafeBlockSigner) returns() // Solidity: function initialize(address _owner, uint256 _overhead, uint256 _scalar, bytes32 _batcherHash, uint64 _gasLimit, address _unsafeBlockSigner, (uint32,uint8,uint8,uint32,uint32,uint128) _config) returns()
func (_SystemConfig *SystemConfigTransactorSession) Initialize(_owner common.Address, _overhead *big.Int, _scalar *big.Int, _batcherHash [32]byte, _gasLimit uint64, _unsafeBlockSigner common.Address) (*types.Transaction, error) { func (_SystemConfig *SystemConfigTransactorSession) Initialize(_owner common.Address, _overhead *big.Int, _scalar *big.Int, _batcherHash [32]byte, _gasLimit uint64, _unsafeBlockSigner common.Address, _config ResourceMeteringResourceConfig) (*types.Transaction, error) {
return _SystemConfig.Contract.Initialize(&_SystemConfig.TransactOpts, _owner, _overhead, _scalar, _batcherHash, _gasLimit, _unsafeBlockSigner) return _SystemConfig.Contract.Initialize(&_SystemConfig.TransactOpts, _owner, _overhead, _scalar, _batcherHash, _gasLimit, _unsafeBlockSigner, _config)
} }
// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. // RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6.
...@@ -616,6 +688,27 @@ func (_SystemConfig *SystemConfigTransactorSession) SetGasLimit(_gasLimit uint64 ...@@ -616,6 +688,27 @@ func (_SystemConfig *SystemConfigTransactorSession) SetGasLimit(_gasLimit uint64
return _SystemConfig.Contract.SetGasLimit(&_SystemConfig.TransactOpts, _gasLimit) return _SystemConfig.Contract.SetGasLimit(&_SystemConfig.TransactOpts, _gasLimit)
} }
// SetResourceConfig is a paid mutator transaction binding the contract method 0xc71973f6.
//
// Solidity: function setResourceConfig((uint32,uint8,uint8,uint32,uint32,uint128) _config) returns()
func (_SystemConfig *SystemConfigTransactor) SetResourceConfig(opts *bind.TransactOpts, _config ResourceMeteringResourceConfig) (*types.Transaction, error) {
return _SystemConfig.contract.Transact(opts, "setResourceConfig", _config)
}
// SetResourceConfig is a paid mutator transaction binding the contract method 0xc71973f6.
//
// Solidity: function setResourceConfig((uint32,uint8,uint8,uint32,uint32,uint128) _config) returns()
func (_SystemConfig *SystemConfigSession) SetResourceConfig(_config ResourceMeteringResourceConfig) (*types.Transaction, error) {
return _SystemConfig.Contract.SetResourceConfig(&_SystemConfig.TransactOpts, _config)
}
// SetResourceConfig is a paid mutator transaction binding the contract method 0xc71973f6.
//
// Solidity: function setResourceConfig((uint32,uint8,uint8,uint32,uint32,uint128) _config) returns()
func (_SystemConfig *SystemConfigTransactorSession) SetResourceConfig(_config ResourceMeteringResourceConfig) (*types.Transaction, error) {
return _SystemConfig.Contract.SetResourceConfig(&_SystemConfig.TransactOpts, _config)
}
// SetUnsafeBlockSigner is a paid mutator transaction binding the contract method 0x18d13918. // SetUnsafeBlockSigner is a paid mutator transaction binding the contract method 0x18d13918.
// //
// Solidity: function setUnsafeBlockSigner(address _unsafeBlockSigner) returns() // Solidity: function setUnsafeBlockSigner(address _unsafeBlockSigner) returns()
......
...@@ -9,11 +9,11 @@ import ( ...@@ -9,11 +9,11 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/solc" "github.com/ethereum-optimism/optimism/op-bindings/solc"
) )
const SystemConfigStorageLayoutJSON = "{\"storage\":[{\"astId\":1000,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"_initialized\",\"offset\":0,\"slot\":\"0\",\"type\":\"t_uint8\"},{\"astId\":1001,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"_initializing\",\"offset\":1,\"slot\":\"0\",\"type\":\"t_bool\"},{\"astId\":1002,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"__gap\",\"offset\":0,\"slot\":\"1\",\"type\":\"t_array(t_uint256)1010_storage\"},{\"astId\":1003,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"_owner\",\"offset\":0,\"slot\":\"51\",\"type\":\"t_address\"},{\"astId\":1004,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"__gap\",\"offset\":0,\"slot\":\"52\",\"type\":\"t_array(t_uint256)1009_storage\"},{\"astId\":1005,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"overhead\",\"offset\":0,\"slot\":\"101\",\"type\":\"t_uint256\"},{\"astId\":1006,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"scalar\",\"offset\":0,\"slot\":\"102\",\"type\":\"t_uint256\"},{\"astId\":1007,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"batcherHash\",\"offset\":0,\"slot\":\"103\",\"type\":\"t_bytes32\"},{\"astId\":1008,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"gasLimit\",\"offset\":0,\"slot\":\"104\",\"type\":\"t_uint64\"}],\"types\":{\"t_address\":{\"encoding\":\"inplace\",\"label\":\"address\",\"numberOfBytes\":\"20\"},\"t_array(t_uint256)1009_storage\":{\"encoding\":\"inplace\",\"label\":\"uint256[49]\",\"numberOfBytes\":\"1568\"},\"t_array(t_uint256)1010_storage\":{\"encoding\":\"inplace\",\"label\":\"uint256[50]\",\"numberOfBytes\":\"1600\"},\"t_bool\":{\"encoding\":\"inplace\",\"label\":\"bool\",\"numberOfBytes\":\"1\"},\"t_bytes32\":{\"encoding\":\"inplace\",\"label\":\"bytes32\",\"numberOfBytes\":\"32\"},\"t_uint256\":{\"encoding\":\"inplace\",\"label\":\"uint256\",\"numberOfBytes\":\"32\"},\"t_uint64\":{\"encoding\":\"inplace\",\"label\":\"uint64\",\"numberOfBytes\":\"8\"},\"t_uint8\":{\"encoding\":\"inplace\",\"label\":\"uint8\",\"numberOfBytes\":\"1\"}}}" const SystemConfigStorageLayoutJSON = "{\"storage\":[{\"astId\":1000,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"_initialized\",\"offset\":0,\"slot\":\"0\",\"type\":\"t_uint8\"},{\"astId\":1001,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"_initializing\",\"offset\":1,\"slot\":\"0\",\"type\":\"t_bool\"},{\"astId\":1002,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"__gap\",\"offset\":0,\"slot\":\"1\",\"type\":\"t_array(t_uint256)1011_storage\"},{\"astId\":1003,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"_owner\",\"offset\":0,\"slot\":\"51\",\"type\":\"t_address\"},{\"astId\":1004,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"__gap\",\"offset\":0,\"slot\":\"52\",\"type\":\"t_array(t_uint256)1010_storage\"},{\"astId\":1005,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"overhead\",\"offset\":0,\"slot\":\"101\",\"type\":\"t_uint256\"},{\"astId\":1006,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"scalar\",\"offset\":0,\"slot\":\"102\",\"type\":\"t_uint256\"},{\"astId\":1007,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"batcherHash\",\"offset\":0,\"slot\":\"103\",\"type\":\"t_bytes32\"},{\"astId\":1008,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"gasLimit\",\"offset\":0,\"slot\":\"104\",\"type\":\"t_uint64\"},{\"astId\":1009,\"contract\":\"contracts/L1/SystemConfig.sol:SystemConfig\",\"label\":\"_resourceConfig\",\"offset\":0,\"slot\":\"105\",\"type\":\"t_struct(ResourceConfig)1012_storage\"}],\"types\":{\"t_address\":{\"encoding\":\"inplace\",\"label\":\"address\",\"numberOfBytes\":\"20\"},\"t_array(t_uint256)1010_storage\":{\"encoding\":\"inplace\",\"label\":\"uint256[49]\",\"numberOfBytes\":\"1568\"},\"t_array(t_uint256)1011_storage\":{\"encoding\":\"inplace\",\"label\":\"uint256[50]\",\"numberOfBytes\":\"1600\"},\"t_bool\":{\"encoding\":\"inplace\",\"label\":\"bool\",\"numberOfBytes\":\"1\"},\"t_bytes32\":{\"encoding\":\"inplace\",\"label\":\"bytes32\",\"numberOfBytes\":\"32\"},\"t_struct(ResourceConfig)1012_storage\":{\"encoding\":\"inplace\",\"label\":\"struct ResourceMetering.ResourceConfig\",\"numberOfBytes\":\"32\"},\"t_uint128\":{\"encoding\":\"inplace\",\"label\":\"uint128\",\"numberOfBytes\":\"16\"},\"t_uint256\":{\"encoding\":\"inplace\",\"label\":\"uint256\",\"numberOfBytes\":\"32\"},\"t_uint32\":{\"encoding\":\"inplace\",\"label\":\"uint32\",\"numberOfBytes\":\"4\"},\"t_uint64\":{\"encoding\":\"inplace\",\"label\":\"uint64\",\"numberOfBytes\":\"8\"},\"t_uint8\":{\"encoding\":\"inplace\",\"label\":\"uint8\",\"numberOfBytes\":\"1\"}}}"
var SystemConfigStorageLayout = new(solc.StorageLayout) var SystemConfigStorageLayout = new(solc.StorageLayout)
var SystemConfigDeployedBin = "0x608060405234801561001057600080fd5b506004361061011b5760003560e01c80638f974d7f116100b2578063e81b2c6d11610081578063f45e65d811610066578063f45e65d814610286578063f68016b71461028f578063ffa1ad74146102a357600080fd5b8063e81b2c6d1461026a578063f2fde38b1461027357600080fd5b80638f974d7f1461021e578063935f029e14610231578063b40a817c14610244578063c9b26f611461025757600080fd5b80634f16540b116100ee5780634f16540b146101bc57806354fd4d50146101e3578063715018a6146101f85780638da5cb5b1461020057600080fd5b80630c18c1621461012057806318d139181461013c5780631fd19ee11461015157806329477e8614610199575b600080fd5b61012960655481565b6040519081526020015b60405180910390f35b61014f61014a366004610cb6565b6102ab565b005b7f65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c08545b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610133565b6101a3627a120081565b60405167ffffffffffffffff9091168152602001610133565b6101297f65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c0881565b6101eb61036f565b6040516101339190610d52565b61014f610412565b60335473ffffffffffffffffffffffffffffffffffffffff16610174565b61014f61022c366004610d7d565b610426565b61014f61023f366004610ddc565b6106aa565b61014f610252366004610dfe565b610743565b61014f610265366004610e19565b61081b565b61012960675481565b61014f610281366004610cb6565b61084b565b61012960665481565b6068546101a39067ffffffffffffffff1681565b610129600081565b6102b361091e565b6102db817f65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c0855565b6040805173ffffffffffffffffffffffffffffffffffffffff8316602082015260009101604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152919052905060035b60007f1d2b0bda21d56b8bd12d4f94ebacffdfb35f5e226f84b461103bb8beab6353be836040516103639190610d52565b60405180910390a35050565b606061039a7f000000000000000000000000000000000000000000000000000000000000000061099f565b6103c37f000000000000000000000000000000000000000000000000000000000000000061099f565b6103ec7f000000000000000000000000000000000000000000000000000000000000000061099f565b6040516020016103fe93929190610e32565b604051602081830303815290604052905090565b61041a61091e565b6104246000610adc565b565b600054610100900460ff16158080156104465750600054600160ff909116105b806104605750303b158015610460575060005460ff166001145b6104f1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084015b60405180910390fd5b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055801561054f57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b627a120067ffffffffffffffff841610156105c6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f53797374656d436f6e6669673a20676173206c696d697420746f6f206c6f770060448201526064016104e8565b6105ce610b53565b6105d78761084b565b606586905560668590556067849055606880547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff85161790557f65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c0882905580156106a157600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b50505050505050565b6106b261091e565b606582905560668190556040805160208101849052908101829052600090606001604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190529050600160007f1d2b0bda21d56b8bd12d4f94ebacffdfb35f5e226f84b461103bb8beab6353be836040516107369190610d52565b60405180910390a3505050565b61074b61091e565b627a120067ffffffffffffffff821610156107c2576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f53797374656d436f6e6669673a20676173206c696d697420746f6f206c6f770060448201526064016104e8565b606880547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff83169081179091556040805160208082019390935281518082039093018352810190526002610332565b61082361091e565b6067819055604080516020808201849052825180830390910181529082019091526000610332565b61085361091e565b73ffffffffffffffffffffffffffffffffffffffff81166108f6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016104e8565b6108ff81610adc565b50565b73ffffffffffffffffffffffffffffffffffffffff163b151590565b60335473ffffffffffffffffffffffffffffffffffffffff163314610424576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016104e8565b6060816000036109e257505060408051808201909152600181527f3000000000000000000000000000000000000000000000000000000000000000602082015290565b8160005b8115610a0c57806109f681610ed7565b9150610a059050600a83610f3e565b91506109e6565b60008167ffffffffffffffff811115610a2757610a27610f52565b6040519080825280601f01601f191660200182016040528015610a51576020820181803683370190505b5090505b8415610ad457610a66600183610f81565b9150610a73600a86610f98565b610a7e906030610fac565b60f81b818381518110610a9357610a93610fc4565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350610acd600a86610f3e565b9450610a55565b949350505050565b6033805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b600054610100900460ff16610bea576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016104e8565b610424600054610100900460ff16610c84576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016104e8565b61042433610adc565b803573ffffffffffffffffffffffffffffffffffffffff81168114610cb157600080fd5b919050565b600060208284031215610cc857600080fd5b610cd182610c8d565b9392505050565b60005b83811015610cf3578181015183820152602001610cdb565b83811115610d02576000848401525b50505050565b60008151808452610d20816020860160208601610cd8565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000610cd16020830184610d08565b803567ffffffffffffffff81168114610cb157600080fd5b60008060008060008060c08789031215610d9657600080fd5b610d9f87610c8d565b9550602087013594506040870135935060608701359250610dc260808801610d65565b9150610dd060a08801610c8d565b90509295509295509295565b60008060408385031215610def57600080fd5b50508035926020909101359150565b600060208284031215610e1057600080fd5b610cd182610d65565b600060208284031215610e2b57600080fd5b5035919050565b60008451610e44818460208901610cd8565b80830190507f2e000000000000000000000000000000000000000000000000000000000000008082528551610e80816001850160208a01610cd8565b60019201918201528351610e9b816002840160208801610cd8565b0160020195945050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610f0857610f08610ea8565b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600082610f4d57610f4d610f0f565b500490565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082821015610f9357610f93610ea8565b500390565b600082610fa757610fa7610f0f565b500690565b60008219821115610fbf57610fbf610ea8565b500190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fdfea164736f6c634300080f000a" var SystemConfigDeployedBin = "0x608060405234801561001057600080fd5b506004361061016c5760003560e01c8063b40a817c116100cd578063f2fde38b11610081578063f68016b711610066578063f68016b71461041c578063f975e92514610430578063ffa1ad741461044357600080fd5b8063f2fde38b14610400578063f45e65d81461041357600080fd5b8063c9b26f61116100b2578063c9b26f61146102b0578063cc731b02146102c3578063e81b2c6d146103f757600080fd5b8063b40a817c1461028a578063c71973f61461029d57600080fd5b80634f16540b11610124578063715018a611610109578063715018a6146102515780638da5cb5b14610259578063935f029e1461027757600080fd5b80634f16540b1461021557806354fd4d501461023c57600080fd5b80631fd19ee1116101555780631fd19ee1146101a257806329477e86146101ea5780634add321d1461020d57600080fd5b80630c18c1621461017157806318d139181461018d575b600080fd5b61017a60655481565b6040519081526020015b60405180910390f35b6101a061019b36600461132c565b61044b565b005b7f65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c08545b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610184565b6101f4627a120081565b60405167ffffffffffffffff9091168152602001610184565b6101f461050f565b61017a7f65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c0881565b61024461053a565b60405161018491906113c8565b6101a06105dd565b60335473ffffffffffffffffffffffffffffffffffffffff166101c5565b6101a06102853660046113db565b6105f1565b6101a0610298366004611415565b61068a565b6101a06102ab36600461156d565b610775565b6101a06102be366004611589565b610789565b6103876040805160c081018252600080825260208201819052918101829052606081018290526080810182905260a0810191909152506040805160c08101825260695463ffffffff8082168352640100000000820460ff9081166020850152650100000000008304169383019390935266010000000000008104831660608301526a0100000000000000000000810490921660808201526e0100000000000000000000000000009091046fffffffffffffffffffffffffffffffff1660a082015290565b6040516101849190600060c08201905063ffffffff80845116835260ff602085015116602084015260ff6040850151166040840152806060850151166060840152806080850151166080840152506fffffffffffffffffffffffffffffffff60a08401511660a083015292915050565b61017a60675481565b6101a061040e36600461132c565b6107b9565b61017a60665481565b6068546101f49067ffffffffffffffff1681565b6101a061043e3660046115a2565b61086d565b61017a600081565b610453610b20565b61047b817f65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c0855565b6040805173ffffffffffffffffffffffffffffffffffffffff8316602082015260009101604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152919052905060035b60007f1d2b0bda21d56b8bd12d4f94ebacffdfb35f5e226f84b461103bb8beab6353be8360405161050391906113c8565b60405180910390a35050565b6069546000906105359063ffffffff6a0100000000000000000000820481169116611644565b905090565b60606105657f0000000000000000000000000000000000000000000000000000000000000000610ba1565b61058e7f0000000000000000000000000000000000000000000000000000000000000000610ba1565b6105b77f0000000000000000000000000000000000000000000000000000000000000000610ba1565b6040516020016105c993929190611670565b604051602081830303815290604052905090565b6105e5610b20565b6105ef6000610cde565b565b6105f9610b20565b606582905560668190556040805160208101849052908101829052600090606001604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190529050600160007f1d2b0bda21d56b8bd12d4f94ebacffdfb35f5e226f84b461103bb8beab6353be8360405161067d91906113c8565b60405180910390a3505050565b610692610b20565b61069a61050f565b67ffffffffffffffff168167ffffffffffffffff16101561071c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f53797374656d436f6e6669673a20676173206c696d697420746f6f206c6f770060448201526064015b60405180910390fd5b606880547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff831690811790915560408051602080820193909352815180820390930183528101905260026104d2565b61077d610b20565b61078681610d55565b50565b610791610b20565b60678190556040805160208082018490528251808303909101815290820190915260006104d2565b6107c1610b20565b73ffffffffffffffffffffffffffffffffffffffff8116610864576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152608401610713565b61078681610cde565b600054610100900460ff161580801561088d5750600054600160ff909116105b806108a75750303b1580156108a7575060005460ff166001145b610933576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a65640000000000000000000000000000000000006064820152608401610713565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055801561099157600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b6109996111c9565b6109a2886107b9565b606587905560668690556067859055606880547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff86161790557f65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c08839055610a1282610d55565b610a1a61050f565b67ffffffffffffffff168467ffffffffffffffff161015610a97576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f53797374656d436f6e6669673a20676173206c696d697420746f6f206c6f77006044820152606401610713565b8015610afa57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050505050565b73ffffffffffffffffffffffffffffffffffffffff163b151590565b60335473ffffffffffffffffffffffffffffffffffffffff1633146105ef576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610713565b606081600003610be457505060408051808201909152600181527f3000000000000000000000000000000000000000000000000000000000000000602082015290565b8160005b8115610c0e5780610bf8816116e6565b9150610c079050600a8361174d565b9150610be8565b60008167ffffffffffffffff811115610c2957610c29611430565b6040519080825280601f01601f191660200182016040528015610c53576020820181803683370190505b5090505b8415610cd657610c68600183611761565b9150610c75600a86611778565b610c8090603061178c565b60f81b818381518110610c9557610c956117a4565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350610ccf600a8661174d565b9450610c57565b949350505050565b6033805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b8060a001516fffffffffffffffffffffffffffffffff16816060015163ffffffff161115610e05576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603560248201527f53797374656d436f6e6669673a206d696e206261736520666565206d7573742060448201527f6265206c657373207468616e206d6178206261736500000000000000000000006064820152608401610713565b6000816040015160ff1611610e9c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f53797374656d436f6e6669673a2064656e6f6d696e61746f722063616e6e6f7460448201527f20626520300000000000000000000000000000000000000000000000000000006064820152608401610713565b6068546080820151825167ffffffffffffffff90921691610ebd91906117d3565b63ffffffff161115610f2b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f53797374656d436f6e6669673a20676173206c696d697420746f6f206c6f77006044820152606401610713565b6000816020015160ff1611610fc2576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602f60248201527f53797374656d436f6e6669673a20656c6173746963697479206d756c7469706c60448201527f6965722063616e6e6f74206265203000000000000000000000000000000000006064820152608401610713565b8051602082015163ffffffff82169160ff90911690610fe29082906117f2565b610fec9190611815565b63ffffffff161461107f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603760248201527f53797374656d436f6e6669673a20707265636973696f6e206c6f73732077697460448201527f6820746172676574207265736f75726365206c696d69740000000000000000006064820152608401610713565b805160698054602084015160408501516060860151608087015160a09097015163ffffffff9687167fffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000009095169490941764010000000060ff94851602177fffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffffff166501000000000093909216929092027fffffffffffffffffffffffffffffffffffffffffffff00000000ffffffffffff1617660100000000000091851691909102177fffff0000000000000000000000000000000000000000ffffffffffffffffffff166a010000000000000000000093909416929092027fffff00000000000000000000000000000000ffffffffffffffffffffffffffff16929092176e0100000000000000000000000000006fffffffffffffffffffffffffffffffff90921691909102179055565b600054610100900460ff16611260576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152608401610713565b6105ef600054610100900460ff166112fa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152608401610713565b6105ef33610cde565b803573ffffffffffffffffffffffffffffffffffffffff8116811461132757600080fd5b919050565b60006020828403121561133e57600080fd5b61134782611303565b9392505050565b60005b83811015611369578181015183820152602001611351565b83811115611378576000848401525b50505050565b6000815180845261139681602086016020860161134e565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000611347602083018461137e565b600080604083850312156113ee57600080fd5b50508035926020909101359150565b803567ffffffffffffffff8116811461132757600080fd5b60006020828403121561142757600080fd5b611347826113fd565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b803563ffffffff8116811461132757600080fd5b803560ff8116811461132757600080fd5b80356fffffffffffffffffffffffffffffffff8116811461132757600080fd5b600060c082840312156114b657600080fd5b60405160c0810181811067ffffffffffffffff82111715611500577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60405290508061150f8361145f565b815261151d60208401611473565b602082015261152e60408401611473565b604082015261153f6060840161145f565b60608201526115506080840161145f565b608082015261156160a08401611484565b60a08201525092915050565b600060c0828403121561157f57600080fd5b61134783836114a4565b60006020828403121561159b57600080fd5b5035919050565b6000806000806000806000610180888a0312156115be57600080fd5b6115c788611303565b96506020880135955060408801359450606088013593506115ea608089016113fd565b92506115f860a08901611303565b91506116078960c08a016114a4565b905092959891949750929550565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600067ffffffffffffffff80831681851680830382111561166757611667611615565b01949350505050565b6000845161168281846020890161134e565b80830190507f2e0000000000000000000000000000000000000000000000000000000000000080825285516116be816001850160208a0161134e565b600192019182015283516116d981600284016020880161134e565b0160020195945050505050565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361171757611717611615565b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b60008261175c5761175c61171e565b500490565b60008282101561177357611773611615565b500390565b6000826117875761178761171e565b500690565b6000821982111561179f5761179f611615565b500190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600063ffffffff80831681851680830382111561166757611667611615565b600063ffffffff808416806118095761180961171e565b92169190910492915050565b600063ffffffff8083168185168183048111821515161561183857611838611615565b0294935050505056fea164736f6c634300080f000a"
func init() { func init() {
if err := json.Unmarshal([]byte(SystemConfigStorageLayoutJSON), SystemConfigStorageLayout); err != nil { if err := json.Unmarshal([]byte(SystemConfigStorageLayoutJSON), SystemConfigStorageLayout); err != nil {
......
...@@ -16,7 +16,6 @@ import ( ...@@ -16,7 +16,6 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -320,7 +319,7 @@ func PostCheckPredeploys(prevDB, currDB *state.StateDB) error { ...@@ -320,7 +319,7 @@ func PostCheckPredeploys(prevDB, currDB *state.StateDB) error {
// PostCheckPredeployStorage will ensure that the predeploys had their storage // PostCheckPredeployStorage will ensure that the predeploys had their storage
// wiped correctly. // wiped correctly.
func PostCheckPredeployStorage(db vm.StateDB, finalSystemOwner common.Address, proxyAdminOwner common.Address) error { func PostCheckPredeployStorage(db *state.StateDB, finalSystemOwner common.Address, proxyAdminOwner common.Address) error {
for name, addr := range predeploys.Predeploys { for name, addr := range predeploys.Predeploys {
if addr == nil { if addr == nil {
return fmt.Errorf("nil address in predeploys mapping for %s", name) return fmt.Errorf("nil address in predeploys mapping for %s", name)
...@@ -468,7 +467,7 @@ func PostCheckLegacyETH(prevDB, migratedDB *state.StateDB, migrationData crossdo ...@@ -468,7 +467,7 @@ func PostCheckLegacyETH(prevDB, migratedDB *state.StateDB, migrationData crossdo
} }
// PostCheckL1Block checks that the L1Block contract was properly set to the L1 origin. // PostCheckL1Block checks that the L1Block contract was properly set to the L1 origin.
func PostCheckL1Block(db vm.StateDB, info *derive.L1BlockInfo) error { func PostCheckL1Block(db *state.StateDB, info *derive.L1BlockInfo) error {
// Slot 0 is the concatenation of the block number and timestamp // Slot 0 is the concatenation of the block number and timestamp
data := db.GetState(predeploys.L1BlockAddr, common.Hash{}).Bytes() data := db.GetState(predeploys.L1BlockAddr, common.Hash{}).Bytes()
blockNumber := binary.BigEndian.Uint64(data[24:]) blockNumber := binary.BigEndian.Uint64(data[24:])
...@@ -558,7 +557,7 @@ func PostCheckL1Block(db vm.StateDB, info *derive.L1BlockInfo) error { ...@@ -558,7 +557,7 @@ func PostCheckL1Block(db vm.StateDB, info *derive.L1BlockInfo) error {
return nil return nil
} }
func CheckWithdrawalsAfter(db vm.StateDB, data crossdomain.MigrationData, l1CrossDomainMessenger *common.Address) error { func CheckWithdrawalsAfter(db *state.StateDB, data crossdomain.MigrationData, l1CrossDomainMessenger *common.Address) error {
wds, invalidMessages, err := data.ToWithdrawals() wds, invalidMessages, err := data.ToWithdrawals()
if err != nil { if err != nil {
return err return err
......
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"strings" "strings"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
...@@ -22,19 +23,45 @@ import ( ...@@ -22,19 +23,45 @@ import (
"github.com/ethereum-optimism/optimism/op-chain-ops/state" "github.com/ethereum-optimism/optimism/op-chain-ops/state"
) )
var proxies = []string{ var (
// proxies represents the set of proxies in front of contracts.
proxies = []string{
"SystemConfigProxy", "SystemConfigProxy",
"L2OutputOracleProxy", "L2OutputOracleProxy",
"L1CrossDomainMessengerProxy", "L1CrossDomainMessengerProxy",
"L1StandardBridgeProxy", "L1StandardBridgeProxy",
"OptimismPortalProxy", "OptimismPortalProxy",
"OptimismMintableERC20FactoryProxy", "OptimismMintableERC20FactoryProxy",
} }
// portalMeteringSlot is the storage slot containing the metering params.
var portalMeteringSlot = common.Hash{31: 0x01} portalMeteringSlot = common.Hash{31: 0x01}
// zeroHash represents the zero value for a hash.
zeroHash = common.Hash{}
// uint128Max is type(uint128).max and is set in the init function.
uint128Max = new(big.Int)
// The default values for the ResourceConfig, used as part of
// an EIP-1559 curve for deposit gas.
defaultResourceConfig = bindings.ResourceMeteringResourceConfig{
MaxResourceLimit: 20_000_000,
ElasticityMultiplier: 10,
BaseFeeMaxChangeDenominator: 8,
MinimumBaseFee: params.GWei,
SystemTxMaxGas: 1_000_000,
}
)
var zeroHash common.Hash func init() {
var ok bool
uint128Max, ok = new(big.Int).SetString("ffffffffffffffffffffffffffffffff", 16)
if !ok {
panic("bad uint128Max")
}
// Set the maximum base fee on the default config.
defaultResourceConfig.MaximumBaseFee = uint128Max
}
// BuildL1DeveloperGenesis will create a L1 genesis block after creating
// all of the state required for an Optimism network to function.
func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) { func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) {
if config.L2OutputOracleStartingTimestamp != -1 { if config.L2OutputOracleStartingTimestamp != -1 {
return nil, errors.New("l2oo starting timestamp must be -1") return nil, errors.New("l2oo starting timestamp must be -1")
...@@ -67,6 +94,26 @@ func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) { ...@@ -67,6 +94,26 @@ func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
portalABI, err := bindings.OptimismPortalMetaData.GetAbi()
if err != nil {
return nil, err
}
// Initialize the OptimismPortal without being paused
data, err := portalABI.Pack("initialize", false)
if err != nil {
return nil, fmt.Errorf("cannot abi encode initialize for OptimismPortal: %w", err)
}
if _, err := upgradeProxy(
backend,
opts,
depsByName["OptimismPortalProxy"].Address,
depsByName["OptimismPortal"].Address,
data,
); err != nil {
return nil, fmt.Errorf("cannot upgrade OptimismPortalProxy: %w", err)
}
sysCfgABI, err := bindings.SystemConfigMetaData.GetAbi() sysCfgABI, err := bindings.SystemConfigMetaData.GetAbi()
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -75,7 +122,8 @@ func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) { ...@@ -75,7 +122,8 @@ func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) {
if gasLimit == 0 { if gasLimit == 0 {
gasLimit = defaultL2GasLimit gasLimit = defaultL2GasLimit
} }
data, err := sysCfgABI.Pack(
data, err = sysCfgABI.Pack(
"initialize", "initialize",
config.FinalSystemOwner, config.FinalSystemOwner,
uint642Big(config.GasPriceOracleOverhead), uint642Big(config.GasPriceOracleOverhead),
...@@ -83,6 +131,7 @@ func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) { ...@@ -83,6 +131,7 @@ func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) {
config.BatchSenderAddress.Hash(), config.BatchSenderAddress.Hash(),
gasLimit, gasLimit,
config.P2PSequencerAddress, config.P2PSequencerAddress,
defaultResourceConfig,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot abi encode initialize for SystemConfig: %w", err) return nil, fmt.Errorf("cannot abi encode initialize for SystemConfig: %w", err)
...@@ -94,7 +143,7 @@ func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) { ...@@ -94,7 +143,7 @@ func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) {
depsByName["SystemConfig"].Address, depsByName["SystemConfig"].Address,
data, data,
); err != nil { ); err != nil {
return nil, err return nil, fmt.Errorf("cannot upgrade SystemConfigProxy: %w", err)
} }
l2ooABI, err := bindings.L2OutputOracleMetaData.GetAbi() l2ooABI, err := bindings.L2OutputOracleMetaData.GetAbi()
...@@ -119,24 +168,6 @@ func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) { ...@@ -119,24 +168,6 @@ func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) {
return nil, err return nil, err
} }
portalABI, err := bindings.OptimismPortalMetaData.GetAbi()
if err != nil {
return nil, err
}
// Initialize the OptimismPortal without being paused
data, err = portalABI.Pack("initialize", false)
if err != nil {
return nil, fmt.Errorf("cannot abi encode initialize for OptimismPortal: %w", err)
}
if _, err := upgradeProxy(
backend,
opts,
depsByName["OptimismPortalProxy"].Address,
depsByName["OptimismPortal"].Address,
data,
); err != nil {
return nil, err
}
l1XDMABI, err := bindings.L1CrossDomainMessengerMetaData.GetAbi() l1XDMABI, err := bindings.L1CrossDomainMessengerMetaData.GetAbi()
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -264,6 +295,7 @@ func deployL1Contracts(config *DeployConfig, backend *backends.SimulatedBackend) ...@@ -264,6 +295,7 @@ func deployL1Contracts(config *DeployConfig, backend *backends.SimulatedBackend)
if gasLimit == 0 { if gasLimit == 0 {
gasLimit = defaultL2GasLimit gasLimit = defaultL2GasLimit
} }
constructors = append(constructors, []deployer.Constructor{ constructors = append(constructors, []deployer.Constructor{
{ {
Name: "SystemConfig", Name: "SystemConfig",
...@@ -274,6 +306,7 @@ func deployL1Contracts(config *DeployConfig, backend *backends.SimulatedBackend) ...@@ -274,6 +306,7 @@ func deployL1Contracts(config *DeployConfig, backend *backends.SimulatedBackend)
config.BatchSenderAddress.Hash(), // left-padded 32 bytes value, version is zero anyway config.BatchSenderAddress.Hash(), // left-padded 32 bytes value, version is zero anyway
gasLimit, gasLimit,
config.P2PSequencerAddress, config.P2PSequencerAddress,
defaultResourceConfig,
}, },
}, },
{ {
...@@ -297,6 +330,7 @@ func deployL1Contracts(config *DeployConfig, backend *backends.SimulatedBackend) ...@@ -297,6 +330,7 @@ func deployL1Contracts(config *DeployConfig, backend *backends.SimulatedBackend)
predeploys.DevL2OutputOracleAddr, predeploys.DevL2OutputOracleAddr,
config.PortalGuardian, config.PortalGuardian,
true, // _paused true, // _paused
predeploys.DevSystemConfigAddr,
}, },
}, },
{ {
...@@ -342,6 +376,7 @@ func l1Deployer(backend *backends.SimulatedBackend, opts *bind.TransactOpts, dep ...@@ -342,6 +376,7 @@ func l1Deployer(backend *backends.SimulatedBackend, opts *bind.TransactOpts, dep
deployment.Args[3].(common.Hash), deployment.Args[3].(common.Hash),
deployment.Args[4].(uint64), deployment.Args[4].(uint64),
deployment.Args[5].(common.Address), deployment.Args[5].(common.Address),
deployment.Args[6].(bindings.ResourceMeteringResourceConfig),
) )
case "L2OutputOracle": case "L2OutputOracle":
_, tx, _, err = bindings.DeployL2OutputOracle( _, tx, _, err = bindings.DeployL2OutputOracle(
...@@ -362,6 +397,7 @@ func l1Deployer(backend *backends.SimulatedBackend, opts *bind.TransactOpts, dep ...@@ -362,6 +397,7 @@ func l1Deployer(backend *backends.SimulatedBackend, opts *bind.TransactOpts, dep
deployment.Args[0].(common.Address), deployment.Args[0].(common.Address),
deployment.Args[1].(common.Address), deployment.Args[1].(common.Address),
deployment.Args[2].(bool), deployment.Args[2].(bool),
deployment.Args[3].(common.Address),
) )
case "L1CrossDomainMessenger": case "L1CrossDomainMessenger":
_, tx, _, err = bindings.DeployL1CrossDomainMessenger( _, tx, _, err = bindings.DeployL1CrossDomainMessenger(
...@@ -421,6 +457,15 @@ func l1Deployer(backend *backends.SimulatedBackend, opts *bind.TransactOpts, dep ...@@ -421,6 +457,15 @@ func l1Deployer(backend *backends.SimulatedBackend, opts *bind.TransactOpts, dep
func upgradeProxy(backend *backends.SimulatedBackend, opts *bind.TransactOpts, proxyAddr common.Address, implAddr common.Address, callData []byte) (*types.Transaction, error) { func upgradeProxy(backend *backends.SimulatedBackend, opts *bind.TransactOpts, proxyAddr common.Address, implAddr common.Address, callData []byte) (*types.Transaction, error) {
var tx *types.Transaction var tx *types.Transaction
code, err := backend.CodeAt(context.Background(), implAddr, nil)
if err != nil {
return nil, err
}
if len(code) == 0 {
return nil, fmt.Errorf("no code at %s", implAddr)
}
proxy, err := bindings.NewProxy(proxyAddr, backend) proxy, err := bindings.NewProxy(proxyAddr, backend)
if err != nil { if err != nil {
return nil, err return nil, err
......
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
...@@ -100,6 +101,30 @@ func TestBuildL1DeveloperGenesis(t *testing.T) { ...@@ -100,6 +101,30 @@ func TestBuildL1DeveloperGenesis(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, "Wrapped Ether", name) require.Equal(t, "Wrapped Ether", name)
sysCfg, err := bindings.NewSystemConfig(predeploys.DevSystemConfigAddr, sim)
require.NoError(t, err)
cfg, err := sysCfg.ResourceConfig(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, cfg, defaultResourceConfig)
owner, err = sysCfg.Owner(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, owner, config.FinalSystemOwner)
overhead, err := sysCfg.Overhead(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, overhead.Uint64(), config.GasPriceOracleOverhead)
scalar, err := sysCfg.Scalar(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, scalar.Uint64(), config.GasPriceOracleScalar)
batcherHash, err := sysCfg.BatcherHash(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, common.Hash(batcherHash), config.BatchSenderAddress.Hash())
gasLimit, err := sysCfg.GasLimit(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, gasLimit, uint64(config.L2GenesisBlockGasLimit))
unsafeBlockSigner, err := sysCfg.UnsafeBlockSigner(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, unsafeBlockSigner, config.P2PSequencerAddress)
// test that we can do deposits, etc. // test that we can do deposits, etc.
priv, err := crypto.HexToECDSA("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80") priv, err := crypto.HexToECDSA("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80")
require.NoError(t, err) require.NoError(t, err)
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
"l1BlockTime": 15, "l1BlockTime": 15,
"l1GenesisBlockNonce": "0x0", "l1GenesisBlockNonce": "0x0",
"cliqueSignerAddress": "0x0000000000000000000000000000000000000000", "cliqueSignerAddress": "0x0000000000000000000000000000000000000000",
"l1GenesisBlockGasLimit": "0xe4e1c0", "l1GenesisBlockGasLimit": "0x1c9c380",
"l1GenesisBlockDifficulty": "0x1", "l1GenesisBlockDifficulty": "0x1",
"finalSystemOwner": "0x0000000000000000000000000000000000000111", "finalSystemOwner": "0x0000000000000000000000000000000000000111",
"portalGuardian": "0x0000000000000000000000000000000000000112", "portalGuardian": "0x0000000000000000000000000000000000000112",
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
"l1GenesisBlockTimestamp": "0x0", "l1GenesisBlockTimestamp": "0x0",
"l1GenesisBlockBaseFeePerGas": "0x3b9aca00", "l1GenesisBlockBaseFeePerGas": "0x3b9aca00",
"l2GenesisBlockNonce": "0x0", "l2GenesisBlockNonce": "0x0",
"l2GenesisBlockGasLimit": "0xe4e1c0", "l2GenesisBlockGasLimit": "0x1c9c380",
"l2GenesisBlockDifficulty": "0x1", "l2GenesisBlockDifficulty": "0x1",
"l2GenesisBlockMixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "l2GenesisBlockMixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"l2GenesisBlockNumber": "0x0", "l2GenesisBlockNumber": "0x0",
......
...@@ -72,6 +72,7 @@ func NewL1Replica(t Testing, log log.Logger, genesis *core.Genesis) *L1Replica { ...@@ -72,6 +72,7 @@ func NewL1Replica(t Testing, log log.Logger, genesis *core.Genesis) *L1Replica {
backend, err := eth.New(n, ethCfg) backend, err := eth.New(n, ethCfg)
require.NoError(t, err) require.NoError(t, err)
backend.Merger().FinalizePoS()
n.RegisterAPIs(tracers.APIs(backend.APIBackend)) n.RegisterAPIs(tracers.APIs(backend.APIBackend))
......
...@@ -6,16 +6,18 @@ import ( ...@@ -6,16 +6,18 @@ import (
"math/big" "math/big"
"time" "time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-node/sources" "github.com/ethereum-optimism/optimism/op-node/sources"
"github.com/ethereum-optimism/optimism/op-proposer/metrics"
"github.com/ethereum-optimism/optimism/op-proposer/proposer" "github.com/ethereum-optimism/optimism/op-proposer/proposer"
opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
) )
...@@ -30,37 +32,35 @@ type L2Proposer struct { ...@@ -30,37 +32,35 @@ type L2Proposer struct {
l1 *ethclient.Client l1 *ethclient.Client
driver *proposer.L2OutputSubmitter driver *proposer.L2OutputSubmitter
address common.Address address common.Address
privKey *ecdsa.PrivateKey
contractAddr common.Address
lastTx common.Hash lastTx common.Hash
} }
type fakeTxMgr struct {
from common.Address
}
func (f fakeTxMgr) From() common.Address {
return f.from
}
func (f fakeTxMgr) Send(_ context.Context, _ txmgr.TxCandidate) (*types.Receipt, error) {
panic("unimplemented")
}
func NewL2Proposer(t Testing, log log.Logger, cfg *ProposerCfg, l1 *ethclient.Client, rollupCl *sources.RollupClient) *L2Proposer { func NewL2Proposer(t Testing, log log.Logger, cfg *ProposerCfg, l1 *ethclient.Client, rollupCl *sources.RollupClient) *L2Proposer {
signer := func(chainID *big.Int) opcrypto.SignerFn {
s := opcrypto.PrivateKeySignerFn(cfg.ProposerKey, chainID)
return func(_ context.Context, addr common.Address, tx *types.Transaction) (*types.Transaction, error) {
return s(addr, tx)
}
}
from := crypto.PubkeyToAddress(cfg.ProposerKey.PublicKey)
proposerCfg := proposer.Config{ proposerCfg := proposer.Config{
L2OutputOracleAddr: cfg.OutputOracleAddr, L2OutputOracleAddr: cfg.OutputOracleAddr,
PollInterval: time.Second, PollInterval: time.Second,
TxManagerConfig: txmgr.Config{
ResubmissionTimeout: 5 * time.Second,
ReceiptQueryInterval: time.Second,
NumConfirmations: 1,
SafeAbortNonceTooLowCount: 4,
From: from,
// Signer is loaded in `proposer.NewL2OutputSubmitter`
},
L1Client: l1, L1Client: l1,
RollupClient: rollupCl, RollupClient: rollupCl,
AllowNonFinalized: cfg.AllowNonFinalized, AllowNonFinalized: cfg.AllowNonFinalized,
From: from, // We use custom signing here instead of using the transaction manager.
SignerFnFactory: signer, TxManager: fakeTxMgr{from: crypto.PubkeyToAddress(cfg.ProposerKey.PublicKey)},
} }
dr, err := proposer.NewL2OutputSubmitter(proposerCfg, log) dr, err := proposer.NewL2OutputSubmitter(proposerCfg, log, metrics.NoopMetrics)
require.NoError(t, err) require.NoError(t, err)
return &L2Proposer{ return &L2Proposer{
...@@ -68,7 +68,49 @@ func NewL2Proposer(t Testing, log log.Logger, cfg *ProposerCfg, l1 *ethclient.Cl ...@@ -68,7 +68,49 @@ func NewL2Proposer(t Testing, log log.Logger, cfg *ProposerCfg, l1 *ethclient.Cl
l1: l1, l1: l1,
driver: dr, driver: dr,
address: crypto.PubkeyToAddress(cfg.ProposerKey.PublicKey), address: crypto.PubkeyToAddress(cfg.ProposerKey.PublicKey),
privKey: cfg.ProposerKey,
contractAddr: cfg.OutputOracleAddr,
}
}
// sendTx reimplements creating & sending transactions because we need to do the final send as async in
// the action tests while we do it synchronously in the real system.
func (p *L2Proposer) sendTx(t Testing, data []byte) {
gasTipCap := big.NewInt(2 * params.GWei)
pendingHeader, err := p.l1.HeaderByNumber(t.Ctx(), big.NewInt(-1))
require.NoError(t, err, "need l1 pending header for gas price estimation")
gasFeeCap := new(big.Int).Add(gasTipCap, new(big.Int).Mul(pendingHeader.BaseFee, big.NewInt(2)))
chainID, err := p.l1.ChainID(t.Ctx())
require.NoError(t, err)
nonce, err := p.l1.NonceAt(t.Ctx(), p.address, nil)
require.NoError(t, err)
gasLimit, err := p.l1.EstimateGas(t.Ctx(), ethereum.CallMsg{
From: p.address,
To: &p.contractAddr,
GasFeeCap: gasFeeCap,
GasTipCap: gasTipCap,
Data: data,
})
require.NoError(t, err)
rawTx := &types.DynamicFeeTx{
Nonce: nonce,
To: &p.contractAddr,
Data: data,
GasFeeCap: gasFeeCap,
GasTipCap: gasTipCap,
Gas: gasLimit,
ChainID: chainID,
} }
tx, err := types.SignNewTx(p.privKey, types.LatestSignerForChainID(chainID), rawTx)
require.NoError(t, err, "need to sign tx")
err = p.l1.SendTransaction(t.Ctx(), tx)
require.NoError(t, err, "need to send tx")
p.lastTx = tx.Hash()
} }
func (p *L2Proposer) CanPropose(t Testing) bool { func (p *L2Proposer) CanPropose(t Testing) bool {
...@@ -84,15 +126,12 @@ func (p *L2Proposer) ActMakeProposalTx(t Testing) { ...@@ -84,15 +126,12 @@ func (p *L2Proposer) ActMakeProposalTx(t Testing) {
} }
require.NoError(t, err) require.NoError(t, err)
tx, err := p.driver.CreateProposalTx(t.Ctx(), output) txData, err := p.driver.ProposeL2OutputTxData(output)
require.NoError(t, err) require.NoError(t, err)
// Note: Use L1 instead of the output submitter's transaction manager because // Note: Use L1 instead of the output submitter's transaction manager because
// this is non-blocking while the txmgr is blocking & deadlocks the tests // this is non-blocking while the txmgr is blocking & deadlocks the tests
err = p.l1.SendTransaction(t.Ctx(), tx) p.sendTx(t, txData)
require.NoError(t, err)
p.lastTx = tx.Hash()
} }
func (p *L2Proposer) LastProposalTx() common.Hash { func (p *L2Proposer) LastProposalTx() common.Hash {
......
...@@ -79,7 +79,7 @@ func MakeDeployParams(t require.TestingT, tp *TestParams) *DeployParams { ...@@ -79,7 +79,7 @@ func MakeDeployParams(t require.TestingT, tp *TestParams) *DeployParams {
L1GenesisBlockNonce: 0, L1GenesisBlockNonce: 0,
CliqueSignerAddress: common.Address{}, // proof of stake, no clique CliqueSignerAddress: common.Address{}, // proof of stake, no clique
L1GenesisBlockTimestamp: hexutil.Uint64(time.Now().Unix()), L1GenesisBlockTimestamp: hexutil.Uint64(time.Now().Unix()),
L1GenesisBlockGasLimit: 15_000_000, L1GenesisBlockGasLimit: 30_000_000,
L1GenesisBlockDifficulty: uint64ToBig(1), L1GenesisBlockDifficulty: uint64ToBig(1),
L1GenesisBlockMixHash: common.Hash{}, L1GenesisBlockMixHash: common.Hash{},
L1GenesisBlockCoinbase: common.Address{}, L1GenesisBlockCoinbase: common.Address{},
...@@ -90,7 +90,7 @@ func MakeDeployParams(t require.TestingT, tp *TestParams) *DeployParams { ...@@ -90,7 +90,7 @@ func MakeDeployParams(t require.TestingT, tp *TestParams) *DeployParams {
FinalizationPeriodSeconds: 12, FinalizationPeriodSeconds: 12,
L2GenesisBlockNonce: 0, L2GenesisBlockNonce: 0,
L2GenesisBlockGasLimit: 15_000_000, L2GenesisBlockGasLimit: 30_000_000,
L2GenesisBlockDifficulty: uint64ToBig(0), L2GenesisBlockDifficulty: uint64ToBig(0),
L2GenesisBlockMixHash: common.Hash{}, L2GenesisBlockMixHash: common.Hash{},
L2GenesisBlockNumber: 0, L2GenesisBlockNumber: 0,
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/beacon/engine"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
...@@ -21,6 +22,7 @@ import ( ...@@ -21,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
) )
...@@ -112,7 +114,6 @@ func initL1Geth(cfg *SystemConfig, genesis *core.Genesis, opts ...GethOption) (* ...@@ -112,7 +114,6 @@ func initL1Geth(cfg *SystemConfig, genesis *core.Genesis, opts ...GethOption) (*
ethConfig := &ethconfig.Config{ ethConfig := &ethconfig.Config{
NetworkId: cfg.DeployConfig.L1ChainID, NetworkId: cfg.DeployConfig.L1ChainID,
Genesis: genesis, Genesis: genesis,
Miner: miner.Config{Etherbase: cfg.DeployConfig.CliqueSignerAddress},
} }
nodeConfig := &node.Config{ nodeConfig := &node.Config{
Name: "l1-geth", Name: "l1-geth",
...@@ -128,44 +129,107 @@ func initL1Geth(cfg *SystemConfig, genesis *core.Genesis, opts ...GethOption) (* ...@@ -128,44 +129,107 @@ func initL1Geth(cfg *SystemConfig, genesis *core.Genesis, opts ...GethOption) (*
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
// Activate merge
l1Eth.Merger().FinalizePoS()
// Clique does not have safe/finalized block info. But we do want to test the usage of that, // Instead of running a whole beacon node, we run this fake-proof-of-stake sidecar that sequences L1 blocks using the Engine API.
// since post-merge L1 has it (incl. Goerli testnet which is already upgraded). So we mock it on top of clique. l1Node.RegisterLifecycle(&fakePoS{
l1Node.RegisterLifecycle(&fakeSafeFinalizedL1{
eth: l1Eth, eth: l1Eth,
log: log.Root(), // geth logger is global anyway. Would be nice to replace with a local logger though.
blockTime: cfg.DeployConfig.L1BlockTime,
// for testing purposes we make it really fast, otherwise we don't see it finalize in short tests // for testing purposes we make it really fast, otherwise we don't see it finalize in short tests
finalizedDistance: 8, finalizedDistance: 8,
safeDistance: 4, safeDistance: 4,
engineAPI: catalyst.NewConsensusAPI(l1Eth),
}) })
return l1Node, l1Eth, nil return l1Node, l1Eth, nil
} }
type fakeSafeFinalizedL1 struct { // fakePoS is a testing-only utility to attach to Geth,
// to build a fake proof-of-stake L1 chain with fixed block time and basic lagging safe/finalized blocks.
type fakePoS struct {
eth *eth.Ethereum eth *eth.Ethereum
log log.Logger
blockTime uint64
finalizedDistance uint64 finalizedDistance uint64
safeDistance uint64 safeDistance uint64
engineAPI *catalyst.ConsensusAPI
sub ethereum.Subscription sub ethereum.Subscription
} }
var _ node.Lifecycle = (*fakeSafeFinalizedL1)(nil) func (f *fakePoS) Start() error {
func (f *fakeSafeFinalizedL1) Start() error {
headChanges := make(chan core.ChainHeadEvent, 10)
headsSub := f.eth.BlockChain().SubscribeChainHeadEvent(headChanges)
f.sub = event.NewSubscription(func(quit <-chan struct{}) error { f.sub = event.NewSubscription(func(quit <-chan struct{}) error {
defer headsSub.Unsubscribe() // poll every half a second: enough to catch up with any block time when ticks are missed
t := time.NewTicker(time.Second / 2)
for { for {
select { select {
case head := <-headChanges: case now := <-t.C:
num := head.Block.NumberU64() chain := f.eth.BlockChain()
if num > f.finalizedDistance { head := chain.CurrentBlock()
toFinalize := f.eth.BlockChain().GetHeaderByNumber(num - f.finalizedDistance) finalized := chain.CurrentFinalBlock()
f.eth.BlockChain().SetFinalized(toFinalize) if finalized == nil { // fallback to genesis if nothing is finalized
finalized = chain.Genesis().Header()
}
safe := chain.CurrentSafeBlock()
if safe == nil { // fallback to finalized if nothing is safe
safe = finalized
}
if head.Number.Uint64() > f.finalizedDistance { // progress finalized block, if we can
finalized = f.eth.BlockChain().GetHeaderByNumber(head.Number.Uint64() - f.finalizedDistance)
}
if head.Number.Uint64() > f.safeDistance { // progress safe block, if we can
safe = f.eth.BlockChain().GetHeaderByNumber(head.Number.Uint64() - f.safeDistance)
}
// start building the block as soon as we are past the current head time
if head.Time >= uint64(now.Unix()) {
continue
}
res, err := f.engineAPI.ForkchoiceUpdatedV1(engine.ForkchoiceStateV1{
HeadBlockHash: head.Hash(),
SafeBlockHash: safe.Hash(),
FinalizedBlockHash: finalized.Hash(),
}, &engine.PayloadAttributes{
Timestamp: head.Time + f.blockTime,
Random: common.Hash{},
SuggestedFeeRecipient: head.Coinbase,
})
if err != nil {
f.log.Error("failed to start building L1 block", "err", err)
continue
}
if res.PayloadID == nil {
f.log.Error("failed to start block building", "res", res)
continue
}
// wait with sealing, if we are not behind already
delay := time.Until(time.Unix(int64(head.Time+f.blockTime), 0))
tim := time.NewTimer(delay)
select {
case <-tim.C:
// no-op
case <-quit:
tim.Stop()
return nil
}
payload, err := f.engineAPI.GetPayloadV1(*res.PayloadID)
if err != nil {
f.log.Error("failed to finish building L1 block", "err", err)
continue
}
if _, err := f.engineAPI.NewPayloadV1(*payload); err != nil {
f.log.Error("failed to insert built L1 block", "err", err)
continue
} }
if num > f.safeDistance { if _, err := f.engineAPI.ForkchoiceUpdatedV1(engine.ForkchoiceStateV1{
toSafe := f.eth.BlockChain().GetHeaderByNumber(num - f.safeDistance) HeadBlockHash: payload.BlockHash,
f.eth.BlockChain().SetSafe(toSafe) SafeBlockHash: safe.Hash(),
FinalizedBlockHash: finalized.Hash(),
}, nil); err != nil {
f.log.Error("failed to make built L1 block canonical", "err", err)
continue
} }
case <-quit: case <-quit:
return nil return nil
...@@ -175,7 +239,7 @@ func (f *fakeSafeFinalizedL1) Start() error { ...@@ -175,7 +239,7 @@ func (f *fakeSafeFinalizedL1) Start() error {
return nil return nil
} }
func (f *fakeSafeFinalizedL1) Stop() error { func (f *fakePoS) Stop() error {
f.sub.Unsubscribe() f.sub.Unsubscribe()
return nil return nil
} }
......
...@@ -15,8 +15,10 @@ import ( ...@@ -15,8 +15,10 @@ import (
batchermetrics "github.com/ethereum-optimism/optimism/op-batcher/metrics" batchermetrics "github.com/ethereum-optimism/optimism/op-batcher/metrics"
"github.com/ethereum-optimism/optimism/op-node/chaincfg" "github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-node/sources" "github.com/ethereum-optimism/optimism/op-node/sources"
proposermetrics "github.com/ethereum-optimism/optimism/op-proposer/metrics"
l2os "github.com/ethereum-optimism/optimism/op-proposer/proposer" l2os "github.com/ethereum-optimism/optimism/op-proposer/proposer"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
...@@ -272,11 +274,15 @@ func TestMigration(t *testing.T) { ...@@ -272,11 +274,15 @@ func TestMigration(t *testing.T) {
L1NodeAddr: forkedL1URL, L1NodeAddr: forkedL1URL,
L1TrustRPC: false, L1TrustRPC: false,
L1RPCKind: sources.RPCKindBasic, L1RPCKind: sources.RPCKindBasic,
RateLimit: 0,
BatchSize: 20,
HttpPollInterval: 12 * time.Second,
}, },
L2: &node.L2EndpointConfig{ L2: &node.L2EndpointConfig{
L2EngineAddr: gethNode.HTTPAuthEndpoint(), L2EngineAddr: gethNode.HTTPAuthEndpoint(),
L2EngineJWTSecret: testingJWTSecret, L2EngineJWTSecret: testingJWTSecret,
}, },
L2Sync: &node.PreparedL2SyncEndpoint{Client: nil, TrustRPC: false},
Driver: driver.Config{ Driver: driver.Config{
VerifierConfDepth: 0, VerifierConfDepth: 0,
SequencerConfDepth: 0, SequencerConfDepth: 0,
...@@ -334,18 +340,23 @@ func TestMigration(t *testing.T) { ...@@ -334,18 +340,23 @@ func TestMigration(t *testing.T) {
ApproxComprRatio: 0.4, ApproxComprRatio: 0.4,
SubSafetyMargin: 4, SubSafetyMargin: 4,
PollInterval: 50 * time.Millisecond, PollInterval: 50 * time.Millisecond,
TxMgrConfig: txmgr.CLIConfig{
L1RPCURL: forkedL1URL,
PrivateKey: hexPriv(secrets.Batcher),
NumConfirmations: 1, NumConfirmations: 1,
ResubmissionTimeout: 5 * time.Second, ResubmissionTimeout: 5 * time.Second,
SafeAbortNonceTooLowCount: 3, SafeAbortNonceTooLowCount: 3,
},
LogConfig: oplog.CLIConfig{ LogConfig: oplog.CLIConfig{
Level: "info", Level: "info",
Format: "text", Format: "text",
}, },
PrivateKey: hexPriv(secrets.Batcher),
}, lgr.New("module", "batcher"), batchermetrics.NoopMetrics) }, lgr.New("module", "batcher"), batchermetrics.NoopMetrics)
require.NoError(t, err) require.NoError(t, err)
t.Cleanup(func() { t.Cleanup(func() {
batcher.StopIfRunning() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
batcher.StopIfRunning(ctx)
}) })
proposer, err := l2os.NewL2OutputSubmitterFromCLIConfig(l2os.CLIConfig{ proposer, err := l2os.NewL2OutputSubmitterFromCLIConfig(l2os.CLIConfig{
...@@ -353,16 +364,19 @@ func TestMigration(t *testing.T) { ...@@ -353,16 +364,19 @@ func TestMigration(t *testing.T) {
RollupRpc: rollupNode.HTTPEndpoint(), RollupRpc: rollupNode.HTTPEndpoint(),
L2OOAddress: l2OS.Address.String(), L2OOAddress: l2OS.Address.String(),
PollInterval: 50 * time.Millisecond, PollInterval: 50 * time.Millisecond,
AllowNonFinalized: true,
TxMgrConfig: txmgr.CLIConfig{
L1RPCURL: forkedL1URL,
PrivateKey: hexPriv(secrets.Proposer),
NumConfirmations: 1, NumConfirmations: 1,
ResubmissionTimeout: 3 * time.Second, ResubmissionTimeout: 3 * time.Second,
SafeAbortNonceTooLowCount: 3, SafeAbortNonceTooLowCount: 3,
AllowNonFinalized: true, },
LogConfig: oplog.CLIConfig{ LogConfig: oplog.CLIConfig{
Level: "info", Level: "info",
Format: "text", Format: "text",
}, },
PrivateKey: hexPriv(secrets.Proposer), }, lgr.New("module", "proposer"), proposermetrics.NoopMetrics)
}, lgr.New("module", "proposer"))
require.NoError(t, err) require.NoError(t, err)
t.Cleanup(func() { t.Cleanup(func() {
proposer.Stop() proposer.Stop()
......
...@@ -75,7 +75,7 @@ func NewOpGeth(t *testing.T, ctx context.Context, cfg *SystemConfig) (*OpGeth, e ...@@ -75,7 +75,7 @@ func NewOpGeth(t *testing.T, ctx context.Context, cfg *SystemConfig) (*OpGeth, e
require.Nil(t, node.Start()) require.Nil(t, node.Start())
auth := rpc.WithHTTPAuth(gn.NewJWTAuth(cfg.JWTSecret)) auth := rpc.WithHTTPAuth(gn.NewJWTAuth(cfg.JWTSecret))
l2Node, err := client.NewRPC(ctx, logger, node.WSAuthEndpoint(), auth) l2Node, err := client.NewRPC(ctx, logger, node.WSAuthEndpoint(), client.WithGethRPCOptions(auth))
require.Nil(t, err) require.Nil(t, err)
// Finally create the engine client // Finally create the engine client
......
...@@ -37,8 +37,10 @@ import ( ...@@ -37,8 +37,10 @@ import (
"github.com/ethereum-optimism/optimism/op-node/rollup/driver" "github.com/ethereum-optimism/optimism/op-node/rollup/driver"
"github.com/ethereum-optimism/optimism/op-node/sources" "github.com/ethereum-optimism/optimism/op-node/sources"
"github.com/ethereum-optimism/optimism/op-node/testlog" "github.com/ethereum-optimism/optimism/op-node/testlog"
proposermetrics "github.com/ethereum-optimism/optimism/op-proposer/metrics"
l2os "github.com/ethereum-optimism/optimism/op-proposer/proposer" l2os "github.com/ethereum-optimism/optimism/op-proposer/proposer"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
) )
var ( var (
...@@ -72,9 +74,9 @@ func DefaultSystemConfig(t *testing.T) SystemConfig { ...@@ -72,9 +74,9 @@ func DefaultSystemConfig(t *testing.T) SystemConfig {
L1BlockTime: 2, L1BlockTime: 2,
L1GenesisBlockNonce: 4660, L1GenesisBlockNonce: 4660,
CliqueSignerAddress: addresses.CliqueSigner, CliqueSignerAddress: common.Address{}, // op-e2e used to run Clique, but now uses fake Proof of Stake.
L1GenesisBlockTimestamp: hexutil.Uint64(time.Now().Unix()), L1GenesisBlockTimestamp: hexutil.Uint64(time.Now().Unix()),
L1GenesisBlockGasLimit: 8_000_000, L1GenesisBlockGasLimit: 30_000_000,
L1GenesisBlockDifficulty: uint642big(1), L1GenesisBlockDifficulty: uint642big(1),
L1GenesisBlockMixHash: common.Hash{}, L1GenesisBlockMixHash: common.Hash{},
L1GenesisBlockCoinbase: common.Address{}, L1GenesisBlockCoinbase: common.Address{},
...@@ -84,7 +86,7 @@ func DefaultSystemConfig(t *testing.T) SystemConfig { ...@@ -84,7 +86,7 @@ func DefaultSystemConfig(t *testing.T) SystemConfig {
L1GenesisBlockBaseFeePerGas: uint642big(7), L1GenesisBlockBaseFeePerGas: uint642big(7),
L2GenesisBlockNonce: 0, L2GenesisBlockNonce: 0,
L2GenesisBlockGasLimit: 8_000_000, L2GenesisBlockGasLimit: 30_000_000,
L2GenesisBlockDifficulty: uint642big(1), L2GenesisBlockDifficulty: uint642big(1),
L2GenesisBlockMixHash: common.Hash{}, L2GenesisBlockMixHash: common.Hash{},
L2GenesisBlockNumber: 0, L2GenesisBlockNumber: 0,
...@@ -193,6 +195,9 @@ type SystemConfig struct { ...@@ -193,6 +195,9 @@ type SystemConfig struct {
// If the proposer can make proposals for L2 blocks derived from L1 blocks which are not finalized on L1 yet. // If the proposer can make proposals for L2 blocks derived from L1 blocks which are not finalized on L1 yet.
NonFinalizedProposals bool NonFinalizedProposals bool
// Explicitly disable batcher, for tests that rely on unsafe L2 payloads
DisableBatcher bool
} }
type System struct { type System struct {
...@@ -215,7 +220,9 @@ func (sys *System) Close() { ...@@ -215,7 +220,9 @@ func (sys *System) Close() {
sys.L2OutputSubmitter.Stop() sys.L2OutputSubmitter.Stop()
} }
if sys.BatchSubmitter != nil { if sys.BatchSubmitter != nil {
sys.BatchSubmitter.StopIfRunning() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
sys.BatchSubmitter.StopIfRunning(ctx)
} }
for _, node := range sys.RollupNodes { for _, node := range sys.RollupNodes {
...@@ -377,11 +384,6 @@ func (cfg SystemConfig) Start(_opts ...SystemConfigOption) (*System, error) { ...@@ -377,11 +384,6 @@ func (cfg SystemConfig) Start(_opts ...SystemConfigOption) (*System, error) {
didErrAfterStart = true didErrAfterStart = true
return nil, err return nil, err
} }
err = l1Backend.StartMining(1)
if err != nil {
didErrAfterStart = true
return nil, err
}
for name, node := range sys.Nodes { for name, node := range sys.Nodes {
if name == "l1" { if name == "l1" {
continue continue
...@@ -412,11 +414,18 @@ func (cfg SystemConfig) Start(_opts ...SystemConfigOption) (*System, error) { ...@@ -412,11 +414,18 @@ func (cfg SystemConfig) Start(_opts ...SystemConfigOption) (*System, error) {
L1NodeAddr: l1EndpointConfig, L1NodeAddr: l1EndpointConfig,
L1TrustRPC: false, L1TrustRPC: false,
L1RPCKind: sources.RPCKindBasic, L1RPCKind: sources.RPCKindBasic,
RateLimit: 0,
BatchSize: 20,
HttpPollInterval: time.Duration(cfg.DeployConfig.L1BlockTime) * time.Second / 10,
} }
rollupCfg.L2 = &rollupNode.L2EndpointConfig{ rollupCfg.L2 = &rollupNode.L2EndpointConfig{
L2EngineAddr: l2EndpointConfig, L2EngineAddr: l2EndpointConfig,
L2EngineJWTSecret: cfg.JWTSecret, L2EngineJWTSecret: cfg.JWTSecret,
} }
rollupCfg.L2Sync = &rollupNode.PreparedL2SyncEndpoint{
Client: nil,
TrustRPC: false,
}
} }
// Geth Clients // Geth Clients
...@@ -563,16 +572,20 @@ func (cfg SystemConfig) Start(_opts ...SystemConfigOption) (*System, error) { ...@@ -563,16 +572,20 @@ func (cfg SystemConfig) Start(_opts ...SystemConfigOption) (*System, error) {
RollupRpc: sys.RollupNodes["sequencer"].HTTPEndpoint(), RollupRpc: sys.RollupNodes["sequencer"].HTTPEndpoint(),
L2OOAddress: predeploys.DevL2OutputOracleAddr.String(), L2OOAddress: predeploys.DevL2OutputOracleAddr.String(),
PollInterval: 50 * time.Millisecond, PollInterval: 50 * time.Millisecond,
TxMgrConfig: txmgr.CLIConfig{
L1RPCURL: sys.Nodes["l1"].WSEndpoint(),
PrivateKey: hexPriv(cfg.Secrets.Proposer),
NumConfirmations: 1, NumConfirmations: 1,
ResubmissionTimeout: 3 * time.Second,
SafeAbortNonceTooLowCount: 3, SafeAbortNonceTooLowCount: 3,
ResubmissionTimeout: 3 * time.Second,
ReceiptQueryInterval: 50 * time.Millisecond,
},
AllowNonFinalized: cfg.NonFinalizedProposals, AllowNonFinalized: cfg.NonFinalizedProposals,
LogConfig: oplog.CLIConfig{ LogConfig: oplog.CLIConfig{
Level: "info", Level: "info",
Format: "text", Format: "text",
}, },
PrivateKey: hexPriv(cfg.Secrets.Proposer), }, sys.cfg.Loggers["proposer"], proposermetrics.NoopMetrics)
}, sys.cfg.Loggers["proposer"])
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to setup l2 output submitter: %w", err) return nil, fmt.Errorf("unable to setup l2 output submitter: %w", err)
} }
...@@ -593,22 +606,29 @@ func (cfg SystemConfig) Start(_opts ...SystemConfigOption) (*System, error) { ...@@ -593,22 +606,29 @@ func (cfg SystemConfig) Start(_opts ...SystemConfigOption) (*System, error) {
ApproxComprRatio: 0.4, ApproxComprRatio: 0.4,
SubSafetyMargin: 4, SubSafetyMargin: 4,
PollInterval: 50 * time.Millisecond, PollInterval: 50 * time.Millisecond,
TxMgrConfig: txmgr.CLIConfig{
L1RPCURL: sys.Nodes["l1"].WSEndpoint(),
PrivateKey: hexPriv(cfg.Secrets.Batcher),
NumConfirmations: 1, NumConfirmations: 1,
ResubmissionTimeout: 5 * time.Second,
SafeAbortNonceTooLowCount: 3, SafeAbortNonceTooLowCount: 3,
ResubmissionTimeout: 3 * time.Second,
ReceiptQueryInterval: 50 * time.Millisecond,
},
LogConfig: oplog.CLIConfig{ LogConfig: oplog.CLIConfig{
Level: "info", Level: "info",
Format: "text", Format: "text",
}, },
PrivateKey: hexPriv(cfg.Secrets.Batcher),
}, sys.cfg.Loggers["batcher"], batchermetrics.NoopMetrics) }, sys.cfg.Loggers["batcher"], batchermetrics.NoopMetrics)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to setup batch submitter: %w", err) return nil, fmt.Errorf("failed to setup batch submitter: %w", err)
} }
// Batcher may be enabled later
if !sys.cfg.DisableBatcher {
if err := sys.BatchSubmitter.Start(); err != nil { if err := sys.BatchSubmitter.Start(); err != nil {
return nil, fmt.Errorf("unable to start batch submitter: %w", err) return nil, fmt.Errorf("unable to start batch submitter: %w", err)
} }
}
return sys, nil return sys, nil
} }
......
...@@ -304,7 +304,7 @@ func TestPendingGasLimit(t *testing.T) { ...@@ -304,7 +304,7 @@ func TestPendingGasLimit(t *testing.T) {
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
// configure the L2 gas limit to be high, and the pending gas limits to be lower for resource saving. // configure the L2 gas limit to be high, and the pending gas limits to be lower for resource saving.
cfg.DeployConfig.L2GenesisBlockGasLimit = 20_000_000 cfg.DeployConfig.L2GenesisBlockGasLimit = 30_000_000
cfg.GethOptions["sequencer"] = []GethOption{ cfg.GethOptions["sequencer"] = []GethOption{
func(ethCfg *ethconfig.Config, nodeCfg *node.Config) error { func(ethCfg *ethconfig.Config, nodeCfg *node.Config) error {
ethCfg.Miner.GasCeil = 10_000_000 ethCfg.Miner.GasCeil = 10_000_000
...@@ -342,8 +342,8 @@ func TestPendingGasLimit(t *testing.T) { ...@@ -342,8 +342,8 @@ func TestPendingGasLimit(t *testing.T) {
for { for {
checkGasLimit(l2Seq, big.NewInt(-1), 10_000_000) checkGasLimit(l2Seq, big.NewInt(-1), 10_000_000)
checkGasLimit(l2Verif, big.NewInt(-1), 9_000_000) checkGasLimit(l2Verif, big.NewInt(-1), 9_000_000)
checkGasLimit(l2Seq, nil, 20_000_000) checkGasLimit(l2Seq, nil, 30_000_000)
latestVerifHeader := checkGasLimit(l2Verif, nil, 20_000_000) latestVerifHeader := checkGasLimit(l2Verif, nil, 30_000_000)
// Stop once the verifier passes genesis: // Stop once the verifier passes genesis:
// this implies we checked a new block from the sequencer, on both sequencer and verifier nodes. // this implies we checked a new block from the sequencer, on both sequencer and verifier nodes.
...@@ -649,7 +649,7 @@ func TestSystemMockP2P(t *testing.T) { ...@@ -649,7 +649,7 @@ func TestSystemMockP2P(t *testing.T) {
require.Contains(t, received, receiptVerif.BlockHash) require.Contains(t, received, receiptVerif.BlockHash)
} }
// TestSystemMockP2P sets up a L1 Geth node, a rollup node, and a L2 geth node and then confirms that // TestSystemRPCAltSync sets up a L1 Geth node, a rollup node, and a L2 geth node and then confirms that
// the nodes can sync L2 blocks before they are confirmed on L1. // the nodes can sync L2 blocks before they are confirmed on L1.
// //
// Test steps: // Test steps:
...@@ -660,24 +660,28 @@ func TestSystemMockP2P(t *testing.T) { ...@@ -660,24 +660,28 @@ func TestSystemMockP2P(t *testing.T) {
// 6. Wait for the RPC sync method to grab the block from the sequencer over RPC and insert it into the verifier's unsafe chain. // 6. Wait for the RPC sync method to grab the block from the sequencer over RPC and insert it into the verifier's unsafe chain.
// 7. Wait for the verifier to sync the unsafe chain into the safe chain. // 7. Wait for the verifier to sync the unsafe chain into the safe chain.
// 8. Verify that the TX is included in the verifier's safe chain. // 8. Verify that the TX is included in the verifier's safe chain.
func TestSystemMockAltSync(t *testing.T) { func TestSystemRPCAltSync(t *testing.T) {
parallel(t) parallel(t)
if !verboseGethNodes { if !verboseGethNodes {
log.Root().SetHandler(log.DiscardHandler()) log.Root().SetHandler(log.DiscardHandler())
} }
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
// slow down L1 blocks so we can see the L2 blocks arrive well before the L1 blocks do. // the default is nil, but this may change in the future.
// Keep the seq window small so the L2 chain is started quick // This test must ensure the blocks are not synced via Gossip, but instead via the alt RPC based sync.
cfg.DeployConfig.L1BlockTime = 10 cfg.P2PTopology = nil
// Disable batcher, so there will not be any L1 data to sync from
cfg.DisableBatcher = true
var published, received []common.Hash var published, received []string
seqTracer, verifTracer := new(FnTracer), new(FnTracer) seqTracer, verifTracer := new(FnTracer), new(FnTracer)
// The sequencer still publishes the blocks to the tracer, even if they do not reach the network due to disabled P2P
seqTracer.OnPublishL2PayloadFn = func(ctx context.Context, payload *eth.ExecutionPayload) { seqTracer.OnPublishL2PayloadFn = func(ctx context.Context, payload *eth.ExecutionPayload) {
published = append(published, payload.BlockHash) published = append(published, payload.ID().String())
} }
// Blocks are now received via the RPC based alt-sync method
verifTracer.OnUnsafeL2PayloadFn = func(ctx context.Context, from peer.ID, payload *eth.ExecutionPayload) { verifTracer.OnUnsafeL2PayloadFn = func(ctx context.Context, from peer.ID, payload *eth.ExecutionPayload) {
received = append(received, payload.BlockHash) received = append(received, payload.ID().String())
} }
cfg.Nodes["sequencer"].Tracer = seqTracer cfg.Nodes["sequencer"].Tracer = seqTracer
cfg.Nodes["verifier"].Tracer = verifTracer cfg.Nodes["verifier"].Tracer = verifTracer
...@@ -687,8 +691,8 @@ func TestSystemMockAltSync(t *testing.T) { ...@@ -687,8 +691,8 @@ func TestSystemMockAltSync(t *testing.T) {
role: "sequencer", role: "sequencer",
action: func(sCfg *SystemConfig, system *System) { action: func(sCfg *SystemConfig, system *System) {
rpc, _ := system.Nodes["sequencer"].Attach() // never errors rpc, _ := system.Nodes["sequencer"].Attach() // never errors
cfg.Nodes["verifier"].L2Sync = &rollupNode.L2SyncRPCConfig{ cfg.Nodes["verifier"].L2Sync = &rollupNode.PreparedL2SyncEndpoint{
Rpc: client.NewBaseRPCClient(rpc), Client: client.NewBaseRPCClient(rpc),
} }
}, },
}) })
...@@ -726,7 +730,7 @@ func TestSystemMockAltSync(t *testing.T) { ...@@ -726,7 +730,7 @@ func TestSystemMockAltSync(t *testing.T) {
require.Equal(t, receiptSeq, receiptVerif) require.Equal(t, receiptSeq, receiptVerif)
// Verify that the tx was received via RPC sync (P2P is disabled) // Verify that the tx was received via RPC sync (P2P is disabled)
require.Contains(t, received, receiptVerif.BlockHash) require.Contains(t, received, eth.BlockID{Hash: receiptVerif.BlockHash, Number: receiptVerif.BlockNumber.Uint64()}.String())
// Verify that everything that was received was published // Verify that everything that was received was published
require.GreaterOrEqual(t, len(published), len(received)) require.GreaterOrEqual(t, len(published), len(received))
...@@ -1445,7 +1449,7 @@ func TestStopStartBatcher(t *testing.T) { ...@@ -1445,7 +1449,7 @@ func TestStopStartBatcher(t *testing.T) {
require.Greater(t, newSeqStatus.SafeL2.Number, seqStatus.SafeL2.Number, "Safe chain did not advance") require.Greater(t, newSeqStatus.SafeL2.Number, seqStatus.SafeL2.Number, "Safe chain did not advance")
// stop the batch submission // stop the batch submission
err = sys.BatchSubmitter.Stop() err = sys.BatchSubmitter.Stop(context.Background())
require.Nil(t, err) require.Nil(t, err)
// wait for any old safe blocks being submitted / derived // wait for any old safe blocks being submitted / derived
......
...@@ -57,7 +57,7 @@ func NewPollingClient(ctx context.Context, lgr log.Logger, c RPC, opts ...Wrappe ...@@ -57,7 +57,7 @@ func NewPollingClient(ctx context.Context, lgr log.Logger, c RPC, opts ...Wrappe
res := &PollingClient{ res := &PollingClient{
c: c, c: c,
lgr: lgr, lgr: lgr,
pollRate: 250 * time.Millisecond, pollRate: 12 * time.Second,
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
pollReqCh: make(chan struct{}, 1), pollReqCh: make(chan struct{}, 1),
......
package client
import (
"context"
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/rpc"
"golang.org/x/time/rate"
)
// RateLimitingClient is a wrapper around a pure RPC that implements a global rate-limit on requests.
type RateLimitingClient struct {
c RPC
rl *rate.Limiter
}
// NewRateLimitingClient implements a global rate-limit for all RPC requests.
// A limit of N will ensure that over a long enough time-frame the given number of tokens per second is targeted.
// Burst limits how far off we can be from the target, by specifying how many requests are allowed at once.
func NewRateLimitingClient(c RPC, limit rate.Limit, burst int) *RateLimitingClient {
return &RateLimitingClient{c: c, rl: rate.NewLimiter(limit, burst)}
}
func (b *RateLimitingClient) Close() {
b.c.Close()
}
func (b *RateLimitingClient) CallContext(ctx context.Context, result any, method string, args ...any) error {
if err := b.rl.Wait(ctx); err != nil {
return err
}
cCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
return b.c.CallContext(cCtx, result, method, args...)
}
func (b *RateLimitingClient) BatchCallContext(ctx context.Context, batch []rpc.BatchElem) error {
if err := b.rl.WaitN(ctx, len(batch)); err != nil {
return err
}
cCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
defer cancel()
return b.c.BatchCallContext(cCtx, batch)
}
func (b *RateLimitingClient) EthSubscribe(ctx context.Context, channel any, args ...any) (ethereum.Subscription, error) {
if err := b.rl.Wait(ctx); err != nil {
return nil, err
}
return b.c.EthSubscribe(ctx, channel, args...)
}
...@@ -10,6 +10,7 @@ import ( ...@@ -10,6 +10,7 @@ import (
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"golang.org/x/time/rate"
"github.com/ethereum-optimism/optimism/op-node/metrics" "github.com/ethereum-optimism/optimism/op-node/metrics"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
...@@ -24,27 +25,85 @@ type RPC interface { ...@@ -24,27 +25,85 @@ type RPC interface {
EthSubscribe(ctx context.Context, channel any, args ...any) (ethereum.Subscription, error) EthSubscribe(ctx context.Context, channel any, args ...any) (ethereum.Subscription, error)
} }
type rpcConfig struct {
gethRPCOptions []rpc.ClientOption
httpPollInterval time.Duration
backoffAttempts int
limit float64
burst int
}
type RPCOption func(cfg *rpcConfig) error
// WithDialBackoff configures the number of attempts for the initial dial to the RPC,
// attempts are executed with an exponential backoff strategy.
func WithDialBackoff(attempts int) RPCOption {
return func(cfg *rpcConfig) error {
cfg.backoffAttempts = attempts
return nil
}
}
// WithHttpPollInterval configures the RPC to poll at the given rate, in case RPC subscriptions are not available.
func WithHttpPollInterval(duration time.Duration) RPCOption {
return func(cfg *rpcConfig) error {
cfg.httpPollInterval = duration
return nil
}
}
// WithGethRPCOptions passes the list of go-ethereum RPC options to the internal RPC instance.
func WithGethRPCOptions(gethRPCOptions ...rpc.ClientOption) RPCOption {
return func(cfg *rpcConfig) error {
cfg.gethRPCOptions = append(cfg.gethRPCOptions, gethRPCOptions...)
return nil
}
}
// WithRateLimit configures the RPC to target the given rate limit (in requests / second).
// See NewRateLimitingClient for more details.
func WithRateLimit(rateLimit float64, burst int) RPCOption {
return func(cfg *rpcConfig) error {
cfg.limit = rateLimit
cfg.burst = burst
return nil
}
}
// NewRPC returns the correct client.RPC instance for a given RPC url. // NewRPC returns the correct client.RPC instance for a given RPC url.
func NewRPC(ctx context.Context, lgr log.Logger, addr string, opts ...rpc.ClientOption) (RPC, error) { func NewRPC(ctx context.Context, lgr log.Logger, addr string, opts ...RPCOption) (RPC, error) {
underlying, err := DialRPCClientWithBackoff(ctx, lgr, addr, opts...) var cfg rpcConfig
for i, opt := range opts {
if err := opt(&cfg); err != nil {
return nil, fmt.Errorf("rpc option %d failed to apply to RPC config: %w", i, err)
}
}
if cfg.backoffAttempts < 1 { // default to at least 1 attempt, or it always fails to dial.
cfg.backoffAttempts = 1
}
underlying, err := dialRPCClientWithBackoff(ctx, lgr, addr, cfg.backoffAttempts, cfg.gethRPCOptions...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
wrapped := &BaseRPCClient{ var wrapped RPC = &BaseRPCClient{c: underlying}
c: underlying,
if cfg.limit != 0 {
wrapped = NewRateLimitingClient(wrapped, rate.Limit(cfg.limit), cfg.burst)
} }
if httpRegex.MatchString(addr) { if httpRegex.MatchString(addr) {
return NewPollingClient(ctx, lgr, wrapped), nil wrapped = NewPollingClient(ctx, lgr, wrapped, WithPollRate(cfg.httpPollInterval))
} }
return wrapped, nil return wrapped, nil
} }
// Dials a JSON-RPC endpoint repeatedly, with a backoff, until a client connection is established. Auth is optional. // Dials a JSON-RPC endpoint repeatedly, with a backoff, until a client connection is established. Auth is optional.
func DialRPCClientWithBackoff(ctx context.Context, log log.Logger, addr string, opts ...rpc.ClientOption) (*rpc.Client, error) { func dialRPCClientWithBackoff(ctx context.Context, log log.Logger, addr string, attempts int, opts ...rpc.ClientOption) (*rpc.Client, error) {
bOff := backoff.Exponential() bOff := backoff.Exponential()
var ret *rpc.Client var ret *rpc.Client
err := backoff.DoCtx(ctx, 10, bOff, func() error { err := backoff.DoCtx(ctx, attempts, bOff, func() error {
client, err := rpc.DialOptions(ctx, addr, opts...) client, err := rpc.DialOptions(ctx, addr, opts...)
if err != nil { if err != nil {
if client == nil { if client == nil {
......
...@@ -52,6 +52,12 @@ jq "select(.valid_data == false)|.tx.hash" $TX_DIR ...@@ -52,6 +52,12 @@ jq "select(.valid_data == false)|.tx.hash" $TX_DIR
# Select all channels that are not ready and then get the id and inclusion block & tx hash of the first frame. # Select all channels that are not ready and then get the id and inclusion block & tx hash of the first frame.
jq "select(.is_ready == false)|[.id, .frames[0].inclusion_block, .frames[0].transaction_hash]" $CHANNEL_DIR jq "select(.is_ready == false)|[.id, .frames[0].inclusion_block, .frames[0].transaction_hash]" $CHANNEL_DIR
# Show all of the frames in a channel without seeing the batches or frame data
jq 'del(.batches)|del(.frames[]|.frame.data)' $CHANNEL_FILE
# Show all batches (without timestamps) in a channel
jq '.batches|del(.[]|.Transactions)' $CHANNEL_FILE
``` ```
......
...@@ -17,3 +17,9 @@ const ( ...@@ -17,3 +17,9 @@ const (
// - L2: Derived chain tip from finalized L1 data // - L2: Derived chain tip from finalized L1 data
Finalized = "finalized" Finalized = "finalized"
) )
func (label BlockLabel) Arg() any { return string(label) }
func (BlockLabel) CheckID(id BlockID) error {
return nil
}
...@@ -14,10 +14,10 @@ import ( ...@@ -14,10 +14,10 @@ import (
// Flags // Flags
const envVarPrefix = "OP_NODE_" const envVarPrefix = "OP_NODE"
func prefixEnvVar(name string) string { func prefixEnvVar(name string) string {
return envVarPrefix + name return envVarPrefix + "_" + name
} }
var ( var (
...@@ -75,6 +75,24 @@ var ( ...@@ -75,6 +75,24 @@ var (
return &out return &out
}(), }(),
} }
L1RPCRateLimit = cli.Float64Flag{
Name: "l1.rpc-rate-limit",
Usage: "Optional self-imposed global rate-limit on L1 RPC requests, specified in requests / second. Disabled if set to 0.",
EnvVar: prefixEnvVar("L1_RPC_RATE_LIMIT"),
Value: 0,
}
L1RPCMaxBatchSize = cli.IntFlag{
Name: "l1.rpc-max-batch-size",
Usage: "Maximum number of RPC requests to bundle, e.g. during L1 blocks receipt fetching. The L1 RPC rate limit counts this as N items, but allows it to burst at once.",
EnvVar: prefixEnvVar("L1_RPC_MAX_BATCH_SIZE"),
Value: 20,
}
L1HTTPPollInterval = cli.DurationFlag{
Name: "l1.http-poll-interval",
Usage: "Polling interval for latest-block subscription when using an HTTP RPC provider. Ignored for other types of RPC endpoints.",
EnvVar: prefixEnvVar("L1_HTTP_POLL_INTERVAL"),
Value: time.Second * 12,
}
L2EngineJWTSecret = cli.StringFlag{ L2EngineJWTSecret = cli.StringFlag{
Name: "l2.jwt-secret", Name: "l2.jwt-secret",
Usage: "Path to JWT secret key. Keys are 32 bytes, hex encoded in a file. A new key will be generated if left empty.", Usage: "Path to JWT secret key. Keys are 32 bytes, hex encoded in a file. A new key will be generated if left empty.",
...@@ -100,6 +118,13 @@ var ( ...@@ -100,6 +118,13 @@ var (
Usage: "Initialize the sequencer in a stopped state. The sequencer can be started using the admin_startSequencer RPC", Usage: "Initialize the sequencer in a stopped state. The sequencer can be started using the admin_startSequencer RPC",
EnvVar: prefixEnvVar("SEQUENCER_STOPPED"), EnvVar: prefixEnvVar("SEQUENCER_STOPPED"),
} }
SequencerMaxSafeLagFlag = cli.Uint64Flag{
Name: "sequencer.max-safe-lag",
Usage: "Maximum number of L2 blocks for restricting the distance between L2 safe and unsafe. Disabled if 0.",
EnvVar: prefixEnvVar("SEQUENCER_MAX_SAFE_LAG"),
Required: false,
Value: 0,
}
SequencerL1Confs = cli.Uint64Flag{ SequencerL1Confs = cli.Uint64Flag{
Name: "sequencer.l1-confs", Name: "sequencer.l1-confs",
Usage: "Number of L1 blocks to keep distance from the L1 head as a sequencer for picking an L1 origin.", Usage: "Number of L1 blocks to keep distance from the L1 head as a sequencer for picking an L1 origin.",
...@@ -175,6 +200,13 @@ var ( ...@@ -175,6 +200,13 @@ var (
EnvVar: prefixEnvVar("L2_BACKUP_UNSAFE_SYNC_RPC"), EnvVar: prefixEnvVar("L2_BACKUP_UNSAFE_SYNC_RPC"),
Required: false, Required: false,
} }
BackupL2UnsafeSyncRPCTrustRPC = cli.StringFlag{
Name: "l2.backup-unsafe-sync-rpc.trustrpc",
Usage: "Like l1.trustrpc, configure if response data from the RPC needs to be verified, e.g. blockhash computation." +
"This does not include checks if the blockhash is part of the canonical chain.",
EnvVar: prefixEnvVar("L2_BACKUP_UNSAFE_SYNC_RPC_TRUST_RPC"),
Required: false,
}
) )
var requiredFlags = []cli.Flag{ var requiredFlags = []cli.Flag{
...@@ -189,10 +221,14 @@ var optionalFlags = []cli.Flag{ ...@@ -189,10 +221,14 @@ var optionalFlags = []cli.Flag{
Network, Network,
L1TrustRPC, L1TrustRPC,
L1RPCProviderKind, L1RPCProviderKind,
L1RPCRateLimit,
L1RPCMaxBatchSize,
L1HTTPPollInterval,
L2EngineJWTSecret, L2EngineJWTSecret,
VerifierL1Confs, VerifierL1Confs,
SequencerEnabledFlag, SequencerEnabledFlag,
SequencerStoppedFlag, SequencerStoppedFlag,
SequencerMaxSafeLagFlag,
SequencerL1Confs, SequencerL1Confs,
L1EpochPollIntervalFlag, L1EpochPollIntervalFlag,
RPCEnableAdmin, RPCEnableAdmin,
...@@ -207,6 +243,7 @@ var optionalFlags = []cli.Flag{ ...@@ -207,6 +243,7 @@ var optionalFlags = []cli.Flag{
HeartbeatMonikerFlag, HeartbeatMonikerFlag,
HeartbeatURLFlag, HeartbeatURLFlag,
BackupL2UnsafeSyncRPC, BackupL2UnsafeSyncRPC,
BackupL2UnsafeSyncRPCTrustRPC,
} }
// Flags contains the list of configuration options available to the binary. // Flags contains the list of configuration options available to the binary.
......
...@@ -34,6 +34,15 @@ var ( ...@@ -34,6 +34,15 @@ var (
Value: "none", Value: "none",
EnvVar: p2pEnv("PEER_SCORING"), EnvVar: p2pEnv("PEER_SCORING"),
} }
PeerScoreBands = cli.StringFlag{
Name: "p2p.score.bands",
Usage: "Sets the peer score bands used primarily for peer score metrics. " +
"Should be provided in following format: <threshold>:<label>;<threshold>:<label>;..." +
"For example: -40:graylist;-20:restricted;0:nopx;20:friend;",
Required: false,
Value: "-40:graylist;-20:restricted;0:nopx;20:friend;",
EnvVar: p2pEnv("SCORE_BANDS"),
}
// Banning Flag - whether or not we want to act on the scoring // Banning Flag - whether or not we want to act on the scoring
Banning = cli.BoolFlag{ Banning = cli.BoolFlag{
...@@ -276,6 +285,10 @@ var p2pFlags = []cli.Flag{ ...@@ -276,6 +285,10 @@ var p2pFlags = []cli.Flag{
NoDiscovery, NoDiscovery,
P2PPrivPath, P2PPrivPath,
P2PPrivRaw, P2PPrivRaw,
PeerScoring,
PeerScoreBands,
Banning,
TopicScoring,
ListenIP, ListenIP,
ListenTCPPort, ListenTCPPort,
ListenUDPPort, ListenUDPPort,
......
...@@ -15,7 +15,6 @@ import ( ...@@ -15,7 +15,6 @@ import (
pb "github.com/libp2p/go-libp2p-pubsub/pb" pb "github.com/libp2p/go-libp2p-pubsub/pb"
libp2pmetrics "github.com/libp2p/go-libp2p/core/metrics" libp2pmetrics "github.com/libp2p/go-libp2p/core/metrics"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/collectors" "github.com/prometheus/client_golang/prometheus/collectors"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
...@@ -66,7 +65,7 @@ type Metricer interface { ...@@ -66,7 +65,7 @@ type Metricer interface {
RecordSequencerSealingTime(duration time.Duration) RecordSequencerSealingTime(duration time.Duration)
Document() []metrics.DocumentedMetric Document() []metrics.DocumentedMetric
// P2P Metrics // P2P Metrics
RecordPeerScoring(peerID peer.ID, score float64) SetPeerScores(scores map[string]float64)
} }
// Metrics tracks all the metrics for the op-node. // Metrics tracks all the metrics for the op-node.
...@@ -287,21 +286,24 @@ func NewMetrics(procName string) *Metrics { ...@@ -287,21 +286,24 @@ func NewMetrics(procName string) *Metrics {
Name: "peer_count", Name: "peer_count",
Help: "Count of currently connected p2p peers", Help: "Count of currently connected p2p peers",
}), }),
StreamCount: factory.NewGauge(prometheus.GaugeOpts{ // Notice: We cannot use peer ids as [Labels] in the GaugeVec
Namespace: ns, // since peer ids would open a service attack vector.
Subsystem: "p2p", // Each peer id would be a separate metric, flooding prometheus.
Name: "stream_count", //
Help: "Count of currently connected p2p streams", // [Labels]: https://prometheus.io/docs/practices/naming/#labels
}),
PeerScores: factory.NewGaugeVec(prometheus.GaugeOpts{ PeerScores: factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Namespace: ns,
Subsystem: "p2p", Subsystem: "p2p",
Name: "peer_scores", Name: "peer_scores",
Help: "Peer scoring", Help: "Count of peer scores grouped by score",
}, []string{ }, []string{
// No label names here since peer ids would open a service attack vector. "band",
// Each peer id would be a separate metric, flooding prometheus. }),
// See: https://prometheus.io/docs/practices/naming/#labels StreamCount: factory.NewGauge(prometheus.GaugeOpts{
Namespace: ns,
Subsystem: "p2p",
Name: "stream_count",
Help: "Count of currently connected p2p streams",
}), }),
GossipEventsTotal: factory.NewCounterVec(prometheus.CounterOpts{ GossipEventsTotal: factory.NewCounterVec(prometheus.CounterOpts{
Namespace: ns, Namespace: ns,
...@@ -350,6 +352,14 @@ func NewMetrics(procName string) *Metrics { ...@@ -350,6 +352,14 @@ func NewMetrics(procName string) *Metrics {
} }
} }
// SetPeerScores updates the peer score [prometheus.GaugeVec].
// This takes a map of labels to scores.
func (m *Metrics) SetPeerScores(scores map[string]float64) {
for label, score := range scores {
m.PeerScores.WithLabelValues(label).Set(score)
}
}
// RecordInfo sets a pseudo-metric that contains versioning and // RecordInfo sets a pseudo-metric that contains versioning and
// config info for the opnode. // config info for the opnode.
func (m *Metrics) RecordInfo(version string) { func (m *Metrics) RecordInfo(version string) {
...@@ -491,10 +501,6 @@ func (m *Metrics) RecordGossipEvent(evType int32) { ...@@ -491,10 +501,6 @@ func (m *Metrics) RecordGossipEvent(evType int32) {
m.GossipEventsTotal.WithLabelValues(pb.TraceEvent_Type_name[evType]).Inc() m.GossipEventsTotal.WithLabelValues(pb.TraceEvent_Type_name[evType]).Inc()
} }
func (m *Metrics) RecordPeerScoring(peerID peer.ID, score float64) {
m.PeerScores.WithLabelValues(peerID.String()).Set(score)
}
func (m *Metrics) IncPeerCount() { func (m *Metrics) IncPeerCount() {
m.PeerCount.Inc() m.PeerCount.Inc()
} }
...@@ -627,7 +633,7 @@ func (n *noopMetricer) RecordSequencerReset() { ...@@ -627,7 +633,7 @@ func (n *noopMetricer) RecordSequencerReset() {
func (n *noopMetricer) RecordGossipEvent(evType int32) { func (n *noopMetricer) RecordGossipEvent(evType int32) {
} }
func (n *noopMetricer) RecordPeerScoring(peerID peer.ID, score float64) { func (n *noopMetricer) SetPeerScores(scores map[string]float64) {
} }
func (n *noopMetricer) IncPeerCount() { func (n *noopMetricer) IncPeerCount() {
......
...@@ -4,8 +4,10 @@ import ( ...@@ -4,8 +4,10 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"time"
"github.com/ethereum-optimism/optimism/op-node/client" "github.com/ethereum-optimism/optimism/op-node/client"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/sources" "github.com/ethereum-optimism/optimism/op-node/sources"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -15,12 +17,14 @@ import ( ...@@ -15,12 +17,14 @@ import (
type L2EndpointSetup interface { type L2EndpointSetup interface {
// Setup a RPC client to a L2 execution engine to process rollup blocks with. // Setup a RPC client to a L2 execution engine to process rollup blocks with.
Setup(ctx context.Context, log log.Logger) (cl client.RPC, err error) Setup(ctx context.Context, log log.Logger, rollupCfg *rollup.Config) (cl client.RPC, rpcCfg *sources.EngineClientConfig, err error)
Check() error Check() error
} }
type L2SyncEndpointSetup interface { type L2SyncEndpointSetup interface {
Setup(ctx context.Context, log log.Logger) (cl client.RPC, err error) // Setup a RPC client to another L2 node to sync L2 blocks from.
// It may return a nil client with nil error if RPC based sync is not enabled.
Setup(ctx context.Context, log log.Logger, rollupCfg *rollup.Config) (cl client.RPC, rpcCfg *sources.SyncClientConfig, err error)
Check() error Check() error
} }
...@@ -28,7 +32,8 @@ type L1EndpointSetup interface { ...@@ -28,7 +32,8 @@ type L1EndpointSetup interface {
// Setup a RPC client to a L1 node to pull rollup input-data from. // Setup a RPC client to a L1 node to pull rollup input-data from.
// The results of the RPC client may be trusted for faster processing, or strictly validated. // The results of the RPC client may be trusted for faster processing, or strictly validated.
// The kind of the RPC may be non-basic, to optimize RPC usage. // The kind of the RPC may be non-basic, to optimize RPC usage.
Setup(ctx context.Context, log log.Logger) (cl client.RPC, trust bool, kind sources.RPCProviderKind, err error) Setup(ctx context.Context, log log.Logger, rollupCfg *rollup.Config) (cl client.RPC, rpcCfg *sources.L1ClientConfig, err error)
Check() error
} }
type L2EndpointConfig struct { type L2EndpointConfig struct {
...@@ -49,17 +54,17 @@ func (cfg *L2EndpointConfig) Check() error { ...@@ -49,17 +54,17 @@ func (cfg *L2EndpointConfig) Check() error {
return nil return nil
} }
func (cfg *L2EndpointConfig) Setup(ctx context.Context, log log.Logger) (client.RPC, error) { func (cfg *L2EndpointConfig) Setup(ctx context.Context, log log.Logger, rollupCfg *rollup.Config) (client.RPC, *sources.EngineClientConfig, error) {
if err := cfg.Check(); err != nil { if err := cfg.Check(); err != nil {
return nil, err return nil, nil, err
} }
auth := rpc.WithHTTPAuth(gn.NewJWTAuth(cfg.L2EngineJWTSecret)) auth := rpc.WithHTTPAuth(gn.NewJWTAuth(cfg.L2EngineJWTSecret))
l2Node, err := client.NewRPC(ctx, log, cfg.L2EngineAddr, auth) l2Node, err := client.NewRPC(ctx, log, cfg.L2EngineAddr, client.WithGethRPCOptions(auth))
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
return l2Node, nil return l2Node, sources.EngineClientDefaultConfig(rollupCfg), nil
} }
// PreparedL2Endpoints enables testing with in-process pre-setup RPC connections to L2 engines // PreparedL2Endpoints enables testing with in-process pre-setup RPC connections to L2 engines
...@@ -76,51 +81,51 @@ func (p *PreparedL2Endpoints) Check() error { ...@@ -76,51 +81,51 @@ func (p *PreparedL2Endpoints) Check() error {
var _ L2EndpointSetup = (*PreparedL2Endpoints)(nil) var _ L2EndpointSetup = (*PreparedL2Endpoints)(nil)
func (p *PreparedL2Endpoints) Setup(ctx context.Context, log log.Logger) (client.RPC, error) { func (p *PreparedL2Endpoints) Setup(ctx context.Context, log log.Logger, rollupCfg *rollup.Config) (client.RPC, *sources.EngineClientConfig, error) {
return p.Client, nil return p.Client, sources.EngineClientDefaultConfig(rollupCfg), nil
} }
// L2SyncEndpointConfig contains configuration for the fallback sync endpoint // L2SyncEndpointConfig contains configuration for the fallback sync endpoint
type L2SyncEndpointConfig struct { type L2SyncEndpointConfig struct {
// Address of the L2 RPC to use for backup sync // Address of the L2 RPC to use for backup sync, may be empty if RPC alt-sync is disabled.
L2NodeAddr string L2NodeAddr string
TrustRPC bool
} }
var _ L2SyncEndpointSetup = (*L2SyncEndpointConfig)(nil) var _ L2SyncEndpointSetup = (*L2SyncEndpointConfig)(nil)
func (cfg *L2SyncEndpointConfig) Setup(ctx context.Context, log log.Logger) (client.RPC, error) { // Setup creates an RPC client to sync from.
// It will return nil without error if no sync method is configured.
func (cfg *L2SyncEndpointConfig) Setup(ctx context.Context, log log.Logger, rollupCfg *rollup.Config) (client.RPC, *sources.SyncClientConfig, error) {
if cfg.L2NodeAddr == "" {
return nil, nil, nil
}
l2Node, err := client.NewRPC(ctx, log, cfg.L2NodeAddr) l2Node, err := client.NewRPC(ctx, log, cfg.L2NodeAddr)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
return l2Node, nil return l2Node, sources.SyncClientDefaultConfig(rollupCfg, cfg.TrustRPC), nil
} }
func (cfg *L2SyncEndpointConfig) Check() error { func (cfg *L2SyncEndpointConfig) Check() error {
if cfg.L2NodeAddr == "" { // empty addr is valid, as it is optional.
return errors.New("empty L2 Node Address")
}
return nil return nil
} }
type L2SyncRPCConfig struct { type PreparedL2SyncEndpoint struct {
// RPC endpoint to use for syncing // RPC endpoint to use for syncing, may be nil if RPC alt-sync is disabled.
Rpc client.RPC Client client.RPC
TrustRPC bool
} }
var _ L2SyncEndpointSetup = (*L2SyncRPCConfig)(nil) var _ L2SyncEndpointSetup = (*PreparedL2SyncEndpoint)(nil)
func (cfg *L2SyncRPCConfig) Setup(ctx context.Context, log log.Logger) (client.RPC, error) { func (cfg *PreparedL2SyncEndpoint) Setup(ctx context.Context, log log.Logger, rollupCfg *rollup.Config) (client.RPC, *sources.SyncClientConfig, error) {
return cfg.Rpc, nil return cfg.Client, sources.SyncClientDefaultConfig(rollupCfg, cfg.TrustRPC), nil
} }
func (cfg *L2SyncRPCConfig) Check() error { func (cfg *PreparedL2SyncEndpoint) Check() error {
if cfg.Rpc == nil {
return errors.New("rpc cannot be nil")
}
return nil return nil
} }
...@@ -135,16 +140,48 @@ type L1EndpointConfig struct { ...@@ -135,16 +140,48 @@ type L1EndpointConfig struct {
// L1RPCKind identifies the RPC provider kind that serves the RPC, // L1RPCKind identifies the RPC provider kind that serves the RPC,
// to inform the optimal usage of the RPC for transaction receipts fetching. // to inform the optimal usage of the RPC for transaction receipts fetching.
L1RPCKind sources.RPCProviderKind L1RPCKind sources.RPCProviderKind
// RateLimit specifies a self-imposed rate-limit on L1 requests. 0 is no rate-limit.
RateLimit float64
// BatchSize specifies the maximum batch-size, which also applies as L1 rate-limit burst amount (if set).
BatchSize int
// HttpPollInterval specifies the interval between polling for the latest L1 block,
// when the RPC is detected to be an HTTP type.
// It is recommended to use websockets or IPC for efficient following of the changing block.
// Setting this to 0 disables polling.
HttpPollInterval time.Duration
} }
var _ L1EndpointSetup = (*L1EndpointConfig)(nil) var _ L1EndpointSetup = (*L1EndpointConfig)(nil)
func (cfg *L1EndpointConfig) Setup(ctx context.Context, log log.Logger) (cl client.RPC, trust bool, kind sources.RPCProviderKind, err error) { func (cfg *L1EndpointConfig) Check() error {
l1Node, err := client.NewRPC(ctx, log, cfg.L1NodeAddr) if cfg.BatchSize < 1 || cfg.BatchSize > 500 {
return fmt.Errorf("batch size is invalid or unreasonable: %d", cfg.BatchSize)
}
if cfg.RateLimit < 0 {
return fmt.Errorf("rate limit cannot be negative")
}
return nil
}
func (cfg *L1EndpointConfig) Setup(ctx context.Context, log log.Logger, rollupCfg *rollup.Config) (client.RPC, *sources.L1ClientConfig, error) {
opts := []client.RPCOption{
client.WithHttpPollInterval(cfg.HttpPollInterval),
client.WithDialBackoff(10),
}
if cfg.RateLimit != 0 {
opts = append(opts, client.WithRateLimit(cfg.RateLimit, cfg.BatchSize))
}
l1Node, err := client.NewRPC(ctx, log, cfg.L1NodeAddr, opts...)
if err != nil { if err != nil {
return nil, false, sources.RPCKindBasic, fmt.Errorf("failed to dial L1 address (%s): %w", cfg.L1NodeAddr, err) return nil, nil, fmt.Errorf("failed to dial L1 address (%s): %w", cfg.L1NodeAddr, err)
} }
return l1Node, cfg.L1TrustRPC, cfg.L1RPCKind, nil rpcCfg := sources.L1ClientDefaultConfig(rollupCfg, cfg.L1TrustRPC, cfg.L1RPCKind)
rpcCfg.MaxRequestsPerBatch = cfg.BatchSize
return l1Node, rpcCfg, nil
} }
// PreparedL1Endpoint enables testing with an in-process pre-setup RPC connection to L1 // PreparedL1Endpoint enables testing with an in-process pre-setup RPC connection to L1
...@@ -156,6 +193,14 @@ type PreparedL1Endpoint struct { ...@@ -156,6 +193,14 @@ type PreparedL1Endpoint struct {
var _ L1EndpointSetup = (*PreparedL1Endpoint)(nil) var _ L1EndpointSetup = (*PreparedL1Endpoint)(nil)
func (p *PreparedL1Endpoint) Setup(ctx context.Context, log log.Logger) (cl client.RPC, trust bool, kind sources.RPCProviderKind, err error) { func (p *PreparedL1Endpoint) Setup(ctx context.Context, log log.Logger, rollupCfg *rollup.Config) (client.RPC, *sources.L1ClientConfig, error) {
return p.Client, p.TrustRPC, p.RPCProviderKind, nil return p.Client, sources.L1ClientDefaultConfig(rollupCfg, p.TrustRPC, p.RPCProviderKind), nil
}
func (cfg *PreparedL1Endpoint) Check() error {
if cfg.Client == nil {
return errors.New("rpc client cannot be nil")
}
return nil
} }
...@@ -80,6 +80,9 @@ func (cfg *Config) Check() error { ...@@ -80,6 +80,9 @@ func (cfg *Config) Check() error {
if err := cfg.L2.Check(); err != nil { if err := cfg.L2.Check(); err != nil {
return fmt.Errorf("l2 endpoint config error: %w", err) return fmt.Errorf("l2 endpoint config error: %w", err)
} }
if err := cfg.L2Sync.Check(); err != nil {
return fmt.Errorf("sync config error: %w", err)
}
if err := cfg.Rollup.Check(); err != nil { if err := cfg.Rollup.Check(); err != nil {
return fmt.Errorf("rollup config error: %w", err) return fmt.Errorf("rollup config error: %w", err)
} }
......
...@@ -33,6 +33,7 @@ type OpNode struct { ...@@ -33,6 +33,7 @@ type OpNode struct {
l1Source *sources.L1Client // L1 Client to fetch data from l1Source *sources.L1Client // L1 Client to fetch data from
l2Driver *driver.Driver // L2 Engine to Sync l2Driver *driver.Driver // L2 Engine to Sync
l2Source *sources.EngineClient // L2 Execution Engine RPC bindings l2Source *sources.EngineClient // L2 Execution Engine RPC bindings
rpcSync *sources.SyncClient // Alt-sync RPC client, optional (may be nil)
server *rpcServer // RPC server hosting the rollup-node API server *rpcServer // RPC server hosting the rollup-node API
p2pNode *p2p.NodeP2P // P2P node functionality p2pNode *p2p.NodeP2P // P2P node functionality
p2pSigner p2p.Signer // p2p gogssip application messages will be signed with this signer p2pSigner p2p.Signer // p2p gogssip application messages will be signed with this signer
...@@ -86,6 +87,9 @@ func (n *OpNode) init(ctx context.Context, cfg *Config, snapshotLog log.Logger) ...@@ -86,6 +87,9 @@ func (n *OpNode) init(ctx context.Context, cfg *Config, snapshotLog log.Logger)
if err := n.initL2(ctx, cfg, snapshotLog); err != nil { if err := n.initL2(ctx, cfg, snapshotLog); err != nil {
return err return err
} }
if err := n.initRPCSync(ctx, cfg); err != nil {
return err
}
if err := n.initP2PSigner(ctx, cfg); err != nil { if err := n.initP2PSigner(ctx, cfg); err != nil {
return err return err
} }
...@@ -112,14 +116,13 @@ func (n *OpNode) initTracer(ctx context.Context, cfg *Config) error { ...@@ -112,14 +116,13 @@ func (n *OpNode) initTracer(ctx context.Context, cfg *Config) error {
} }
func (n *OpNode) initL1(ctx context.Context, cfg *Config) error { func (n *OpNode) initL1(ctx context.Context, cfg *Config) error {
l1Node, trustRPC, rpcProvKind, err := cfg.L1.Setup(ctx, n.log) l1Node, rpcCfg, err := cfg.L1.Setup(ctx, n.log, &cfg.Rollup)
if err != nil { if err != nil {
return fmt.Errorf("failed to get L1 RPC client: %w", err) return fmt.Errorf("failed to get L1 RPC client: %w", err)
} }
n.l1Source, err = sources.NewL1Client( n.l1Source, err = sources.NewL1Client(
client.NewInstrumentedRPC(l1Node, n.metrics), n.log, n.metrics.L1SourceCache, client.NewInstrumentedRPC(l1Node, n.metrics), n.log, n.metrics.L1SourceCache, rpcCfg)
sources.L1ClientDefaultConfig(&cfg.Rollup, trustRPC, rpcProvKind))
if err != nil { if err != nil {
return fmt.Errorf("failed to create L1 source: %w", err) return fmt.Errorf("failed to create L1 source: %w", err)
} }
...@@ -180,14 +183,13 @@ func (n *OpNode) initRuntimeConfig(ctx context.Context, cfg *Config) error { ...@@ -180,14 +183,13 @@ func (n *OpNode) initRuntimeConfig(ctx context.Context, cfg *Config) error {
} }
func (n *OpNode) initL2(ctx context.Context, cfg *Config, snapshotLog log.Logger) error { func (n *OpNode) initL2(ctx context.Context, cfg *Config, snapshotLog log.Logger) error {
rpcClient, err := cfg.L2.Setup(ctx, n.log) rpcClient, rpcCfg, err := cfg.L2.Setup(ctx, n.log, &cfg.Rollup)
if err != nil { if err != nil {
return fmt.Errorf("failed to setup L2 execution-engine RPC client: %w", err) return fmt.Errorf("failed to setup L2 execution-engine RPC client: %w", err)
} }
n.l2Source, err = sources.NewEngineClient( n.l2Source, err = sources.NewEngineClient(
client.NewInstrumentedRPC(rpcClient, n.metrics), n.log, n.metrics.L2SourceCache, client.NewInstrumentedRPC(rpcClient, n.metrics), n.log, n.metrics.L2SourceCache, rpcCfg,
sources.EngineClientDefaultConfig(&cfg.Rollup),
) )
if err != nil { if err != nil {
return fmt.Errorf("failed to create Engine client: %w", err) return fmt.Errorf("failed to create Engine client: %w", err)
...@@ -197,29 +199,24 @@ func (n *OpNode) initL2(ctx context.Context, cfg *Config, snapshotLog log.Logger ...@@ -197,29 +199,24 @@ func (n *OpNode) initL2(ctx context.Context, cfg *Config, snapshotLog log.Logger
return err return err
} }
var syncClient *sources.SyncClient n.l2Driver = driver.NewDriver(&cfg.Driver, &cfg.Rollup, n.l2Source, n.l1Source, n, n, n.log, snapshotLog, n.metrics)
// If the L2 sync config is present, use it to create a sync client
if cfg.L2Sync != nil {
if err := cfg.L2Sync.Check(); err != nil {
log.Info("L2 sync config is not present, skipping L2 sync client setup", "err", err)
} else {
rpcSyncClient, err := cfg.L2Sync.Setup(ctx, n.log)
if err != nil {
return fmt.Errorf("failed to setup L2 execution-engine RPC client for backup sync: %w", err)
}
// The sync client's RPC is always trusted return nil
config := sources.SyncClientDefaultConfig(&cfg.Rollup, true) }
syncClient, err = sources.NewSyncClient(n.OnUnsafeL2Payload, rpcSyncClient, n.log, n.metrics.L2SourceCache, config) func (n *OpNode) initRPCSync(ctx context.Context, cfg *Config) error {
rpcSyncClient, rpcCfg, err := cfg.L2Sync.Setup(ctx, n.log, &cfg.Rollup)
if err != nil { if err != nil {
return fmt.Errorf("failed to create sync client: %w", err) return fmt.Errorf("failed to setup L2 execution-engine RPC client for backup sync: %w", err)
} }
if rpcSyncClient == nil { // if no RPC client is configured to sync from, then don't add the RPC sync client
return nil
} }
syncClient, err := sources.NewSyncClient(n.OnUnsafeL2Payload, rpcSyncClient, n.log, n.metrics.L2SourceCache, rpcCfg)
if err != nil {
return fmt.Errorf("failed to create sync client: %w", err)
} }
n.rpcSync = syncClient
n.l2Driver = driver.NewDriver(&cfg.Driver, &cfg.Rollup, n.l2Source, n.l1Source, syncClient, n, n.log, snapshotLog, n.metrics)
return nil return nil
} }
...@@ -292,11 +289,12 @@ func (n *OpNode) Start(ctx context.Context) error { ...@@ -292,11 +289,12 @@ func (n *OpNode) Start(ctx context.Context) error {
} }
// If the backup unsafe sync client is enabled, start its event loop // If the backup unsafe sync client is enabled, start its event loop
if n.l2Driver.L2SyncCl != nil { if n.rpcSync != nil {
if err := n.l2Driver.L2SyncCl.Start(); err != nil { if err := n.rpcSync.Start(); err != nil {
n.log.Error("Could not start the backup sync client", "err", err) n.log.Error("Could not start the backup sync client", "err", err)
return err return err
} }
n.log.Info("Started L2-RPC sync service")
} }
return nil return nil
...@@ -375,6 +373,14 @@ func (n *OpNode) OnUnsafeL2Payload(ctx context.Context, from peer.ID, payload *e ...@@ -375,6 +373,14 @@ func (n *OpNode) OnUnsafeL2Payload(ctx context.Context, from peer.ID, payload *e
return nil return nil
} }
func (n *OpNode) RequestL2Range(ctx context.Context, start, end uint64) error {
if n.rpcSync != nil {
return n.rpcSync.RequestL2Range(ctx, start, end)
}
n.log.Debug("ignoring request to sync L2 range, no sync method available")
return nil
}
func (n *OpNode) P2P() p2p.Node { func (n *OpNode) P2P() p2p.Node {
return n.p2pNode return n.p2pNode
} }
...@@ -413,8 +419,8 @@ func (n *OpNode) Close() error { ...@@ -413,8 +419,8 @@ func (n *OpNode) Close() error {
} }
// If the L2 sync client is present & running, close it. // If the L2 sync client is present & running, close it.
if n.l2Driver.L2SyncCl != nil { if n.rpcSync != nil {
if err := n.l2Driver.L2SyncCl.Close(); err != nil { if err := n.rpcSync.Close(); err != nil {
result = multierror.Append(result, fmt.Errorf("failed to close L2 engine backup sync client cleanly: %w", err)) result = multierror.Append(result, fmt.Errorf("failed to close L2 engine backup sync client cleanly: %w", err))
} }
} }
......
...@@ -115,7 +115,7 @@ func TestOutputAtBlock(t *testing.T) { ...@@ -115,7 +115,7 @@ func TestOutputAtBlock(t *testing.T) {
require.NoError(t, server.Start()) require.NoError(t, server.Start())
defer server.Stop() defer server.Stop()
client, err := rpcclient.DialRPCClientWithBackoff(context.Background(), log, "http://"+server.Addr().String()) client, err := rpcclient.NewRPC(context.Background(), log, "http://"+server.Addr().String(), rpcclient.WithDialBackoff(3))
require.NoError(t, err) require.NoError(t, err)
var out *eth.OutputResponse var out *eth.OutputResponse
...@@ -147,7 +147,7 @@ func TestVersion(t *testing.T) { ...@@ -147,7 +147,7 @@ func TestVersion(t *testing.T) {
assert.NoError(t, server.Start()) assert.NoError(t, server.Start())
defer server.Stop() defer server.Stop()
client, err := rpcclient.DialRPCClientWithBackoff(context.Background(), log, "http://"+server.Addr().String()) client, err := rpcclient.NewRPC(context.Background(), log, "http://"+server.Addr().String(), rpcclient.WithDialBackoff(3))
assert.NoError(t, err) assert.NoError(t, err)
var out string var out string
...@@ -189,7 +189,7 @@ func TestSyncStatus(t *testing.T) { ...@@ -189,7 +189,7 @@ func TestSyncStatus(t *testing.T) {
assert.NoError(t, server.Start()) assert.NoError(t, server.Start())
defer server.Stop() defer server.Stop()
client, err := rpcclient.DialRPCClientWithBackoff(context.Background(), log, "http://"+server.Addr().String()) client, err := rpcclient.NewRPC(context.Background(), log, "http://"+server.Addr().String(), rpcclient.WithDialBackoff(3))
assert.NoError(t, err) assert.NoError(t, err)
var out *eth.SyncStatus var out *eth.SyncStatus
......
package p2p
import (
"testing"
"github.com/stretchr/testify/require"
)
// TestBandScorer_ParseDefault tests the [BandScorer.Parse] function
// on the default band scores cli flag value.
func TestBandScorer_ParseDefault(t *testing.T) {
// Create a new band scorer.
bandScorer, err := NewBandScorer("-40:graylist;-20:restricted;0:nopx;20:friend;")
require.NoError(t, err)
// Validate the [BandScorer] internals.
require.ElementsMatch(t, bandScorer.bands, []scorePair{
{band: "graylist", threshold: -40},
{band: "restricted", threshold: -20},
{band: "nopx", threshold: 0},
{band: "friend", threshold: 20},
})
}
// TestBandScorer_BucketCorrectly tests the [BandScorer.Bucket] function
// on a variety of scores.
func TestBandScorer_BucketCorrectly(t *testing.T) {
// Create a new band scorer.
bandScorer, err := NewBandScorer("-40:graylist;-20:restricted;0:nopx;20:friend;")
require.NoError(t, err)
// Validate the [BandScorer] internals.
require.Equal(t, bandScorer.Bucket(-100), "graylist")
require.Equal(t, bandScorer.Bucket(-40), "graylist")
require.Equal(t, bandScorer.Bucket(-39), "restricted")
require.Equal(t, bandScorer.Bucket(-20), "restricted")
require.Equal(t, bandScorer.Bucket(-19), "nopx")
require.Equal(t, bandScorer.Bucket(0), "nopx")
require.Equal(t, bandScorer.Bucket(1), "friend")
require.Equal(t, bandScorer.Bucket(20), "friend")
require.Equal(t, bandScorer.Bucket(21), "friend")
}
// TestBandScorer_BucketInverted tests the [BandScorer.Bucket] function
// on a variety of scores, in descending order.
func TestBandScorer_BucketInverted(t *testing.T) {
// Create a new band scorer.
bandScorer, err := NewBandScorer("20:friend;0:nopx;-20:restricted;-40:graylist;")
require.NoError(t, err)
// Validate the [BandScorer] internals.
require.Equal(t, bandScorer.Bucket(-100), "graylist")
require.Equal(t, bandScorer.Bucket(-40), "graylist")
require.Equal(t, bandScorer.Bucket(-39), "restricted")
require.Equal(t, bandScorer.Bucket(-20), "restricted")
require.Equal(t, bandScorer.Bucket(-19), "nopx")
require.Equal(t, bandScorer.Bucket(0), "nopx")
require.Equal(t, bandScorer.Bucket(1), "friend")
require.Equal(t, bandScorer.Bucket(20), "friend")
require.Equal(t, bandScorer.Bucket(21), "friend")
}
// TestBandScorer_ParseEmpty tests the [BandScorer.Parse] function
// on an empty string.
func TestBandScorer_ParseEmpty(t *testing.T) {
// Create a band scorer on an empty string.
bandScorer, err := NewBandScorer("")
require.NoError(t, err)
// Validate the [BandScorer] internals.
require.Len(t, bandScorer.bands, 0)
}
// TestBandScorer_ParseWhitespace tests the [BandScorer.Parse] function
// on a variety of whitespaced strings.
func TestBandScorer_ParseWhitespace(t *testing.T) {
// Create a band scorer on an empty string.
bandScorer, err := NewBandScorer(" ; ; ; ")
require.NoError(t, err)
// Validate the [BandScorer] internals.
require.Len(t, bandScorer.bands, 0)
}
...@@ -58,6 +58,10 @@ func NewConfig(ctx *cli.Context, blockTime uint64) (*p2p.Config, error) { ...@@ -58,6 +58,10 @@ func NewConfig(ctx *cli.Context, blockTime uint64) (*p2p.Config, error) {
return nil, fmt.Errorf("failed to load p2p peer scoring options: %w", err) return nil, fmt.Errorf("failed to load p2p peer scoring options: %w", err)
} }
if err := loadPeerScoreBands(conf, ctx); err != nil {
return nil, fmt.Errorf("failed to load p2p peer score bands: %w", err)
}
if err := loadBanningOption(conf, ctx); err != nil { if err := loadBanningOption(conf, ctx); err != nil {
return nil, fmt.Errorf("failed to load banning option: %w", err) return nil, fmt.Errorf("failed to load banning option: %w", err)
} }
...@@ -121,6 +125,17 @@ func loadPeerScoringParams(conf *p2p.Config, ctx *cli.Context, blockTime uint64) ...@@ -121,6 +125,17 @@ func loadPeerScoringParams(conf *p2p.Config, ctx *cli.Context, blockTime uint64)
return nil return nil
} }
// loadPeerScoreBands loads [p2p.BandScorer] from the CLI context.
func loadPeerScoreBands(conf *p2p.Config, ctx *cli.Context) error {
scoreBands := ctx.GlobalString(flags.PeerScoreBands.Name)
bandScorer, err := p2p.NewBandScorer(scoreBands)
if err != nil {
return err
}
conf.BandScoreThresholds = *bandScorer
return nil
}
// loadBanningOption loads whether or not to ban peers from the CLI context. // loadBanningOption loads whether or not to ban peers from the CLI context.
func loadBanningOption(conf *p2p.Config, ctx *cli.Context) error { func loadBanningOption(conf *p2p.Config, ctx *cli.Context) error {
ban := ctx.GlobalBool(flags.Banning.Name) ban := ctx.GlobalBool(flags.Banning.Name)
......
...@@ -54,6 +54,9 @@ type Config struct { ...@@ -54,6 +54,9 @@ type Config struct {
PeerScoring pubsub.PeerScoreParams PeerScoring pubsub.PeerScoreParams
TopicScoring pubsub.TopicScoreParams TopicScoring pubsub.TopicScoreParams
// Peer Score Band Thresholds
BandScoreThresholds BandScoreThresholds
// Whether to ban peers based on their [PeerScoring] score. // Whether to ban peers based on their [PeerScoring] score.
BanningEnabled bool BanningEnabled bool
...@@ -151,6 +154,10 @@ func (conf *Config) PeerScoringParams() *pubsub.PeerScoreParams { ...@@ -151,6 +154,10 @@ func (conf *Config) PeerScoringParams() *pubsub.PeerScoreParams {
return &conf.PeerScoring return &conf.PeerScoring
} }
func (conf *Config) PeerBandScorer() *BandScoreThresholds {
return &conf.BandScoreThresholds
}
func (conf *Config) BanPeers() bool { func (conf *Config) BanPeers() bool {
return conf.BanningEnabled return conf.BanningEnabled
} }
......
...@@ -55,6 +55,7 @@ type GossipSetupConfigurables interface { ...@@ -55,6 +55,7 @@ type GossipSetupConfigurables interface {
TopicScoringParams() *pubsub.TopicScoreParams TopicScoringParams() *pubsub.TopicScoreParams
BanPeers() bool BanPeers() bool
ConfigureGossip(params *pubsub.GossipSubParams) []pubsub.Option ConfigureGossip(params *pubsub.GossipSubParams) []pubsub.Option
PeerBandScorer() *BandScoreThresholds
} }
type GossipRuntimeConfig interface { type GossipRuntimeConfig interface {
...@@ -64,7 +65,8 @@ type GossipRuntimeConfig interface { ...@@ -64,7 +65,8 @@ type GossipRuntimeConfig interface {
//go:generate mockery --name GossipMetricer //go:generate mockery --name GossipMetricer
type GossipMetricer interface { type GossipMetricer interface {
RecordGossipEvent(evType int32) RecordGossipEvent(evType int32)
RecordPeerScoring(peerID peer.ID, score float64) // Peer Scoring Metric Funcs
SetPeerScores(map[string]float64)
} }
func blocksTopicV1(cfg *rollup.Config) string { func blocksTopicV1(cfg *rollup.Config) string {
......
// Code generated by mockery v2.14.0. DO NOT EDIT. // Code generated by mockery v2.22.1. DO NOT EDIT.
package mocks package mocks
...@@ -123,13 +123,16 @@ func (_m *ConnectionGater) InterceptUpgraded(_a0 network.Conn) (bool, control.Di ...@@ -123,13 +123,16 @@ func (_m *ConnectionGater) InterceptUpgraded(_a0 network.Conn) (bool, control.Di
ret := _m.Called(_a0) ret := _m.Called(_a0)
var r0 bool var r0 bool
var r1 control.DisconnectReason
if rf, ok := ret.Get(0).(func(network.Conn) (bool, control.DisconnectReason)); ok {
return rf(_a0)
}
if rf, ok := ret.Get(0).(func(network.Conn) bool); ok { if rf, ok := ret.Get(0).(func(network.Conn) bool); ok {
r0 = rf(_a0) r0 = rf(_a0)
} else { } else {
r0 = ret.Get(0).(bool) r0 = ret.Get(0).(bool)
} }
var r1 control.DisconnectReason
if rf, ok := ret.Get(1).(func(network.Conn) control.DisconnectReason); ok { if rf, ok := ret.Get(1).(func(network.Conn) control.DisconnectReason); ok {
r1 = rf(_a0) r1 = rf(_a0)
} else { } else {
...@@ -139,6 +142,20 @@ func (_m *ConnectionGater) InterceptUpgraded(_a0 network.Conn) (bool, control.Di ...@@ -139,6 +142,20 @@ func (_m *ConnectionGater) InterceptUpgraded(_a0 network.Conn) (bool, control.Di
return r0, r1 return r0, r1
} }
// IsBlocked provides a mock function with given fields: p
func (_m *ConnectionGater) IsBlocked(p peer.ID) bool {
ret := _m.Called(p)
var r0 bool
if rf, ok := ret.Get(0).(func(peer.ID) bool); ok {
r0 = rf(p)
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// ListBlockedAddrs provides a mock function with given fields: // ListBlockedAddrs provides a mock function with given fields:
func (_m *ConnectionGater) ListBlockedAddrs() []net.IP { func (_m *ConnectionGater) ListBlockedAddrs() []net.IP {
ret := _m.Called() ret := _m.Called()
......
// Code generated by mockery v2.14.0. DO NOT EDIT. // Code generated by mockery v2.22.1. DO NOT EDIT.
package mocks package mocks
import ( import mock "github.com/stretchr/testify/mock"
mock "github.com/stretchr/testify/mock"
peer "github.com/libp2p/go-libp2p/core/peer"
)
// GossipMetricer is an autogenerated mock type for the GossipMetricer type // GossipMetricer is an autogenerated mock type for the GossipMetricer type
type GossipMetricer struct { type GossipMetricer struct {
...@@ -18,9 +14,9 @@ func (_m *GossipMetricer) RecordGossipEvent(evType int32) { ...@@ -18,9 +14,9 @@ func (_m *GossipMetricer) RecordGossipEvent(evType int32) {
_m.Called(evType) _m.Called(evType)
} }
// RecordPeerScoring provides a mock function with given fields: peerID, score // SetPeerScores provides a mock function with given fields: _a0
func (_m *GossipMetricer) RecordPeerScoring(peerID peer.ID, score float64) { func (_m *GossipMetricer) SetPeerScores(_a0 map[string]float64) {
_m.Called(peerID, score) _m.Called(_a0)
} }
type mockConstructorTestingTNewGossipMetricer interface { type mockConstructorTestingTNewGossipMetricer interface {
......
// Code generated by mockery v2.14.0. DO NOT EDIT. // Code generated by mockery v2.22.1. DO NOT EDIT.
package mocks package mocks
...@@ -13,6 +13,20 @@ type PeerGater struct { ...@@ -13,6 +13,20 @@ type PeerGater struct {
mock.Mock mock.Mock
} }
// IsBlocked provides a mock function with given fields: _a0
func (_m *PeerGater) IsBlocked(_a0 peer.ID) bool {
ret := _m.Called(_a0)
var r0 bool
if rf, ok := ret.Get(0).(func(peer.ID) bool); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// Update provides a mock function with given fields: _a0, _a1 // Update provides a mock function with given fields: _a0, _a1
func (_m *PeerGater) Update(_a0 peer.ID, _a1 float64) { func (_m *PeerGater) Update(_a0 peer.ID, _a1 float64) {
_m.Called(_a0, _a1) _m.Called(_a0, _a1)
......
// Code generated by mockery v2.14.0. DO NOT EDIT. // Code generated by mockery v2.22.1. DO NOT EDIT.
package mocks package mocks
......
...@@ -3,7 +3,6 @@ package p2p ...@@ -3,7 +3,6 @@ package p2p
import ( import (
log "github.com/ethereum/go-ethereum/log" log "github.com/ethereum/go-ethereum/log"
peer "github.com/libp2p/go-libp2p/core/peer" peer "github.com/libp2p/go-libp2p/core/peer"
slices "golang.org/x/exp/slices"
) )
// ConnectionFactor is the factor by which we multiply the connection score. // ConnectionFactor is the factor by which we multiply the connection score.
...@@ -15,6 +14,7 @@ const PeerScoreThreshold = -100 ...@@ -15,6 +14,7 @@ const PeerScoreThreshold = -100
// gater is an internal implementation of the [PeerGater] interface. // gater is an internal implementation of the [PeerGater] interface.
type gater struct { type gater struct {
connGater ConnectionGater connGater ConnectionGater
blockedMap map[peer.ID]bool
log log.Logger log log.Logger
banEnabled bool banEnabled bool
} }
...@@ -25,29 +25,51 @@ type gater struct { ...@@ -25,29 +25,51 @@ type gater struct {
type PeerGater interface { type PeerGater interface {
// Update handles a peer score update and blocks/unblocks the peer if necessary. // Update handles a peer score update and blocks/unblocks the peer if necessary.
Update(peer.ID, float64) Update(peer.ID, float64)
// IsBlocked returns true if the given [peer.ID] is blocked.
IsBlocked(peer.ID) bool
} }
// NewPeerGater returns a new peer gater. // NewPeerGater returns a new peer gater.
func NewPeerGater(connGater ConnectionGater, log log.Logger, banEnabled bool) PeerGater { func NewPeerGater(connGater ConnectionGater, log log.Logger, banEnabled bool) PeerGater {
return &gater{ return &gater{
connGater: connGater, connGater: connGater,
blockedMap: make(map[peer.ID]bool),
log: log, log: log,
banEnabled: banEnabled, banEnabled: banEnabled,
} }
} }
// IsBlocked returns true if the given [peer.ID] is blocked.
func (s *gater) IsBlocked(peerID peer.ID) bool {
return s.blockedMap[peerID]
}
// setBlocked sets the blocked status of the given [peer.ID].
func (s *gater) setBlocked(peerID peer.ID, blocked bool) {
s.blockedMap[peerID] = blocked
}
// Update handles a peer score update and blocks/unblocks the peer if necessary. // Update handles a peer score update and blocks/unblocks the peer if necessary.
func (s *gater) Update(id peer.ID, score float64) { func (s *gater) Update(id peer.ID, score float64) {
// Check if the peer score is below the threshold // Check if the peer score is below the threshold
// If so, we need to block the peer // If so, we need to block the peer
if score < PeerScoreThreshold && s.banEnabled { isAlreadyBlocked := s.IsBlocked(id)
if score < PeerScoreThreshold && s.banEnabled && !isAlreadyBlocked {
s.log.Warn("peer blocking enabled, blocking peer", "id", id.String(), "score", score) s.log.Warn("peer blocking enabled, blocking peer", "id", id.String(), "score", score)
err := s.connGater.BlockPeer(id) err := s.connGater.BlockPeer(id)
s.log.Warn("connection gater failed to block peer", id.String(), "err", err) if err != nil {
s.log.Warn("connection gater failed to block peer", "id", id.String(), "err", err)
}
// Set the peer as blocked in the blocked map
s.setBlocked(id, true)
} }
// Unblock peers whose score has recovered to an acceptable level // Unblock peers whose score has recovered to an acceptable level
if (score > PeerScoreThreshold) && slices.Contains(s.connGater.ListBlockedPeers(), id) { if (score > PeerScoreThreshold) && isAlreadyBlocked {
err := s.connGater.UnblockPeer(id) err := s.connGater.UnblockPeer(id)
s.log.Warn("connection gater failed to unblock peer", id.String(), "err", err) if err != nil {
s.log.Warn("connection gater failed to unblock peer", "id", id.String(), "err", err)
}
// Set the peer as unblocked in the blocked map
s.setBlocked(id, false)
} }
} }
...@@ -37,30 +37,59 @@ func (testSuite *PeerGaterTestSuite) TestPeerScoreConstants() { ...@@ -37,30 +37,59 @@ func (testSuite *PeerGaterTestSuite) TestPeerScoreConstants() {
} }
// TestPeerGaterUpdate tests the peer gater update hook. // TestPeerGaterUpdate tests the peer gater update hook.
func (testSuite *PeerGaterTestSuite) TestPeerGaterUpdate() { func (testSuite *PeerGaterTestSuite) TestPeerGater_UpdateBansPeers() {
gater := p2p.NewPeerGater( gater := p2p.NewPeerGater(
testSuite.mockGater, testSuite.mockGater,
testSuite.logger, testSuite.logger,
true, true,
) )
// Return an empty list of already blocked peers
testSuite.mockGater.On("ListBlockedPeers").Return([]peer.ID{}).Once()
// Mock a connection gater peer block call // Mock a connection gater peer block call
// Since the peer score is below the [PeerScoreThreshold] of -100, // Since the peer score is below the [PeerScoreThreshold] of -100,
// the [BlockPeer] method should be called // the [BlockPeer] method should be called
testSuite.mockGater.On("BlockPeer", peer.ID("peer1")).Return(nil) testSuite.mockGater.On("BlockPeer", peer.ID("peer1")).Return(nil).Once()
// The peer should initially be unblocked
testSuite.False(gater.IsBlocked(peer.ID("peer1")))
// Apply the peer gater update // Apply the peer gater update
gater.Update(peer.ID("peer1"), float64(-100)) gater.Update(peer.ID("peer1"), float64(-101))
// The peer should be considered blocked
testSuite.True(gater.IsBlocked(peer.ID("peer1")))
// Now let's unblock the peer
testSuite.mockGater.On("UnblockPeer", peer.ID("peer1")).Return(nil).Once()
gater.Update(peer.ID("peer1"), float64(0))
// The peer should be considered unblocked
testSuite.False(gater.IsBlocked(peer.ID("peer1")))
} }
// TestPeerGaterUpdateNoBanning tests the peer gater update hook without banning set // TestPeerGaterUpdateNoBanning tests the peer gater update hook without banning set
func (testSuite *PeerGaterTestSuite) TestPeerGaterUpdateNoBanning() { func (testSuite *PeerGaterTestSuite) TestPeerGater_UpdateNoBanning() {
gater := p2p.NewPeerGater( gater := p2p.NewPeerGater(
testSuite.mockGater, testSuite.mockGater,
testSuite.logger, testSuite.logger,
false, false,
) )
// Return an empty list of already blocked peers
testSuite.mockGater.On("ListBlockedPeers").Return([]peer.ID{})
// Notice: [BlockPeer] should not be called since banning is not enabled // Notice: [BlockPeer] should not be called since banning is not enabled
// even though the peer score is way below the [PeerScoreThreshold] of -100
gater.Update(peer.ID("peer1"), float64(-100000)) gater.Update(peer.ID("peer1"), float64(-100000))
// The peer should be unblocked
testSuite.False(gater.IsBlocked(peer.ID("peer1")))
// Make sure that if we then "unblock" the peer, nothing happens
gater.Update(peer.ID("peer1"), float64(0))
// The peer should still be unblocked
testSuite.False(gater.IsBlocked(peer.ID("peer1")))
} }
package p2p package p2p
import ( import (
"fmt"
"sort"
"strconv"
"strings"
log "github.com/ethereum/go-ethereum/log" log "github.com/ethereum/go-ethereum/log"
pubsub "github.com/libp2p/go-libp2p-pubsub" pubsub "github.com/libp2p/go-libp2p-pubsub"
peer "github.com/libp2p/go-libp2p/core/peer" peer "github.com/libp2p/go-libp2p/core/peer"
...@@ -11,6 +16,68 @@ type scorer struct { ...@@ -11,6 +16,68 @@ type scorer struct {
metricer GossipMetricer metricer GossipMetricer
log log.Logger log log.Logger
gater PeerGater gater PeerGater
bandScoreThresholds *BandScoreThresholds
}
// scorePair holds a band and its corresponding threshold.
type scorePair struct {
band string
threshold float64
}
// BandScoreThresholds holds the thresholds for classifying peers
// into different score bands.
type BandScoreThresholds struct {
bands []scorePair
}
// NewBandScorer constructs a new [BandScoreThresholds] instance.
func NewBandScorer(str string) (*BandScoreThresholds, error) {
s := &BandScoreThresholds{
bands: make([]scorePair, 0),
}
for _, band := range strings.Split(str, ";") {
// Skip empty band strings.
band := strings.TrimSpace(band)
if band == "" {
continue
}
split := strings.Split(band, ":")
if len(split) != 2 {
return nil, fmt.Errorf("invalid score band: %s", band)
}
threshold, err := strconv.ParseFloat(split[0], 64)
if err != nil {
return nil, err
}
s.bands = append(s.bands, scorePair{
band: split[1],
threshold: threshold,
})
}
// Order the bands by threshold in ascending order.
sort.Slice(s.bands, func(i, j int) bool {
return s.bands[i].threshold < s.bands[j].threshold
})
return s, nil
}
// Bucket returns the appropriate band for a given score.
func (s *BandScoreThresholds) Bucket(score float64) string {
for _, pair := range s.bands {
if score <= pair.threshold {
return pair.band
}
}
// If there is no band threshold higher than the score,
// the peer must be placed in the highest bucket.
if len(s.bands) > 0 {
return s.bands[len(s.bands)-1].band
}
return ""
} }
// Peerstore is a subset of the libp2p peerstore.Peerstore interface. // Peerstore is a subset of the libp2p peerstore.Peerstore interface.
...@@ -34,12 +101,13 @@ type Scorer interface { ...@@ -34,12 +101,13 @@ type Scorer interface {
} }
// NewScorer returns a new peer scorer. // NewScorer returns a new peer scorer.
func NewScorer(peerGater PeerGater, peerStore Peerstore, metricer GossipMetricer, log log.Logger) Scorer { func NewScorer(peerGater PeerGater, peerStore Peerstore, metricer GossipMetricer, bandScoreThresholds *BandScoreThresholds, log log.Logger) Scorer {
return &scorer{ return &scorer{
peerStore: peerStore, peerStore: peerStore,
metricer: metricer, metricer: metricer,
log: log, log: log,
gater: peerGater, gater: peerGater,
bandScoreThresholds: bandScoreThresholds,
} }
} }
...@@ -48,13 +116,18 @@ func NewScorer(peerGater PeerGater, peerStore Peerstore, metricer GossipMetricer ...@@ -48,13 +116,18 @@ func NewScorer(peerGater PeerGater, peerStore Peerstore, metricer GossipMetricer
// The returned [pubsub.ExtendedPeerScoreInspectFn] is called with a mapping of peer IDs to peer score snapshots. // The returned [pubsub.ExtendedPeerScoreInspectFn] is called with a mapping of peer IDs to peer score snapshots.
func (s *scorer) SnapshotHook() pubsub.ExtendedPeerScoreInspectFn { func (s *scorer) SnapshotHook() pubsub.ExtendedPeerScoreInspectFn {
return func(m map[peer.ID]*pubsub.PeerScoreSnapshot) { return func(m map[peer.ID]*pubsub.PeerScoreSnapshot) {
scoreMap := make(map[string]float64)
// Zero out all bands.
for _, b := range s.bandScoreThresholds.bands {
scoreMap[b.band] = 0
}
// Now set the new scores.
for id, snap := range m { for id, snap := range m {
// Record peer score in the metricer band := s.bandScoreThresholds.Bucket(snap.Score)
s.metricer.RecordPeerScoring(id, snap.Score) scoreMap[band] += 1
// Update with the peer gater
s.gater.Update(id, snap.Score) s.gater.Update(id, snap.Score)
} }
s.metricer.SetPeerScores(scoreMap)
} }
} }
......
...@@ -20,15 +20,18 @@ type PeerScorerTestSuite struct { ...@@ -20,15 +20,18 @@ type PeerScorerTestSuite struct {
mockGater *p2pMocks.PeerGater mockGater *p2pMocks.PeerGater
mockStore *p2pMocks.Peerstore mockStore *p2pMocks.Peerstore
mockMetricer *p2pMocks.GossipMetricer mockMetricer *p2pMocks.GossipMetricer
bandScorer *p2p.BandScoreThresholds
logger log.Logger logger log.Logger
} }
// SetupTest sets up the test suite. // SetupTest sets up the test suite.
func (testSuite *PeerScorerTestSuite) SetupTest() { func (testSuite *PeerScorerTestSuite) SetupTest() {
testSuite.mockGater = &p2pMocks.PeerGater{} testSuite.mockGater = &p2pMocks.PeerGater{}
// testSuite.mockConnGater = &p2pMocks.ConnectionGater{}
testSuite.mockStore = &p2pMocks.Peerstore{} testSuite.mockStore = &p2pMocks.Peerstore{}
testSuite.mockMetricer = &p2pMocks.GossipMetricer{} testSuite.mockMetricer = &p2pMocks.GossipMetricer{}
bandScorer, err := p2p.NewBandScorer("-40:graylist;0:friend;")
testSuite.NoError(err)
testSuite.bandScorer = bandScorer
testSuite.logger = testlog.Logger(testSuite.T(), log.LvlError) testSuite.logger = testlog.Logger(testSuite.T(), log.LvlError)
} }
...@@ -37,44 +40,49 @@ func TestPeerScorer(t *testing.T) { ...@@ -37,44 +40,49 @@ func TestPeerScorer(t *testing.T) {
suite.Run(t, new(PeerScorerTestSuite)) suite.Run(t, new(PeerScorerTestSuite))
} }
// TestPeerScorerOnConnect ensures we can call the OnConnect method on the peer scorer. // TestScorer_OnConnect ensures we can call the OnConnect method on the peer scorer.
func (testSuite *PeerScorerTestSuite) TestPeerScorerOnConnect() { func (testSuite *PeerScorerTestSuite) TestScorer_OnConnect() {
scorer := p2p.NewScorer( scorer := p2p.NewScorer(
testSuite.mockGater, testSuite.mockGater,
testSuite.mockStore, testSuite.mockStore,
testSuite.mockMetricer, testSuite.mockMetricer,
testSuite.bandScorer,
testSuite.logger, testSuite.logger,
) )
scorer.OnConnect() scorer.OnConnect()
} }
// TestPeerScorerOnDisconnect ensures we can call the OnDisconnect method on the peer scorer. // TestScorer_OnDisconnect ensures we can call the OnDisconnect method on the peer scorer.
func (testSuite *PeerScorerTestSuite) TestPeerScorerOnDisconnect() { func (testSuite *PeerScorerTestSuite) TestScorer_OnDisconnect() {
scorer := p2p.NewScorer( scorer := p2p.NewScorer(
testSuite.mockGater, testSuite.mockGater,
testSuite.mockStore, testSuite.mockStore,
testSuite.mockMetricer, testSuite.mockMetricer,
testSuite.bandScorer,
testSuite.logger, testSuite.logger,
) )
scorer.OnDisconnect() scorer.OnDisconnect()
} }
// TestSnapshotHook tests running the snapshot hook on the peer scorer. // TestScorer_SnapshotHook tests running the snapshot hook on the peer scorer.
func (testSuite *PeerScorerTestSuite) TestSnapshotHook() { func (testSuite *PeerScorerTestSuite) TestScorer_SnapshotHook() {
scorer := p2p.NewScorer( scorer := p2p.NewScorer(
testSuite.mockGater, testSuite.mockGater,
testSuite.mockStore, testSuite.mockStore,
testSuite.mockMetricer, testSuite.mockMetricer,
testSuite.bandScorer,
testSuite.logger, testSuite.logger,
) )
inspectFn := scorer.SnapshotHook() inspectFn := scorer.SnapshotHook()
// Mock the snapshot updates
// This doesn't return anything
testSuite.mockMetricer.On("RecordPeerScoring", peer.ID("peer1"), float64(-100)).Return(nil)
// Mock the peer gater call // Mock the peer gater call
testSuite.mockGater.On("Update", peer.ID("peer1"), float64(-100)).Return(nil) testSuite.mockGater.On("Update", peer.ID("peer1"), float64(-100)).Return(nil).Once()
// The metricer should then be called with the peer score band map
testSuite.mockMetricer.On("SetPeerScores", map[string]float64{
"friend": 0,
"graylist": 1,
}).Return(nil).Once()
// Apply the snapshot // Apply the snapshot
snapshotMap := map[peer.ID]*pubsub.PeerScoreSnapshot{ snapshotMap := map[peer.ID]*pubsub.PeerScoreSnapshot{
...@@ -83,26 +91,46 @@ func (testSuite *PeerScorerTestSuite) TestSnapshotHook() { ...@@ -83,26 +91,46 @@ func (testSuite *PeerScorerTestSuite) TestSnapshotHook() {
}, },
} }
inspectFn(snapshotMap) inspectFn(snapshotMap)
// Change the peer score now to a different band
testSuite.mockGater.On("Update", peer.ID("peer1"), float64(0)).Return(nil).Once()
// The metricer should then be called with the peer score band map
testSuite.mockMetricer.On("SetPeerScores", map[string]float64{
"friend": 1,
"graylist": 0,
}).Return(nil).Once()
// Apply the snapshot
snapshotMap = map[peer.ID]*pubsub.PeerScoreSnapshot{
peer.ID("peer1"): {
Score: 0,
},
}
inspectFn(snapshotMap)
} }
// TestSnapshotHookBlockPeer tests running the snapshot hook on the peer scorer with a peer score below the threshold. // TestScorer_SnapshotHookBlocksPeer tests running the snapshot hook on the peer scorer with a peer score below the threshold.
// This implies that the peer should be blocked. // This implies that the peer should be blocked.
func (testSuite *PeerScorerTestSuite) TestSnapshotHookBlockPeer() { func (testSuite *PeerScorerTestSuite) TestScorer_SnapshotHookBlocksPeer() {
scorer := p2p.NewScorer( scorer := p2p.NewScorer(
testSuite.mockGater, testSuite.mockGater,
testSuite.mockStore, testSuite.mockStore,
testSuite.mockMetricer, testSuite.mockMetricer,
testSuite.bandScorer,
testSuite.logger, testSuite.logger,
) )
inspectFn := scorer.SnapshotHook() inspectFn := scorer.SnapshotHook()
// Mock the snapshot updates
// This doesn't return anything
testSuite.mockMetricer.On("RecordPeerScoring", peer.ID("peer1"), float64(-101)).Return(nil)
// Mock the peer gater call // Mock the peer gater call
testSuite.mockGater.On("Update", peer.ID("peer1"), float64(-101)).Return(nil) testSuite.mockGater.On("Update", peer.ID("peer1"), float64(-101)).Return(nil)
// The metricer should then be called with the peer score band map
testSuite.mockMetricer.On("SetPeerScores", map[string]float64{
"friend": 0,
"graylist": 1,
}).Return(nil)
// Apply the snapshot // Apply the snapshot
snapshotMap := map[peer.ID]*pubsub.PeerScoreSnapshot{ snapshotMap := map[peer.ID]*pubsub.PeerScoreSnapshot{
peer.ID("peer1"): { peer.ID("peer1"): {
......
...@@ -14,7 +14,7 @@ func ConfigurePeerScoring(h host.Host, g ConnectionGater, gossipConf GossipSetup ...@@ -14,7 +14,7 @@ func ConfigurePeerScoring(h host.Host, g ConnectionGater, gossipConf GossipSetup
peerScoreThresholds := NewPeerScoreThresholds() peerScoreThresholds := NewPeerScoreThresholds()
banEnabled := gossipConf.BanPeers() banEnabled := gossipConf.BanPeers()
peerGater := NewPeerGater(g, log, banEnabled) peerGater := NewPeerGater(g, log, banEnabled)
scorer := NewScorer(peerGater, h.Peerstore(), m, log) scorer := NewScorer(peerGater, h.Peerstore(), m, gossipConf.PeerBandScorer(), log)
opts := []pubsub.Option{} opts := []pubsub.Option{}
// Check the app specific score since libp2p doesn't export it's [validate] function :/ // Check the app specific score since libp2p doesn't export it's [validate] function :/
if peerScoreParams != nil && peerScoreParams.AppSpecificScore != nil { if peerScoreParams != nil && peerScoreParams.AppSpecificScore != nil {
......
...@@ -11,7 +11,7 @@ import ( ...@@ -11,7 +11,7 @@ import (
p2pMocks "github.com/ethereum-optimism/optimism/op-node/p2p/mocks" p2pMocks "github.com/ethereum-optimism/optimism/op-node/p2p/mocks"
testlog "github.com/ethereum-optimism/optimism/op-node/testlog" testlog "github.com/ethereum-optimism/optimism/op-node/testlog"
mock "github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
suite "github.com/stretchr/testify/suite" suite "github.com/stretchr/testify/suite"
log "github.com/ethereum/go-ethereum/log" log "github.com/ethereum/go-ethereum/log"
...@@ -30,6 +30,7 @@ type PeerScoresTestSuite struct { ...@@ -30,6 +30,7 @@ type PeerScoresTestSuite struct {
mockGater *p2pMocks.ConnectionGater mockGater *p2pMocks.ConnectionGater
mockStore *p2pMocks.Peerstore mockStore *p2pMocks.Peerstore
mockMetricer *p2pMocks.GossipMetricer mockMetricer *p2pMocks.GossipMetricer
bandScorer p2p.BandScoreThresholds
logger log.Logger logger log.Logger
} }
...@@ -38,6 +39,9 @@ func (testSuite *PeerScoresTestSuite) SetupTest() { ...@@ -38,6 +39,9 @@ func (testSuite *PeerScoresTestSuite) SetupTest() {
testSuite.mockGater = &p2pMocks.ConnectionGater{} testSuite.mockGater = &p2pMocks.ConnectionGater{}
testSuite.mockStore = &p2pMocks.Peerstore{} testSuite.mockStore = &p2pMocks.Peerstore{}
testSuite.mockMetricer = &p2pMocks.GossipMetricer{} testSuite.mockMetricer = &p2pMocks.GossipMetricer{}
bandScorer, err := p2p.NewBandScorer("0:graylist;")
testSuite.NoError(err)
testSuite.bandScorer = *bandScorer
testSuite.logger = testlog.Logger(testSuite.T(), log.LvlError) testSuite.logger = testlog.Logger(testSuite.T(), log.LvlError)
} }
...@@ -68,6 +72,7 @@ func newGossipSubs(testSuite *PeerScoresTestSuite, ctx context.Context, hosts [] ...@@ -68,6 +72,7 @@ func newGossipSubs(testSuite *PeerScoresTestSuite, ctx context.Context, hosts []
rt := pubsub.DefaultGossipSubRouter(h) rt := pubsub.DefaultGossipSubRouter(h)
opts := []pubsub.Option{} opts := []pubsub.Option{}
opts = append(opts, p2p.ConfigurePeerScoring(h, testSuite.mockGater, &p2p.Config{ opts = append(opts, p2p.ConfigurePeerScoring(h, testSuite.mockGater, &p2p.Config{
BandScoreThresholds: testSuite.bandScorer,
PeerScoring: pubsub.PeerScoreParams{ PeerScoring: pubsub.PeerScoreParams{
AppSpecificScore: func(p peer.ID) float64 { AppSpecificScore: func(p peer.ID) float64 {
if p == hosts[0].ID() { if p == hosts[0].ID() {
...@@ -118,8 +123,7 @@ func (testSuite *PeerScoresTestSuite) TestNegativeScores() { ...@@ -118,8 +123,7 @@ func (testSuite *PeerScoresTestSuite) TestNegativeScores() {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
testSuite.mockMetricer.On("RecordPeerScoring", mock.Anything, float64(0)).Return(nil) testSuite.mockMetricer.On("SetPeerScores", mock.Anything).Return(nil)
testSuite.mockMetricer.On("RecordPeerScoring", mock.Anything, float64(-1000)).Return(nil)
testSuite.mockGater.On("ListBlockedPeers").Return([]peer.ID{}) testSuite.mockGater.On("ListBlockedPeers").Return([]peer.ID{})
......
...@@ -68,6 +68,10 @@ func (p *Prepared) PeerScoringParams() *pubsub.PeerScoreParams { ...@@ -68,6 +68,10 @@ func (p *Prepared) PeerScoringParams() *pubsub.PeerScoreParams {
return nil return nil
} }
func (p *Prepared) PeerBandScorer() *BandScoreThresholds {
return nil
}
func (p *Prepared) BanPeers() bool { func (p *Prepared) BanPeers() bool {
return false return false
} }
......
...@@ -107,7 +107,7 @@ type EngineQueue struct { ...@@ -107,7 +107,7 @@ type EngineQueue struct {
// The queued-up attributes // The queued-up attributes
safeAttributesParent eth.L2BlockRef safeAttributesParent eth.L2BlockRef
safeAttributes *eth.PayloadAttributes safeAttributes *eth.PayloadAttributes
unsafePayloads PayloadsQueue // queue of unsafe payloads, ordered by ascending block number, may have gaps unsafePayloads *PayloadsQueue // queue of unsafe payloads, ordered by ascending block number, may have gaps and duplicates
// Tracks which L2 blocks where last derived from which L1 block. At most finalityLookback large. // Tracks which L2 blocks where last derived from which L1 block. At most finalityLookback large.
finalityData []FinalityData finalityData []FinalityData
...@@ -132,11 +132,7 @@ func NewEngineQueue(log log.Logger, cfg *rollup.Config, engine Engine, metrics M ...@@ -132,11 +132,7 @@ func NewEngineQueue(log log.Logger, cfg *rollup.Config, engine Engine, metrics M
engine: engine, engine: engine,
metrics: metrics, metrics: metrics,
finalityData: make([]FinalityData, 0, finalityLookback), finalityData: make([]FinalityData, 0, finalityLookback),
unsafePayloads: PayloadsQueue{ unsafePayloads: NewPayloadsQueue(maxUnsafePayloadsMemory, payloadMemSize),
MaxSize: maxUnsafePayloadsMemory,
SizeFn: payloadMemSize,
blockNos: make(map[uint64]bool),
},
prev: prev, prev: prev,
l1Fetcher: l1Fetcher, l1Fetcher: l1Fetcher,
} }
...@@ -682,7 +678,8 @@ func (eq *EngineQueue) Reset(ctx context.Context, _ eth.L1BlockRef, _ eth.System ...@@ -682,7 +678,8 @@ func (eq *EngineQueue) Reset(ctx context.Context, _ eth.L1BlockRef, _ eth.System
return io.EOF return io.EOF
} }
// GetUnsafeQueueGap retrieves the current [start, end] range of the gap between the tip of the unsafe priority queue and the unsafe head. // GetUnsafeQueueGap retrieves the current [start, end) range (incl. start, excl. end)
// of the gap between the tip of the unsafe priority queue and the unsafe head.
// If there is no gap, the difference between end and start will be 0. // If there is no gap, the difference between end and start will be 0.
func (eq *EngineQueue) GetUnsafeQueueGap(expectedNumber uint64) (start uint64, end uint64) { func (eq *EngineQueue) GetUnsafeQueueGap(expectedNumber uint64) (start uint64, end uint64) {
// The start of the gap is always the unsafe head + 1 // The start of the gap is always the unsafe head + 1
...@@ -691,9 +688,11 @@ func (eq *EngineQueue) GetUnsafeQueueGap(expectedNumber uint64) (start uint64, e ...@@ -691,9 +688,11 @@ func (eq *EngineQueue) GetUnsafeQueueGap(expectedNumber uint64) (start uint64, e
// If the priority queue is empty, the end is the first block number at the top of the priority queue // If the priority queue is empty, the end is the first block number at the top of the priority queue
// Otherwise, the end is the expected block number // Otherwise, the end is the expected block number
if first := eq.unsafePayloads.Peek(); first != nil { if first := eq.unsafePayloads.Peek(); first != nil {
// Don't include the payload we already have in the sync range
end = first.ID().Number end = first.ID().Number
} else { } else {
end = expectedNumber // Include the expected payload in the sync range
end = expectedNumber + 1
} }
return start, end return start, end
......
...@@ -5,6 +5,8 @@ import ( ...@@ -5,6 +5,8 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
) )
...@@ -48,8 +50,8 @@ func (pq *payloadsByNumber) Pop() any { ...@@ -48,8 +50,8 @@ func (pq *payloadsByNumber) Pop() any {
} }
const ( const (
// ~580 bytes per payload, with some margin for overhead // ~580 bytes per payload, with some margin for overhead like map data
payloadMemFixedCost uint64 = 600 payloadMemFixedCost uint64 = 800
// 24 bytes per tx overhead (size of slice header in memory) // 24 bytes per tx overhead (size of slice header in memory)
payloadTxMemOverhead uint64 = 24 payloadTxMemOverhead uint64 = 24
) )
...@@ -72,15 +74,25 @@ func payloadMemSize(p *eth.ExecutionPayload) uint64 { ...@@ -72,15 +74,25 @@ func payloadMemSize(p *eth.ExecutionPayload) uint64 {
// without the need to use heap.Push/heap.Pop as caller. // without the need to use heap.Push/heap.Pop as caller.
// PayloadsQueue maintains a MaxSize by counting and tracking sizes of added eth.ExecutionPayload entries. // PayloadsQueue maintains a MaxSize by counting and tracking sizes of added eth.ExecutionPayload entries.
// When the size grows too large, the first (lowest block-number) payload is removed from the queue. // When the size grows too large, the first (lowest block-number) payload is removed from the queue.
// PayloadsQueue allows entries with same block number, or even full duplicates. // PayloadsQueue allows entries with same block number, but does not allow duplicate blocks
type PayloadsQueue struct { type PayloadsQueue struct {
pq payloadsByNumber pq payloadsByNumber
currentSize uint64 currentSize uint64
MaxSize uint64 MaxSize uint64
blockNos map[uint64]bool blockHashes map[common.Hash]struct{}
SizeFn func(p *eth.ExecutionPayload) uint64 SizeFn func(p *eth.ExecutionPayload) uint64
} }
func NewPayloadsQueue(maxSize uint64, sizeFn func(p *eth.ExecutionPayload) uint64) *PayloadsQueue {
return &PayloadsQueue{
pq: nil,
currentSize: 0,
MaxSize: maxSize,
blockHashes: make(map[common.Hash]struct{}),
SizeFn: sizeFn,
}
}
func (upq *PayloadsQueue) Len() int { func (upq *PayloadsQueue) Len() int {
return len(upq.pq) return len(upq.pq)
} }
...@@ -100,8 +112,8 @@ func (upq *PayloadsQueue) Push(p *eth.ExecutionPayload) error { ...@@ -100,8 +112,8 @@ func (upq *PayloadsQueue) Push(p *eth.ExecutionPayload) error {
if p == nil { if p == nil {
return errors.New("cannot add nil payload") return errors.New("cannot add nil payload")
} }
if upq.blockNos[p.ID().Number] { if _, ok := upq.blockHashes[p.BlockHash]; ok {
return errors.New("cannot add duplicate payload") return fmt.Errorf("cannot add duplicate payload %s", p.ID())
} }
size := upq.SizeFn(p) size := upq.SizeFn(p)
if size > upq.MaxSize { if size > upq.MaxSize {
...@@ -115,7 +127,7 @@ func (upq *PayloadsQueue) Push(p *eth.ExecutionPayload) error { ...@@ -115,7 +127,7 @@ func (upq *PayloadsQueue) Push(p *eth.ExecutionPayload) error {
for upq.currentSize > upq.MaxSize { for upq.currentSize > upq.MaxSize {
upq.Pop() upq.Pop()
} }
upq.blockNos[p.ID().Number] = true upq.blockHashes[p.BlockHash] = struct{}{}
return nil return nil
} }
...@@ -137,7 +149,7 @@ func (upq *PayloadsQueue) Pop() *eth.ExecutionPayload { ...@@ -137,7 +149,7 @@ func (upq *PayloadsQueue) Pop() *eth.ExecutionPayload {
} }
ps := heap.Pop(&upq.pq).(payloadAndSize) // nosemgrep ps := heap.Pop(&upq.pq).(payloadAndSize) // nosemgrep
upq.currentSize -= ps.size upq.currentSize -= ps.size
// remove the key from the blockNos map // remove the key from the block hashes map
delete(upq.blockNos, ps.payload.ID().Number) delete(upq.blockHashes, ps.payload.BlockHash)
return ps.payload return ps.payload
} }
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"container/heap" "container/heap"
"testing" "testing"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
...@@ -74,20 +75,17 @@ func TestPayloadMemSize(t *testing.T) { ...@@ -74,20 +75,17 @@ func TestPayloadMemSize(t *testing.T) {
} }
func TestPayloadsQueue(t *testing.T) { func TestPayloadsQueue(t *testing.T) {
pq := PayloadsQueue{ pq := NewPayloadsQueue(payloadMemFixedCost*3, payloadMemSize)
MaxSize: payloadMemFixedCost * 3,
SizeFn: payloadMemSize,
blockNos: make(map[uint64]bool),
}
require.Equal(t, 0, pq.Len()) require.Equal(t, 0, pq.Len())
require.Equal(t, (*eth.ExecutionPayload)(nil), pq.Peek()) require.Equal(t, (*eth.ExecutionPayload)(nil), pq.Peek())
require.Equal(t, (*eth.ExecutionPayload)(nil), pq.Pop()) require.Equal(t, (*eth.ExecutionPayload)(nil), pq.Pop())
a := &eth.ExecutionPayload{BlockNumber: 3} a := &eth.ExecutionPayload{BlockNumber: 3, BlockHash: common.Hash{3}}
b := &eth.ExecutionPayload{BlockNumber: 4} b := &eth.ExecutionPayload{BlockNumber: 4, BlockHash: common.Hash{4}}
c := &eth.ExecutionPayload{BlockNumber: 5} c := &eth.ExecutionPayload{BlockNumber: 5, BlockHash: common.Hash{5}}
d := &eth.ExecutionPayload{BlockNumber: 6} d := &eth.ExecutionPayload{BlockNumber: 6, BlockHash: common.Hash{6}}
bAlt := &eth.ExecutionPayload{BlockNumber: 4} bAlt := &eth.ExecutionPayload{BlockNumber: 4, BlockHash: common.Hash{0xff}}
bDup := &eth.ExecutionPayload{BlockNumber: 4, BlockHash: common.Hash{4}}
require.NoError(t, pq.Push(b)) require.NoError(t, pq.Push(b))
require.Equal(t, pq.Len(), 1) require.Equal(t, pq.Len(), 1)
require.Equal(t, pq.Peek(), b) require.Equal(t, pq.Peek(), b)
...@@ -130,7 +128,9 @@ func TestPayloadsQueue(t *testing.T) { ...@@ -130,7 +128,9 @@ func TestPayloadsQueue(t *testing.T) {
require.Equal(t, pq.Peek(), a) require.Equal(t, pq.Peek(), a)
// No duplicates allowed // No duplicates allowed
require.Error(t, pq.Push(bAlt)) require.Error(t, pq.Push(bDup))
// But reorg data allowed
require.NoError(t, pq.Push(bAlt))
require.NoError(t, pq.Push(d)) require.NoError(t, pq.Push(d))
require.Equal(t, pq.Len(), 3) require.Equal(t, pq.Len(), 3)
......
package derive
import (
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require"
)
var (
// ABI encoding helpers
dynBytes, _ = abi.NewType("bytes", "", nil)
address, _ = abi.NewType("address", "", nil)
uint256T, _ = abi.NewType("uint256", "", nil)
addressArgs = abi.Arguments{
{Type: address},
}
bytesArgs = abi.Arguments{
{Type: dynBytes},
}
twoUint256 = abi.Arguments{
{Type: uint256T},
{Type: uint256T},
}
oneUint256 = abi.Arguments{
{Type: uint256T},
}
)
// TestProcessSystemConfigUpdateLogEvent tests the parsing of an event and mutating the
// SystemConfig. The hook will build the ABI encoded data dynamically. All tests create
// a new SystemConfig and apply a log against it and then assert that the mutated system
// config is equal to the defined system config in the test.
func TestProcessSystemConfigUpdateLogEvent(t *testing.T) {
tests := []struct {
name string
log *types.Log
config eth.SystemConfig
hook func(*testing.T, *types.Log) *types.Log
err bool
}{
{
// The log data is ignored by consensus and no modifications to the
// system config occur.
name: "SystemConfigUpdateUnsafeBlockSigner",
log: &types.Log{
Topics: []common.Hash{
ConfigUpdateEventABIHash,
ConfigUpdateEventVersion0,
SystemConfigUpdateUnsafeBlockSigner,
},
},
hook: func(t *testing.T, log *types.Log) *types.Log {
addr := common.Address{}
data, err := addressArgs.Pack(&addr)
require.NoError(t, err)
log.Data = data
return log
},
config: eth.SystemConfig{},
err: false,
},
{
// The batcher address should be updated.
name: "SystemConfigUpdateBatcher",
log: &types.Log{
Topics: []common.Hash{
ConfigUpdateEventABIHash,
ConfigUpdateEventVersion0,
SystemConfigUpdateBatcher,
},
},
hook: func(t *testing.T, log *types.Log) *types.Log {
addr := common.Address{19: 0xaa}
addrData, err := addressArgs.Pack(&addr)
require.NoError(t, err)
data, err := bytesArgs.Pack(addrData)
require.NoError(t, err)
log.Data = data
return log
},
config: eth.SystemConfig{
BatcherAddr: common.Address{19: 0xaa},
},
err: false,
},
{
// The overhead and the scalar should be updated.
name: "SystemConfigUpdateGasConfig",
log: &types.Log{
Topics: []common.Hash{
ConfigUpdateEventABIHash,
ConfigUpdateEventVersion0,
SystemConfigUpdateGasConfig,
},
},
hook: func(t *testing.T, log *types.Log) *types.Log {
overhead := big.NewInt(0xff)
scalar := big.NewInt(0xaa)
numberData, err := twoUint256.Pack(overhead, scalar)
require.NoError(t, err)
data, err := bytesArgs.Pack(numberData)
require.NoError(t, err)
log.Data = data
return log
},
config: eth.SystemConfig{
Overhead: eth.Bytes32{31: 0xff},
Scalar: eth.Bytes32{31: 0xaa},
},
err: false,
},
{
// The gas limit should be updated.
name: "SystemConfigUpdateGasLimit",
log: &types.Log{
Topics: []common.Hash{
ConfigUpdateEventABIHash,
ConfigUpdateEventVersion0,
SystemConfigUpdateGasLimit,
},
},
hook: func(t *testing.T, log *types.Log) *types.Log {
gasLimit := big.NewInt(0xbb)
numberData, err := oneUint256.Pack(gasLimit)
require.NoError(t, err)
data, err := bytesArgs.Pack(numberData)
require.NoError(t, err)
log.Data = data
return log
},
config: eth.SystemConfig{
GasLimit: 0xbb,
},
err: false,
},
{
name: "SystemConfigOneTopic",
log: &types.Log{
Topics: []common.Hash{
ConfigUpdateEventABIHash,
},
},
hook: func(t *testing.T, log *types.Log) *types.Log {
return log
},
config: eth.SystemConfig{},
err: true,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
config := eth.SystemConfig{}
err := ProcessSystemConfigUpdateLogEvent(&config, test.hook(t, test.log))
if test.err {
require.Error(t, err)
} else {
require.NoError(t, err)
}
require.Equal(t, config, test.config)
})
}
}
...@@ -16,4 +16,8 @@ type Config struct { ...@@ -16,4 +16,8 @@ type Config struct {
// SequencerStopped is false when the driver should sequence new blocks. // SequencerStopped is false when the driver should sequence new blocks.
SequencerStopped bool `json:"sequencer_stopped"` SequencerStopped bool `json:"sequencer_stopped"`
// SequencerMaxSafeLag is the maximum number of L2 blocks for restricting the distance between L2 safe and unsafe.
// Disabled if 0.
SequencerMaxSafeLag uint64 `json:"sequencer_max_safe_lag"`
} }
...@@ -10,7 +10,6 @@ import ( ...@@ -10,7 +10,6 @@ import (
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-node/sources"
) )
type Metrics interface { type Metrics interface {
...@@ -82,8 +81,19 @@ type Network interface { ...@@ -82,8 +81,19 @@ type Network interface {
PublishL2Payload(ctx context.Context, payload *eth.ExecutionPayload) error PublishL2Payload(ctx context.Context, payload *eth.ExecutionPayload) error
} }
type AltSync interface {
// RequestL2Range informs the sync source that the given range of L2 blocks is missing,
// and should be retrieved from any available alternative syncing source.
// The start of the range is inclusive, the end is exclusive.
// The sync results should be returned back to the driver via the OnUnsafeL2Payload(ctx, payload) method.
// The latest requested range should always take priority over previous requests.
// There may be overlaps in requested ranges.
// An error may be returned if the scheduling fails immediately, e.g. a context timeout.
RequestL2Range(ctx context.Context, start, end uint64) error
}
// NewDriver composes an events handler that tracks L1 state, triggers L2 derivation, and optionally sequences new L2 blocks. // NewDriver composes an events handler that tracks L1 state, triggers L2 derivation, and optionally sequences new L2 blocks.
func NewDriver(driverCfg *Config, cfg *rollup.Config, l2 L2Chain, l1 L1Chain, syncClient *sources.SyncClient, network Network, log log.Logger, snapshotLog log.Logger, metrics Metrics) *Driver { func NewDriver(driverCfg *Config, cfg *rollup.Config, l2 L2Chain, l1 L1Chain, altSync AltSync, network Network, log log.Logger, snapshotLog log.Logger, metrics Metrics) *Driver {
l1State := NewL1State(log, metrics) l1State := NewL1State(log, metrics)
sequencerConfDepth := NewConfDepth(driverCfg.SequencerConfDepth, l1State.L1Head, l1) sequencerConfDepth := NewConfDepth(driverCfg.SequencerConfDepth, l1State.L1Head, l1)
findL1Origin := NewL1OriginSelector(log, cfg, sequencerConfDepth) findL1Origin := NewL1OriginSelector(log, cfg, sequencerConfDepth)
...@@ -115,6 +125,6 @@ func NewDriver(driverCfg *Config, cfg *rollup.Config, l2 L2Chain, l1 L1Chain, sy ...@@ -115,6 +125,6 @@ func NewDriver(driverCfg *Config, cfg *rollup.Config, l2 L2Chain, l1 L1Chain, sy
l1SafeSig: make(chan eth.L1BlockRef, 10), l1SafeSig: make(chan eth.L1BlockRef, 10),
l1FinalizedSig: make(chan eth.L1BlockRef, 10), l1FinalizedSig: make(chan eth.L1BlockRef, 10),
unsafeL2Payloads: make(chan *eth.ExecutionPayload, 10), unsafeL2Payloads: make(chan *eth.ExecutionPayload, 10),
L2SyncCl: syncClient, altSync: altSync,
} }
} }
...@@ -16,7 +16,6 @@ import ( ...@@ -16,7 +16,6 @@ import (
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-node/sources"
"github.com/ethereum-optimism/optimism/op-service/backoff" "github.com/ethereum-optimism/optimism/op-service/backoff"
) )
...@@ -64,8 +63,8 @@ type Driver struct { ...@@ -64,8 +63,8 @@ type Driver struct {
l1SafeSig chan eth.L1BlockRef l1SafeSig chan eth.L1BlockRef
l1FinalizedSig chan eth.L1BlockRef l1FinalizedSig chan eth.L1BlockRef
// Backup unsafe sync client // Interface to signal the L2 block range to sync.
L2SyncCl *sources.SyncClient altSync AltSync
// L2 Signals: // L2 Signals:
...@@ -200,11 +199,12 @@ func (s *Driver) eventLoop() { ...@@ -200,11 +199,12 @@ func (s *Driver) eventLoop() {
sequencerTimer.Reset(delay) sequencerTimer.Reset(delay)
} }
// Create a ticker to check if there is a gap in the engine queue every 15 seconds // Create a ticker to check if there is a gap in the engine queue. Whenever
// If there is, we send requests to the backup RPC to retrieve the missing payloads // there is, we send requests to sync source to retrieve the missing payloads.
// and add them to the unsafe queue. syncCheckInterval := time.Duration(s.config.BlockTime) * time.Second * 2
altSyncTicker := time.NewTicker(15 * time.Second) altSyncTicker := time.NewTicker(syncCheckInterval)
defer altSyncTicker.Stop() defer altSyncTicker.Stop()
lastUnsafeL2 := s.derivation.UnsafeL2Head()
for { for {
// If we are sequencing, and the L1 state is ready, update the trigger for the next sequencer action. // If we are sequencing, and the L1 state is ready, update the trigger for the next sequencer action.
...@@ -212,14 +212,35 @@ func (s *Driver) eventLoop() { ...@@ -212,14 +212,35 @@ func (s *Driver) eventLoop() {
// And avoid sequencing if the derivation pipeline indicates the engine is not ready. // And avoid sequencing if the derivation pipeline indicates the engine is not ready.
if s.driverConfig.SequencerEnabled && !s.driverConfig.SequencerStopped && if s.driverConfig.SequencerEnabled && !s.driverConfig.SequencerStopped &&
s.l1State.L1Head() != (eth.L1BlockRef{}) && s.derivation.EngineReady() { s.l1State.L1Head() != (eth.L1BlockRef{}) && s.derivation.EngineReady() {
if s.driverConfig.SequencerMaxSafeLag > 0 && s.derivation.SafeL2Head().Number+s.driverConfig.SequencerMaxSafeLag <= s.derivation.UnsafeL2Head().Number {
// If the safe head has fallen behind by a significant number of blocks, delay creating new blocks
// until the safe lag is below SequencerMaxSafeLag.
if sequencerCh != nil {
s.log.Warn(
"Delay creating new block since safe lag exceeds limit",
"safe_l2", s.derivation.SafeL2Head(),
"unsafe_l2", s.derivation.UnsafeL2Head(),
)
sequencerCh = nil
}
} else if s.sequencer.BuildingOnto().ID() != s.derivation.UnsafeL2Head().ID() {
// If we are sequencing, and the L1 state is ready, update the trigger for the next sequencer action.
// This may adjust at any time based on fork-choice changes or previous errors.
//
// update sequencer time if the head changed // update sequencer time if the head changed
if s.sequencer.BuildingOnto().ID() != s.derivation.UnsafeL2Head().ID() {
planSequencerAction() planSequencerAction()
} }
} else { } else {
sequencerCh = nil sequencerCh = nil
} }
// If the engine is not ready, or if the L2 head is actively changing, then reset the alt-sync:
// there is no need to request L2 blocks when we are syncing already.
if head := s.derivation.UnsafeL2Head(); head != lastUnsafeL2 || !s.derivation.EngineReady() {
lastUnsafeL2 = head
altSyncTicker.Reset(syncCheckInterval)
}
select { select {
case <-sequencerCh: case <-sequencerCh:
payload, err := s.sequencer.RunNextSequencerAction(ctx) payload, err := s.sequencer.RunNextSequencerAction(ctx)
...@@ -237,10 +258,12 @@ func (s *Driver) eventLoop() { ...@@ -237,10 +258,12 @@ func (s *Driver) eventLoop() {
} }
planSequencerAction() // schedule the next sequencer action to keep the sequencing looping planSequencerAction() // schedule the next sequencer action to keep the sequencing looping
case <-altSyncTicker.C: case <-altSyncTicker.C:
// Check if there is a gap in the current unsafe payload queue. If there is, attempt to fetch // Check if there is a gap in the current unsafe payload queue.
// missing payloads from the backup RPC (if it is configured). ctx, cancel := context.WithTimeout(ctx, time.Second*2)
if s.L2SyncCl != nil { err := s.checkForGapInUnsafeQueue(ctx)
s.checkForGapInUnsafeQueue(ctx) cancel()
if err != nil {
s.log.Warn("failed to check for unsafe L2 blocks to sync", "err", err)
} }
case payload := <-s.unsafeL2Payloads: case payload := <-s.unsafeL2Payloads:
s.snapshot("New unsafe payload") s.snapshot("New unsafe payload")
...@@ -462,35 +485,29 @@ type hashAndErrorChannel struct { ...@@ -462,35 +485,29 @@ type hashAndErrorChannel struct {
err chan error err chan error
} }
// checkForGapInUnsafeQueue checks if there is a gap in the unsafe queue and attempts to retrieve the missing payloads from the backup RPC. // checkForGapInUnsafeQueue checks if there is a gap in the unsafe queue and attempts to retrieve the missing payloads from an alt-sync method.
// WARNING: The sync client's attempt to retrieve the missing payloads is not guaranteed to succeed, and it will fail silently (besides // WARNING: This is only an outgoing signal, the blocks are not guaranteed to be retrieved.
// emitting warning logs) if the requests fail. // Results are received through OnUnsafeL2Payload.
func (s *Driver) checkForGapInUnsafeQueue(ctx context.Context) { func (s *Driver) checkForGapInUnsafeQueue(ctx context.Context) error {
// subtract genesis time from wall clock to get the time elapsed since genesis, and then divide that // subtract genesis time from wall clock to get the time elapsed since genesis, and then divide that
// difference by the block time to get the expected L2 block number at the current time. If the // difference by the block time to get the expected L2 block number at the current time. If the
// unsafe head does not have this block number, then there is a gap in the queue. // unsafe head does not have this block number, then there is a gap in the queue.
wallClock := uint64(time.Now().Unix()) wallClock := uint64(time.Now().Unix())
genesisTimestamp := s.config.Genesis.L2Time genesisTimestamp := s.config.Genesis.L2Time
if wallClock < genesisTimestamp {
s.log.Debug("nothing to sync, did not reach genesis L2 time yet", "genesis", genesisTimestamp)
return nil
}
wallClockGenesisDiff := wallClock - genesisTimestamp wallClockGenesisDiff := wallClock - genesisTimestamp
expectedL2Block := wallClockGenesisDiff / s.config.BlockTime // Note: round down, we should not request blocks into the future.
blocksSinceGenesis := wallClockGenesisDiff / s.config.BlockTime
expectedL2Block := s.config.Genesis.L2.Number + blocksSinceGenesis
start, end := s.derivation.GetUnsafeQueueGap(expectedL2Block) start, end := s.derivation.GetUnsafeQueueGap(expectedL2Block)
size := end - start
// Check if there is a gap between the unsafe head and the expected L2 block number at the current time. // Check if there is a gap between the unsafe head and the expected L2 block number at the current time.
if size > 0 { if end > start {
s.log.Warn("Gap in payload queue tip and expected unsafe chain detected", "start", start, "end", end, "size", size) s.log.Debug("requesting missing unsafe L2 block range", "start", start, "end", end, "size", end-start)
s.log.Info("Attempting to fetch missing payloads from backup RPC", "start", start, "end", end, "size", size) return s.altSync.RequestL2Range(ctx, start, end)
// Attempt to fetch the missing payloads from the backup unsafe sync RPC concurrently.
// Concurrent requests are safe here due to the engine queue being a priority queue.
for blockNumber := start; blockNumber <= end; blockNumber++ {
select {
case s.L2SyncCl.FetchUnsafeBlock <- blockNumber:
// Do nothing- the block number was successfully sent into the channel
default:
return // If the channel is full, return and wait for the next iteration of the event loop
}
}
} }
return nil
} }
...@@ -109,6 +109,9 @@ func FindL2Heads(ctx context.Context, cfg *rollup.Config, l1 L1Chain, l2 L2Chain ...@@ -109,6 +109,9 @@ func FindL2Heads(ctx context.Context, cfg *rollup.Config, l1 L1Chain, l2 L2Chain
return nil, fmt.Errorf("failed to fetch current L2 forkchoice state: %w", err) return nil, fmt.Errorf("failed to fetch current L2 forkchoice state: %w", err)
} }
lgr.Info("Loaded current L2 heads", "unsafe", result.Unsafe, "safe", result.Safe, "finalized", result.Finalized,
"unsafe_origin", result.Unsafe.L1Origin, "unsafe_origin", result.Safe.L1Origin)
// Remember original unsafe block to determine reorg depth // Remember original unsafe block to determine reorg depth
prevUnsafe := result.Unsafe prevUnsafe := result.Unsafe
...@@ -134,6 +137,7 @@ func FindL2Heads(ctx context.Context, cfg *rollup.Config, l1 L1Chain, l2 L2Chain ...@@ -134,6 +137,7 @@ func FindL2Heads(ctx context.Context, cfg *rollup.Config, l1 L1Chain, l2 L2Chain
// Exit, find-sync start should start over, to move to an available L1 chain with block-by-number / not-found case. // Exit, find-sync start should start over, to move to an available L1 chain with block-by-number / not-found case.
return nil, fmt.Errorf("failed to retrieve L1 block: %w", err) return nil, fmt.Errorf("failed to retrieve L1 block: %w", err)
} }
lgr.Info("Walking back L1Block by hash", "curr", l1Block, "next", b, "l2block", n)
l1Block = b l1Block = b
ahead = false ahead = false
} else if l1Block == (eth.L1BlockRef{}) || n.L1Origin.Hash != l1Block.Hash { } else if l1Block == (eth.L1BlockRef{}) || n.L1Origin.Hash != l1Block.Hash {
...@@ -145,9 +149,10 @@ func FindL2Heads(ctx context.Context, cfg *rollup.Config, l1 L1Chain, l2 L2Chain ...@@ -145,9 +149,10 @@ func FindL2Heads(ctx context.Context, cfg *rollup.Config, l1 L1Chain, l2 L2Chain
} }
l1Block = b l1Block = b
ahead = notFound ahead = notFound
lgr.Info("Walking back L1Block by number", "curr", l1Block, "next", b, "l2block", n)
} }
lgr.Trace("walking sync start", "number", n.Number) lgr.Trace("walking sync start", "l2block", n)
// Don't walk past genesis. If we were at the L2 genesis, but could not find its L1 origin, // Don't walk past genesis. If we were at the L2 genesis, but could not find its L1 origin,
// the L2 chain is building on the wrong L1 branch. // the L2 chain is building on the wrong L1 branch.
...@@ -201,6 +206,8 @@ func FindL2Heads(ctx context.Context, cfg *rollup.Config, l1 L1Chain, l2 L2Chain ...@@ -201,6 +206,8 @@ func FindL2Heads(ctx context.Context, cfg *rollup.Config, l1 L1Chain, l2 L2Chain
// Don't traverse further than the finalized head to find a safe head // Don't traverse further than the finalized head to find a safe head
if n.Number == result.Finalized.Number { if n.Number == result.Finalized.Number {
lgr.Info("Hit finalized L2 head, returning immediately", "unsafe", result.Unsafe, "safe", result.Safe,
"finalized", result.Finalized, "unsafe_origin", result.Unsafe.L1Origin, "unsafe_origin", result.Safe.L1Origin)
result.Safe = n result.Safe = n
return result, nil return result, nil
} }
......
...@@ -98,6 +98,9 @@ func NewL1EndpointConfig(ctx *cli.Context) *node.L1EndpointConfig { ...@@ -98,6 +98,9 @@ func NewL1EndpointConfig(ctx *cli.Context) *node.L1EndpointConfig {
L1NodeAddr: ctx.GlobalString(flags.L1NodeAddr.Name), L1NodeAddr: ctx.GlobalString(flags.L1NodeAddr.Name),
L1TrustRPC: ctx.GlobalBool(flags.L1TrustRPC.Name), L1TrustRPC: ctx.GlobalBool(flags.L1TrustRPC.Name),
L1RPCKind: sources.RPCProviderKind(strings.ToLower(ctx.GlobalString(flags.L1RPCProviderKind.Name))), L1RPCKind: sources.RPCProviderKind(strings.ToLower(ctx.GlobalString(flags.L1RPCProviderKind.Name))),
RateLimit: ctx.GlobalFloat64(flags.L1RPCRateLimit.Name),
BatchSize: ctx.GlobalInt(flags.L1RPCMaxBatchSize.Name),
HttpPollInterval: ctx.Duration(flags.L1HTTPPollInterval.Name),
} }
} }
...@@ -136,6 +139,7 @@ func NewL2EndpointConfig(ctx *cli.Context, log log.Logger) (*node.L2EndpointConf ...@@ -136,6 +139,7 @@ func NewL2EndpointConfig(ctx *cli.Context, log log.Logger) (*node.L2EndpointConf
func NewL2SyncEndpointConfig(ctx *cli.Context) *node.L2SyncEndpointConfig { func NewL2SyncEndpointConfig(ctx *cli.Context) *node.L2SyncEndpointConfig {
return &node.L2SyncEndpointConfig{ return &node.L2SyncEndpointConfig{
L2NodeAddr: ctx.GlobalString(flags.BackupL2UnsafeSyncRPC.Name), L2NodeAddr: ctx.GlobalString(flags.BackupL2UnsafeSyncRPC.Name),
TrustRPC: ctx.GlobalBool(flags.BackupL2UnsafeSyncRPCTrustRPC.Name),
} }
} }
...@@ -145,6 +149,7 @@ func NewDriverConfig(ctx *cli.Context) *driver.Config { ...@@ -145,6 +149,7 @@ func NewDriverConfig(ctx *cli.Context) *driver.Config {
SequencerConfDepth: ctx.GlobalUint64(flags.SequencerL1Confs.Name), SequencerConfDepth: ctx.GlobalUint64(flags.SequencerL1Confs.Name),
SequencerEnabled: ctx.GlobalBool(flags.SequencerEnabledFlag.Name), SequencerEnabled: ctx.GlobalBool(flags.SequencerEnabledFlag.Name),
SequencerStopped: ctx.GlobalBool(flags.SequencerStoppedFlag.Name), SequencerStopped: ctx.GlobalBool(flags.SequencerStoppedFlag.Name),
SequencerMaxSafeLag: ctx.GlobalUint64(flags.SequencerMaxSafeLagFlag.Name),
} }
} }
......
...@@ -13,6 +13,7 @@ import ( ...@@ -13,6 +13,7 @@ import (
"context" "context"
"fmt" "fmt"
"math/big" "math/big"
"time"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -56,6 +57,11 @@ type EthClientConfig struct { ...@@ -56,6 +57,11 @@ type EthClientConfig struct {
// RPCProviderKind is a hint at what type of RPC provider we are dealing with // RPCProviderKind is a hint at what type of RPC provider we are dealing with
RPCProviderKind RPCProviderKind RPCProviderKind RPCProviderKind
// Method reset duration defines how long we stick to available RPC methods,
// till we re-attempt the user-preferred methods.
// If this is 0 then the client does not fall back to less optimal but available methods.
MethodResetDuration time.Duration
} }
func (c *EthClientConfig) Check() error { func (c *EthClientConfig) Check() error {
...@@ -118,9 +124,25 @@ type EthClient struct { ...@@ -118,9 +124,25 @@ type EthClient struct {
// This may be modified concurrently, but we don't lock since it's a single // This may be modified concurrently, but we don't lock since it's a single
// uint64 that's not critical (fine to miss or mix up a modification) // uint64 that's not critical (fine to miss or mix up a modification)
availableReceiptMethods ReceiptsFetchingMethod availableReceiptMethods ReceiptsFetchingMethod
// lastMethodsReset tracks when availableReceiptMethods was last reset.
// When receipt-fetching fails it falls back to available methods,
// but periodically it will try to reset to the preferred optimal methods.
lastMethodsReset time.Time
// methodResetDuration defines how long we take till we reset lastMethodsReset
methodResetDuration time.Duration
} }
func (s *EthClient) PickReceiptsMethod(txCount uint64) ReceiptsFetchingMethod { func (s *EthClient) PickReceiptsMethod(txCount uint64) ReceiptsFetchingMethod {
if now := time.Now(); now.Sub(s.lastMethodsReset) > s.methodResetDuration {
m := AvailableReceiptsFetchingMethods(s.provKind)
if s.availableReceiptMethods != m {
s.log.Warn("resetting back RPC preferences, please review RPC provider kind setting", "kind", s.provKind.String())
}
s.availableReceiptMethods = m
s.lastMethodsReset = now
}
return PickBestReceiptsFetchingMethod(s.provKind, s.availableReceiptMethods, txCount) return PickBestReceiptsFetchingMethod(s.provKind, s.availableReceiptMethods, txCount)
} }
...@@ -128,7 +150,7 @@ func (s *EthClient) OnReceiptsMethodErr(m ReceiptsFetchingMethod, err error) { ...@@ -128,7 +150,7 @@ func (s *EthClient) OnReceiptsMethodErr(m ReceiptsFetchingMethod, err error) {
if unusableMethod(err) { if unusableMethod(err) {
// clear the bit of the method that errored // clear the bit of the method that errored
s.availableReceiptMethods &^= m s.availableReceiptMethods &^= m
s.log.Warn("failed to use selected RPC method for receipt fetching, falling back to alternatives", s.log.Warn("failed to use selected RPC method for receipt fetching, temporarily falling back to alternatives",
"provider_kind", s.provKind, "failed_method", m, "fallback", s.availableReceiptMethods, "err", err) "provider_kind", s.provKind, "failed_method", m, "fallback", s.availableReceiptMethods, "err", err)
} else { } else {
s.log.Debug("failed to use selected RPC method for receipt fetching, but method does appear to be available, so we continue to use it", s.log.Debug("failed to use selected RPC method for receipt fetching, but method does appear to be available, so we continue to use it",
...@@ -155,6 +177,8 @@ func NewEthClient(client client.RPC, log log.Logger, metrics caching.Metrics, co ...@@ -155,6 +177,8 @@ func NewEthClient(client client.RPC, log log.Logger, metrics caching.Metrics, co
headersCache: caching.NewLRUCache(metrics, "headers", config.HeadersCacheSize), headersCache: caching.NewLRUCache(metrics, "headers", config.HeadersCacheSize),
payloadsCache: caching.NewLRUCache(metrics, "payloads", config.PayloadsCacheSize), payloadsCache: caching.NewLRUCache(metrics, "payloads", config.PayloadsCacheSize),
availableReceiptMethods: AvailableReceiptsFetchingMethods(config.RPCProviderKind), availableReceiptMethods: AvailableReceiptsFetchingMethods(config.RPCProviderKind),
lastMethodsReset: time.Now(),
methodResetDuration: config.MethodResetDuration,
}, nil }, nil
} }
...@@ -165,9 +189,39 @@ func (s *EthClient) SubscribeNewHead(ctx context.Context, ch chan<- *types.Heade ...@@ -165,9 +189,39 @@ func (s *EthClient) SubscribeNewHead(ctx context.Context, ch chan<- *types.Heade
return s.client.EthSubscribe(ctx, ch, "newHeads") return s.client.EthSubscribe(ctx, ch, "newHeads")
} }
func (s *EthClient) headerCall(ctx context.Context, method string, id any) (*HeaderInfo, error) { // rpcBlockID is an internal type to enforce header and block call results match the requested identifier
type rpcBlockID interface {
// Arg translates the object into an RPC argument
Arg() any
// CheckID verifies a block/header result matches the requested block identifier
CheckID(id eth.BlockID) error
}
// hashID implements rpcBlockID for safe block-by-hash fetching
type hashID common.Hash
func (h hashID) Arg() any { return common.Hash(h) }
func (h hashID) CheckID(id eth.BlockID) error {
if common.Hash(h) != id.Hash {
return fmt.Errorf("expected block hash %s but got block %s", common.Hash(h), id)
}
return nil
}
// numberID implements rpcBlockID for safe block-by-number fetching
type numberID uint64
func (n numberID) Arg() any { return hexutil.EncodeUint64(uint64(n)) }
func (n numberID) CheckID(id eth.BlockID) error {
if uint64(n) != id.Number {
return fmt.Errorf("expected block number %d but got block %s", uint64(n), id)
}
return nil
}
func (s *EthClient) headerCall(ctx context.Context, method string, id rpcBlockID) (*HeaderInfo, error) {
var header *rpcHeader var header *rpcHeader
err := s.client.CallContext(ctx, &header, method, id, false) // headers are just blocks without txs err := s.client.CallContext(ctx, &header, method, id.Arg(), false) // headers are just blocks without txs
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -178,13 +232,16 @@ func (s *EthClient) headerCall(ctx context.Context, method string, id any) (*Hea ...@@ -178,13 +232,16 @@ func (s *EthClient) headerCall(ctx context.Context, method string, id any) (*Hea
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := id.CheckID(eth.ToBlockID(info)); err != nil {
return nil, fmt.Errorf("fetched block header does not match requested ID: %w", err)
}
s.headersCache.Add(info.Hash(), info) s.headersCache.Add(info.Hash(), info)
return info, nil return info, nil
} }
func (s *EthClient) blockCall(ctx context.Context, method string, id any) (*HeaderInfo, types.Transactions, error) { func (s *EthClient) blockCall(ctx context.Context, method string, id rpcBlockID) (*HeaderInfo, types.Transactions, error) {
var block *rpcBlock var block *rpcBlock
err := s.client.CallContext(ctx, &block, method, id, true) err := s.client.CallContext(ctx, &block, method, id.Arg(), true)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
...@@ -195,14 +252,17 @@ func (s *EthClient) blockCall(ctx context.Context, method string, id any) (*Head ...@@ -195,14 +252,17 @@ func (s *EthClient) blockCall(ctx context.Context, method string, id any) (*Head
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
if err := id.CheckID(eth.ToBlockID(info)); err != nil {
return nil, nil, fmt.Errorf("fetched block data does not match requested ID: %w", err)
}
s.headersCache.Add(info.Hash(), info) s.headersCache.Add(info.Hash(), info)
s.transactionsCache.Add(info.Hash(), txs) s.transactionsCache.Add(info.Hash(), txs)
return info, txs, nil return info, txs, nil
} }
func (s *EthClient) payloadCall(ctx context.Context, method string, id any) (*eth.ExecutionPayload, error) { func (s *EthClient) payloadCall(ctx context.Context, method string, id rpcBlockID) (*eth.ExecutionPayload, error) {
var block *rpcBlock var block *rpcBlock
err := s.client.CallContext(ctx, &block, method, id, true) err := s.client.CallContext(ctx, &block, method, id.Arg(), true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -213,6 +273,9 @@ func (s *EthClient) payloadCall(ctx context.Context, method string, id any) (*et ...@@ -213,6 +273,9 @@ func (s *EthClient) payloadCall(ctx context.Context, method string, id any) (*et
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := id.CheckID(payload.ID()); err != nil {
return nil, fmt.Errorf("fetched payload does not match requested ID: %w", err)
}
s.payloadsCache.Add(payload.BlockHash, payload) s.payloadsCache.Add(payload.BlockHash, payload)
return payload, nil return payload, nil
} }
...@@ -231,17 +294,17 @@ func (s *EthClient) InfoByHash(ctx context.Context, hash common.Hash) (eth.Block ...@@ -231,17 +294,17 @@ func (s *EthClient) InfoByHash(ctx context.Context, hash common.Hash) (eth.Block
if header, ok := s.headersCache.Get(hash); ok { if header, ok := s.headersCache.Get(hash); ok {
return header.(*HeaderInfo), nil return header.(*HeaderInfo), nil
} }
return s.headerCall(ctx, "eth_getBlockByHash", hash) return s.headerCall(ctx, "eth_getBlockByHash", hashID(hash))
} }
func (s *EthClient) InfoByNumber(ctx context.Context, number uint64) (eth.BlockInfo, error) { func (s *EthClient) InfoByNumber(ctx context.Context, number uint64) (eth.BlockInfo, error) {
// can't hit the cache when querying by number due to reorgs. // can't hit the cache when querying by number due to reorgs.
return s.headerCall(ctx, "eth_getBlockByNumber", hexutil.EncodeUint64(number)) return s.headerCall(ctx, "eth_getBlockByNumber", numberID(number))
} }
func (s *EthClient) InfoByLabel(ctx context.Context, label eth.BlockLabel) (eth.BlockInfo, error) { func (s *EthClient) InfoByLabel(ctx context.Context, label eth.BlockLabel) (eth.BlockInfo, error) {
// can't hit the cache when querying the head due to reorgs / changes. // can't hit the cache when querying the head due to reorgs / changes.
return s.headerCall(ctx, "eth_getBlockByNumber", string(label)) return s.headerCall(ctx, "eth_getBlockByNumber", label)
} }
func (s *EthClient) InfoAndTxsByHash(ctx context.Context, hash common.Hash) (eth.BlockInfo, types.Transactions, error) { func (s *EthClient) InfoAndTxsByHash(ctx context.Context, hash common.Hash) (eth.BlockInfo, types.Transactions, error) {
...@@ -250,32 +313,32 @@ func (s *EthClient) InfoAndTxsByHash(ctx context.Context, hash common.Hash) (eth ...@@ -250,32 +313,32 @@ func (s *EthClient) InfoAndTxsByHash(ctx context.Context, hash common.Hash) (eth
return header.(*HeaderInfo), txs.(types.Transactions), nil return header.(*HeaderInfo), txs.(types.Transactions), nil
} }
} }
return s.blockCall(ctx, "eth_getBlockByHash", hash) return s.blockCall(ctx, "eth_getBlockByHash", hashID(hash))
} }
func (s *EthClient) InfoAndTxsByNumber(ctx context.Context, number uint64) (eth.BlockInfo, types.Transactions, error) { func (s *EthClient) InfoAndTxsByNumber(ctx context.Context, number uint64) (eth.BlockInfo, types.Transactions, error) {
// can't hit the cache when querying by number due to reorgs. // can't hit the cache when querying by number due to reorgs.
return s.blockCall(ctx, "eth_getBlockByNumber", hexutil.EncodeUint64(number)) return s.blockCall(ctx, "eth_getBlockByNumber", numberID(number))
} }
func (s *EthClient) InfoAndTxsByLabel(ctx context.Context, label eth.BlockLabel) (eth.BlockInfo, types.Transactions, error) { func (s *EthClient) InfoAndTxsByLabel(ctx context.Context, label eth.BlockLabel) (eth.BlockInfo, types.Transactions, error) {
// can't hit the cache when querying the head due to reorgs / changes. // can't hit the cache when querying the head due to reorgs / changes.
return s.blockCall(ctx, "eth_getBlockByNumber", string(label)) return s.blockCall(ctx, "eth_getBlockByNumber", label)
} }
func (s *EthClient) PayloadByHash(ctx context.Context, hash common.Hash) (*eth.ExecutionPayload, error) { func (s *EthClient) PayloadByHash(ctx context.Context, hash common.Hash) (*eth.ExecutionPayload, error) {
if payload, ok := s.payloadsCache.Get(hash); ok { if payload, ok := s.payloadsCache.Get(hash); ok {
return payload.(*eth.ExecutionPayload), nil return payload.(*eth.ExecutionPayload), nil
} }
return s.payloadCall(ctx, "eth_getBlockByHash", hash) return s.payloadCall(ctx, "eth_getBlockByHash", hashID(hash))
} }
func (s *EthClient) PayloadByNumber(ctx context.Context, number uint64) (*eth.ExecutionPayload, error) { func (s *EthClient) PayloadByNumber(ctx context.Context, number uint64) (*eth.ExecutionPayload, error) {
return s.payloadCall(ctx, "eth_getBlockByNumber", hexutil.EncodeUint64(number)) return s.payloadCall(ctx, "eth_getBlockByNumber", numberID(number))
} }
func (s *EthClient) PayloadByLabel(ctx context.Context, label eth.BlockLabel) (*eth.ExecutionPayload, error) { func (s *EthClient) PayloadByLabel(ctx context.Context, label eth.BlockLabel) (*eth.ExecutionPayload, error) {
return s.payloadCall(ctx, "eth_getBlockByNumber", string(label)) return s.payloadCall(ctx, "eth_getBlockByNumber", label)
} }
// FetchReceipts returns a block info and all of the receipts associated with transactions in the block. // FetchReceipts returns a block info and all of the receipts associated with transactions in the block.
......
...@@ -140,3 +140,40 @@ func TestEthClient_InfoByNumber(t *testing.T) { ...@@ -140,3 +140,40 @@ func TestEthClient_InfoByNumber(t *testing.T) {
require.Equal(t, info, expectedInfo) require.Equal(t, info, expectedInfo)
m.Mock.AssertExpectations(t) m.Mock.AssertExpectations(t)
} }
func TestEthClient_WrongInfoByNumber(t *testing.T) {
m := new(mockRPC)
_, rhdr := randHeader()
rhdr2 := *rhdr
rhdr2.Number += 1
n := rhdr.Number
ctx := context.Background()
m.On("CallContext", ctx, new(*rpcHeader),
"eth_getBlockByNumber", []any{n.String(), false}).Run(func(args mock.Arguments) {
*args[1].(**rpcHeader) = &rhdr2
}).Return([]error{nil})
s, err := NewL1Client(m, nil, nil, L1ClientDefaultConfig(&rollup.Config{SeqWindowSize: 10}, true, RPCKindBasic))
require.NoError(t, err)
_, err = s.InfoByNumber(ctx, uint64(n))
require.Error(t, err, "cannot accept the wrong block")
m.Mock.AssertExpectations(t)
}
func TestEthClient_WrongInfoByHash(t *testing.T) {
m := new(mockRPC)
_, rhdr := randHeader()
rhdr2 := *rhdr
rhdr2.Root[0] += 1
rhdr2.Hash = rhdr2.computeBlockHash()
k := rhdr.Hash
ctx := context.Background()
m.On("CallContext", ctx, new(*rpcHeader),
"eth_getBlockByHash", []any{k, false}).Run(func(args mock.Arguments) {
*args[1].(**rpcHeader) = &rhdr2
}).Return([]error{nil})
s, err := NewL1Client(m, nil, nil, L1ClientDefaultConfig(&rollup.Config{SeqWindowSize: 10}, true, RPCKindBasic))
require.NoError(t, err)
_, err = s.InfoByHash(ctx, k)
require.Error(t, err, "cannot accept the wrong block")
m.Mock.AssertExpectations(t)
}
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"strings" "strings"
"time"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -40,6 +41,7 @@ func L1ClientDefaultConfig(config *rollup.Config, trustRPC bool, kind RPCProvide ...@@ -40,6 +41,7 @@ func L1ClientDefaultConfig(config *rollup.Config, trustRPC bool, kind RPCProvide
TrustRPC: trustRPC, TrustRPC: trustRPC,
MustBePostMerge: false, MustBePostMerge: false,
RPCProviderKind: kind, RPCProviderKind: kind,
MethodResetDuration: time.Minute,
}, },
// Not bounded by span, to cover find-sync-start range fully for speedy recovery after errors. // Not bounded by span, to cover find-sync-start range fully for speedy recovery after errors.
L1BlockRefsCacheSize: fullSpan, L1BlockRefsCacheSize: fullSpan,
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"strings" "strings"
"time"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -50,6 +51,7 @@ func L2ClientDefaultConfig(config *rollup.Config, trustRPC bool) *L2ClientConfig ...@@ -50,6 +51,7 @@ func L2ClientDefaultConfig(config *rollup.Config, trustRPC bool) *L2ClientConfig
TrustRPC: trustRPC, TrustRPC: trustRPC,
MustBePostMerge: true, MustBePostMerge: true,
RPCProviderKind: RPCKindBasic, RPCProviderKind: RPCKindBasic,
MethodResetDuration: time.Minute,
}, },
// Not bounded by span, to cover find-sync-start range fully for speedy recovery after errors. // Not bounded by span, to cover find-sync-start range fully for speedy recovery after errors.
L2BlockRefsCacheSize: fullSpan, L2BlockRefsCacheSize: fullSpan,
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"math/rand" "math/rand"
"testing" "testing"
"time"
"github.com/ethereum-optimism/optimism/op-node/client" "github.com/ethereum-optimism/optimism/op-node/client"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
...@@ -85,6 +86,7 @@ func (e *methodNotFoundError) Error() string { ...@@ -85,6 +86,7 @@ func (e *methodNotFoundError) Error() string {
type ReceiptsTestCase struct { type ReceiptsTestCase struct {
name string name string
providerKind RPCProviderKind providerKind RPCProviderKind
staticMethod bool
setup func(t *testing.T) (*rpcBlock, []ReceiptsRequest) setup func(t *testing.T) (*rpcBlock, []ReceiptsRequest)
} }
...@@ -142,6 +144,10 @@ func (tc *ReceiptsTestCase) Run(t *testing.T) { ...@@ -142,6 +144,10 @@ func (tc *ReceiptsTestCase) Run(t *testing.T) {
TrustRPC: false, TrustRPC: false,
MustBePostMerge: false, MustBePostMerge: false,
RPCProviderKind: tc.providerKind, RPCProviderKind: tc.providerKind,
MethodResetDuration: time.Minute,
}
if tc.staticMethod { // if static, instantly reset, for fast clock-independent testing
testCfg.MethodResetDuration = 0
} }
logger := testlog.Logger(t, log.LvlError) logger := testlog.Logger(t, log.LvlError)
ethCl, err := NewEthClient(client.NewBaseRPCClient(cl), logger, nil, testCfg) ethCl, err := NewEthClient(client.NewBaseRPCClient(cl), logger, nil, testCfg)
...@@ -226,6 +232,12 @@ func TestEthClient_FetchReceipts(t *testing.T) { ...@@ -226,6 +232,12 @@ func TestEthClient_FetchReceipts(t *testing.T) {
providerKind: RPCKindAlchemy, providerKind: RPCKindAlchemy,
setup: fallbackCase(30, AlchemyGetTransactionReceipts), setup: fallbackCase(30, AlchemyGetTransactionReceipts),
}, },
{
name: "alchemy sticky",
providerKind: RPCKindAlchemy,
staticMethod: true,
setup: fallbackCase(30, AlchemyGetTransactionReceipts, AlchemyGetTransactionReceipts),
},
{ {
name: "alchemy fallback 1", name: "alchemy fallback 1",
providerKind: RPCKindAlchemy, providerKind: RPCKindAlchemy,
......
...@@ -3,12 +3,17 @@ package sources ...@@ -3,12 +3,17 @@ package sources
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"io"
"sync" "sync"
"time"
"github.com/ethereum-optimism/optimism/op-node/client" "github.com/ethereum-optimism/optimism/op-node/client"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/sources/caching" "github.com/ethereum-optimism/optimism/op-node/sources/caching"
"github.com/ethereum-optimism/optimism/op-service/backoff"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/peer"
) )
...@@ -18,24 +23,30 @@ var ErrNoUnsafeL2PayloadChannel = errors.New("unsafeL2Payloads channel must not ...@@ -18,24 +23,30 @@ var ErrNoUnsafeL2PayloadChannel = errors.New("unsafeL2Payloads channel must not
// RpcSyncPeer is a mock PeerID for the RPC sync client. // RpcSyncPeer is a mock PeerID for the RPC sync client.
var RpcSyncPeer peer.ID = "ALT_RPC_SYNC" var RpcSyncPeer peer.ID = "ALT_RPC_SYNC"
// receivePayload queues the received payload for processing.
// This may return an error if there's no capacity for the payload.
type receivePayload = func(ctx context.Context, from peer.ID, payload *eth.ExecutionPayload) error type receivePayload = func(ctx context.Context, from peer.ID, payload *eth.ExecutionPayload) error
type SyncClientInterface interface { type RPCSync interface {
io.Closer
// Start starts an additional worker syncing job
Start() error Start() error
Close() error // RequestL2Range signals that the given range should be fetched, implementing the alt-sync interface.
fetchUnsafeBlockFromRpc(ctx context.Context, blockNumber uint64) RequestL2Range(ctx context.Context, start, end uint64) error
} }
type SyncClient struct { type SyncClient struct {
*L2Client *L2Client
FetchUnsafeBlock chan uint64
done chan struct{} requests chan uint64
resCtx context.Context
resCancel context.CancelFunc
receivePayload receivePayload receivePayload receivePayload
wg sync.WaitGroup wg sync.WaitGroup
} }
var _ SyncClientInterface = (*SyncClient)(nil)
type SyncClientConfig struct { type SyncClientConfig struct {
L2ClientConfig L2ClientConfig
} }
...@@ -51,41 +62,92 @@ func NewSyncClient(receiver receivePayload, client client.RPC, log log.Logger, m ...@@ -51,41 +62,92 @@ func NewSyncClient(receiver receivePayload, client client.RPC, log log.Logger, m
if err != nil { if err != nil {
return nil, err return nil, err
} }
// This resource context is shared between all workers that may be started
resCtx, resCancel := context.WithCancel(context.Background())
return &SyncClient{ return &SyncClient{
L2Client: l2Client, L2Client: l2Client,
FetchUnsafeBlock: make(chan uint64, 128), resCtx: resCtx,
done: make(chan struct{}), resCancel: resCancel,
requests: make(chan uint64, 128),
receivePayload: receiver, receivePayload: receiver,
}, nil }, nil
} }
// Start starts up the state loop. // Start starts the syncing background work. This may not be called after Close().
// The loop will have been started if err is not nil.
func (s *SyncClient) Start() error { func (s *SyncClient) Start() error {
// TODO(CLI-3635): we can start multiple event loop runners as workers, to parallelize the work
s.wg.Add(1) s.wg.Add(1)
go s.eventLoop() go s.eventLoop()
return nil return nil
} }
// Close sends a signal to the event loop to stop. // Close sends a signal to close all concurrent syncing work.
func (s *SyncClient) Close() error { func (s *SyncClient) Close() error {
s.done <- struct{}{} s.resCancel()
s.wg.Wait() s.wg.Wait()
return nil return nil
} }
func (s *SyncClient) RequestL2Range(ctx context.Context, start, end uint64) error {
// Drain previous requests now that we have new information
for len(s.requests) > 0 {
select { // in case requests is being read at the same time, don't block on draining it.
case <-s.requests:
default:
break
}
}
// TODO(CLI-3635): optimize the by-range fetching with the Engine API payloads-by-range method.
s.log.Info("Scheduling to fetch missing payloads from backup RPC", "start", start, "end", end, "size", end-start)
for i := start; i < end; i++ {
select {
case s.requests <- i:
case <-ctx.Done():
return ctx.Err()
}
}
return nil
}
// eventLoop is the main event loop for the sync client. // eventLoop is the main event loop for the sync client.
func (s *SyncClient) eventLoop() { func (s *SyncClient) eventLoop() {
defer s.wg.Done() defer s.wg.Done()
s.log.Info("Starting sync client event loop") s.log.Info("Starting sync client event loop")
backoffStrategy := &backoff.ExponentialStrategy{
Min: 1000,
Max: 20_000,
MaxJitter: 250,
}
for { for {
select { select {
case <-s.done: case <-s.resCtx.Done():
s.log.Debug("Shutting down RPC sync worker")
return
case reqNum := <-s.requests:
err := backoff.DoCtx(s.resCtx, 5, backoffStrategy, func() error {
// Limit the maximum time for fetching payloads
ctx, cancel := context.WithTimeout(s.resCtx, time.Second*10)
defer cancel()
// We are only fetching one block at a time here.
return s.fetchUnsafeBlockFromRpc(ctx, reqNum)
})
if err != nil {
if err == s.resCtx.Err() {
return return
case blockNumber := <-s.FetchUnsafeBlock: }
s.fetchUnsafeBlockFromRpc(context.Background(), blockNumber) s.log.Error("failed syncing L2 block via RPC", "err", err, "num", reqNum)
// Reschedule at end of queue
select {
case s.requests <- reqNum:
default:
// drop syncing job if we are too busy with sync jobs already.
}
}
} }
} }
} }
...@@ -95,28 +157,22 @@ func (s *SyncClient) eventLoop() { ...@@ -95,28 +157,22 @@ func (s *SyncClient) eventLoop() {
// //
// Post Shanghai hardfork, the engine API's `PayloadBodiesByRange` method will be much more efficient, but for now, // Post Shanghai hardfork, the engine API's `PayloadBodiesByRange` method will be much more efficient, but for now,
// the `eth_getBlockByNumber` method is more widely available. // the `eth_getBlockByNumber` method is more widely available.
func (s *SyncClient) fetchUnsafeBlockFromRpc(ctx context.Context, blockNumber uint64) { func (s *SyncClient) fetchUnsafeBlockFromRpc(ctx context.Context, blockNumber uint64) error {
s.log.Info("Requesting unsafe payload from backup RPC", "block number", blockNumber) s.log.Info("Requesting unsafe payload from backup RPC", "block number", blockNumber)
payload, err := s.PayloadByNumber(ctx, blockNumber) payload, err := s.PayloadByNumber(ctx, blockNumber)
if err != nil { if err != nil {
s.log.Warn("Failed to convert block to execution payload", "block number", blockNumber, "err", err) return fmt.Errorf("failed to fetch payload by number (%d): %w", blockNumber, err)
return
}
// Signature validation is not necessary here since the backup RPC is trusted.
if _, ok := payload.CheckBlockHash(); !ok {
s.log.Warn("Received invalid payload from backup RPC; invalid block hash", "payload", payload.ID())
return
} }
// Note: the underlying RPC client used for syncing verifies the execution payload blockhash, if set to untrusted.
s.log.Info("Received unsafe payload from backup RPC", "payload", payload.ID()) s.log.Info("Received unsafe payload from backup RPC", "payload", payload.ID())
// Send the retrieved payload to the `unsafeL2Payloads` channel. // Send the retrieved payload to the `unsafeL2Payloads` channel.
if err = s.receivePayload(ctx, RpcSyncPeer, payload); err != nil { if err = s.receivePayload(ctx, RpcSyncPeer, payload); err != nil {
s.log.Warn("Failed to send payload into the driver's unsafeL2Payloads channel", "payload", payload.ID(), "err", err) return fmt.Errorf("failed to send payload %s into the driver's unsafeL2Payloads channel: %w", payload.ID(), err)
return
} else { } else {
s.log.Info("Sent received payload into the driver's unsafeL2Payloads channel", "payload", payload.ID()) s.log.Debug("Sent received payload into the driver's unsafeL2Payloads channel", "payload", payload.ID())
return nil
} }
} }
...@@ -245,3 +245,23 @@ func RandomBlockPrependTxs(rng *rand.Rand, txCount int, ptxs ...*types.Transacti ...@@ -245,3 +245,23 @@ func RandomBlockPrependTxs(rng *rand.Rand, txCount int, ptxs ...*types.Transacti
} }
return block, receipts return block, receipts
} }
func RandomOutputResponse(rng *rand.Rand) *eth.OutputResponse {
return &eth.OutputResponse{
Version: eth.Bytes32(RandomHash(rng)),
OutputRoot: eth.Bytes32(RandomHash(rng)),
BlockRef: RandomL2BlockRef(rng),
WithdrawalStorageRoot: RandomHash(rng),
StateRoot: RandomHash(rng),
Status: &eth.SyncStatus{
CurrentL1: RandomBlockRef(rng),
CurrentL1Finalized: RandomBlockRef(rng),
HeadL1: RandomBlockRef(rng),
SafeL1: RandomBlockRef(rng),
FinalizedL1: RandomBlockRef(rng),
UnsafeL2: RandomL2BlockRef(rng),
SafeL2: RandomL2BlockRef(rng),
FinalizedL2: RandomL2BlockRef(rng),
},
}
}
...@@ -8,14 +8,13 @@ import ( ...@@ -8,14 +8,13 @@ import (
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof" oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
opsigner "github.com/ethereum-optimism/optimism/op-signer/client" "github.com/ethereum-optimism/optimism/op-service/txmgr"
) )
const envVarPrefix = "OP_PROPOSER" const envVarPrefix = "OP_PROPOSER"
var ( var (
/* Required Flags */ // Required Flags
L1EthRpcFlag = cli.StringFlag{ L1EthRpcFlag = cli.StringFlag{
Name: "l1-eth-rpc", Name: "l1-eth-rpc",
Usage: "HTTP provider URL for L1", Usage: "HTTP provider URL for L1",
...@@ -41,53 +40,14 @@ var ( ...@@ -41,53 +40,14 @@ var (
Required: true, Required: true,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "POLL_INTERVAL"), EnvVar: opservice.PrefixEnvVar(envVarPrefix, "POLL_INTERVAL"),
} }
NumConfirmationsFlag = cli.Uint64Flag{ // Optional flags
Name: "num-confirmations",
Usage: "Number of confirmations which we will wait after " +
"appending a new batch",
Required: true,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "NUM_CONFIRMATIONS"),
}
SafeAbortNonceTooLowCountFlag = cli.Uint64Flag{
Name: "safe-abort-nonce-too-low-count",
Usage: "Number of ErrNonceTooLow observations required to " +
"give up on a tx at a particular nonce without receiving " +
"confirmation",
Required: true,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "SAFE_ABORT_NONCE_TOO_LOW_COUNT"),
}
ResubmissionTimeoutFlag = cli.DurationFlag{
Name: "resubmission-timeout",
Usage: "Duration we will wait before resubmitting a " +
"transaction to L1",
Required: true,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "RESUBMISSION_TIMEOUT"),
}
/* Optional flags */
MnemonicFlag = cli.StringFlag{
Name: "mnemonic",
Usage: "The mnemonic used to derive the wallets for either the " +
"sequencer or the l2output",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "MNEMONIC"),
}
L2OutputHDPathFlag = cli.StringFlag{
Name: "l2-output-hd-path",
Usage: "The HD path used to derive the l2output wallet from the " +
"mnemonic. The mnemonic flag must also be set.",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "L2_OUTPUT_HD_PATH"),
}
PrivateKeyFlag = cli.StringFlag{
Name: "private-key",
Usage: "The private key to use with the l2output wallet. Must not be used with mnemonic.",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "PRIVATE_KEY"),
}
AllowNonFinalizedFlag = cli.BoolFlag{ AllowNonFinalizedFlag = cli.BoolFlag{
Name: "allow-non-finalized", Name: "allow-non-finalized",
Usage: "Allow the proposer to submit proposals for L2 blocks derived from non-finalized L1 blocks.", Usage: "Allow the proposer to submit proposals for L2 blocks derived from non-finalized L1 blocks.",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "ALLOW_NON_FINALIZED"), EnvVar: opservice.PrefixEnvVar(envVarPrefix, "ALLOW_NON_FINALIZED"),
} }
// Legacy Flags
L2OutputHDPathFlag = txmgr.L2OutputHDPathFlag
) )
var requiredFlags = []cli.Flag{ var requiredFlags = []cli.Flag{
...@@ -95,15 +55,9 @@ var requiredFlags = []cli.Flag{ ...@@ -95,15 +55,9 @@ var requiredFlags = []cli.Flag{
RollupRpcFlag, RollupRpcFlag,
L2OOAddressFlag, L2OOAddressFlag,
PollIntervalFlag, PollIntervalFlag,
NumConfirmationsFlag,
SafeAbortNonceTooLowCountFlag,
ResubmissionTimeoutFlag,
} }
var optionalFlags = []cli.Flag{ var optionalFlags = []cli.Flag{
MnemonicFlag,
L2OutputHDPathFlag,
PrivateKeyFlag,
AllowNonFinalizedFlag, AllowNonFinalizedFlag,
} }
...@@ -113,7 +67,7 @@ func init() { ...@@ -113,7 +67,7 @@ func init() {
optionalFlags = append(optionalFlags, oplog.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, oplog.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, opmetrics.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, opmetrics.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, oppprof.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, oppprof.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, opsigner.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, txmgr.CLIFlags(envVarPrefix)...)
Flags = append(requiredFlags, optionalFlags...) Flags = append(requiredFlags, optionalFlags...)
} }
......
package metrics
import (
"context"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/prometheus/client_golang/prometheus"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
)
const Namespace = "op_proposer"
type Metricer interface {
RecordInfo(version string)
RecordUp()
// Records all L1 and L2 block events
opmetrics.RefMetricer
RecordL2BlocksProposed(l2ref eth.L2BlockRef)
}
type Metrics struct {
ns string
registry *prometheus.Registry
factory opmetrics.Factory
opmetrics.RefMetrics
Info prometheus.GaugeVec
Up prometheus.Gauge
}
var _ Metricer = (*Metrics)(nil)
func NewMetrics(procName string) *Metrics {
if procName == "" {
procName = "default"
}
ns := Namespace + "_" + procName
registry := opmetrics.NewRegistry()
factory := opmetrics.With(registry)
return &Metrics{
ns: ns,
registry: registry,
factory: factory,
RefMetrics: opmetrics.MakeRefMetrics(ns, factory),
Info: *factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns,
Name: "info",
Help: "Pseudo-metric tracking version and config info",
}, []string{
"version",
}),
Up: factory.NewGauge(prometheus.GaugeOpts{
Namespace: ns,
Name: "up",
Help: "1 if the op-proposer has finished starting up",
}),
}
}
func (m *Metrics) Serve(ctx context.Context, host string, port int) error {
return opmetrics.ListenAndServe(ctx, m.registry, host, port)
}
func (m *Metrics) StartBalanceMetrics(ctx context.Context,
l log.Logger, client *ethclient.Client, account common.Address) {
opmetrics.LaunchBalanceMetrics(ctx, l, m.registry, m.ns, client, account)
}
// RecordInfo sets a pseudo-metric that contains versioning and
// config info for the op-proposer.
func (m *Metrics) RecordInfo(version string) {
m.Info.WithLabelValues(version).Set(1)
}
// RecordUp sets the up metric to 1.
func (m *Metrics) RecordUp() {
prometheus.MustRegister()
m.Up.Set(1)
}
const (
BlockProposed = "proposed"
)
// RecordL2BlocksProposed should be called when new L2 block is proposed
func (m *Metrics) RecordL2BlocksProposed(l2ref eth.L2BlockRef) {
m.RecordL2Ref(BlockProposed, l2ref)
}
package metrics
import (
"github.com/ethereum-optimism/optimism/op-node/eth"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
)
type noopMetrics struct{ opmetrics.NoopRefMetrics }
var NoopMetrics Metricer = new(noopMetrics)
func (*noopMetrics) RecordInfo(version string) {}
func (*noopMetrics) RecordUp() {}
func (*noopMetrics) RecordL2BlocksProposed(l2ref eth.L2BlockRef) {}
package proposer
import (
"math/big"
"math/rand"
"testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-node/testutils"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require"
)
// setupL2OutputOracle deploys the L2 Output Oracle contract to a simulated backend
func setupL2OutputOracle() (common.Address, *bind.TransactOpts, *backends.SimulatedBackend, *bindings.L2OutputOracle, error) {
privateKey, err := crypto.GenerateKey()
from := crypto.PubkeyToAddress(privateKey.PublicKey)
if err != nil {
return common.Address{}, nil, nil, nil, err
}
opts, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(1337))
if err != nil {
return common.Address{}, nil, nil, nil, err
}
backend := backends.NewSimulatedBackend(core.GenesisAlloc{from: {Balance: big.NewInt(params.Ether)}}, 50_000_000)
_, _, contract, err := bindings.DeployL2OutputOracle(
opts,
backend,
big.NewInt(10),
big.NewInt(2),
big.NewInt(0),
big.NewInt(0),
from,
common.Address{0xdd},
big.NewInt(100))
if err != nil {
return common.Address{}, nil, nil, nil, err
}
return from, opts, backend, contract, nil
}
// TestManualABIPacking ensure that the manual ABI packing is the same as going through the bound contract.
// We don't use the contract to transact because it does not fit our transaction management scheme, but
// we want to make sure that we don't incorrectly create the transaction data.
func TestManualABIPacking(t *testing.T) {
_, opts, _, contract, err := setupL2OutputOracle()
require.NoError(t, err)
rng := rand.New(rand.NewSource(1234))
abi, err := bindings.L2OutputOracleMetaData.GetAbi()
require.NoError(t, err)
output := testutils.RandomOutputResponse(rng)
txData, err := proposeL2OutputTxData(abi, output)
require.NoError(t, err)
// set a gas limit to disable gas estimation. The invariantes that the L2OO tries to uphold
// are not maintained in this test.
opts.GasLimit = 100_000
tx, err := contract.ProposeL2Output(
opts,
output.OutputRoot,
new(big.Int).SetUint64(output.BlockRef.Number),
output.Status.CurrentL1.Hash,
new(big.Int).SetUint64(output.Status.CurrentL1.Number))
require.NoError(t, err)
require.Equal(t, txData, tx.Data())
}
...@@ -10,13 +10,11 @@ import ( ...@@ -10,13 +10,11 @@ import (
"github.com/ethereum-optimism/optimism/op-node/sources" "github.com/ethereum-optimism/optimism/op-node/sources"
"github.com/ethereum-optimism/optimism/op-proposer/flags" "github.com/ethereum-optimism/optimism/op-proposer/flags"
opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof" oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
opsigner "github.com/ethereum-optimism/optimism/op-signer/client"
) )
// Config contains the well typed fields that are used to initialize the output submitter. // Config contains the well typed fields that are used to initialize the output submitter.
...@@ -24,12 +22,10 @@ import ( ...@@ -24,12 +22,10 @@ import (
type Config struct { type Config struct {
L2OutputOracleAddr common.Address L2OutputOracleAddr common.Address
PollInterval time.Duration PollInterval time.Duration
TxManagerConfig txmgr.Config TxManager txmgr.TxManager
L1Client *ethclient.Client L1Client *ethclient.Client
RollupClient *sources.RollupClient RollupClient *sources.RollupClient
AllowNonFinalized bool AllowNonFinalized bool
From common.Address
SignerFnFactory opcrypto.SignerFactory
} }
// CLIConfig is a well typed config that is parsed from the CLI params. // CLIConfig is a well typed config that is parsed from the CLI params.
...@@ -51,47 +47,19 @@ type CLIConfig struct { ...@@ -51,47 +47,19 @@ type CLIConfig struct {
// and creating a new batch. // and creating a new batch.
PollInterval time.Duration PollInterval time.Duration
// NumConfirmations is the number of confirmations which we will wait after
// appending new batches.
NumConfirmations uint64
// SafeAbortNonceTooLowCount is the number of ErrNonceTooLowObservations
// required to give up on a tx at a particular nonce without receiving
// confirmation.
SafeAbortNonceTooLowCount uint64
// ResubmissionTimeout is time we will wait before resubmitting a
// transaction.
ResubmissionTimeout time.Duration
// Mnemonic is the HD seed used to derive the wallet private keys for both
// the sequence and proposer. Must be used in conjunction with
// SequencerHDPath and ProposerHDPath.
Mnemonic string
// L2OutputHDPath is the derivation path used to obtain the private key for
// the l2output transactions.
L2OutputHDPath string
// PrivateKey is the private key used for l2output transactions.
PrivateKey string
RPCConfig oprpc.CLIConfig
/* Optional Params */
// AllowNonFinalized can be set to true to propose outputs // AllowNonFinalized can be set to true to propose outputs
// for L2 blocks derived from non-finalized L1 data. // for L2 blocks derived from non-finalized L1 data.
AllowNonFinalized bool AllowNonFinalized bool
TxMgrConfig txmgr.CLIConfig
RPCConfig oprpc.CLIConfig
LogConfig oplog.CLIConfig LogConfig oplog.CLIConfig
MetricsConfig opmetrics.CLIConfig MetricsConfig opmetrics.CLIConfig
PprofConfig oppprof.CLIConfig PprofConfig oppprof.CLIConfig
// SignerConfig contains the client config for op-signer service
SignerConfig opsigner.CLIConfig
} }
func (c CLIConfig) Check() error { func (c CLIConfig) Check() error {
...@@ -107,7 +75,7 @@ func (c CLIConfig) Check() error { ...@@ -107,7 +75,7 @@ func (c CLIConfig) Check() error {
if err := c.PprofConfig.Check(); err != nil { if err := c.PprofConfig.Check(); err != nil {
return err return err
} }
if err := c.SignerConfig.Check(); err != nil { if err := c.TxMgrConfig.Check(); err != nil {
return err return err
} }
return nil return nil
...@@ -121,18 +89,12 @@ func NewConfig(ctx *cli.Context) CLIConfig { ...@@ -121,18 +89,12 @@ func NewConfig(ctx *cli.Context) CLIConfig {
RollupRpc: ctx.GlobalString(flags.RollupRpcFlag.Name), RollupRpc: ctx.GlobalString(flags.RollupRpcFlag.Name),
L2OOAddress: ctx.GlobalString(flags.L2OOAddressFlag.Name), L2OOAddress: ctx.GlobalString(flags.L2OOAddressFlag.Name),
PollInterval: ctx.GlobalDuration(flags.PollIntervalFlag.Name), PollInterval: ctx.GlobalDuration(flags.PollIntervalFlag.Name),
NumConfirmations: ctx.GlobalUint64(flags.NumConfirmationsFlag.Name), TxMgrConfig: txmgr.ReadCLIConfig(ctx),
SafeAbortNonceTooLowCount: ctx.GlobalUint64(flags.SafeAbortNonceTooLowCountFlag.Name),
ResubmissionTimeout: ctx.GlobalDuration(flags.ResubmissionTimeoutFlag.Name),
Mnemonic: ctx.GlobalString(flags.MnemonicFlag.Name),
L2OutputHDPath: ctx.GlobalString(flags.L2OutputHDPathFlag.Name),
PrivateKey: ctx.GlobalString(flags.PrivateKeyFlag.Name),
// Optional Flags // Optional Flags
AllowNonFinalized: ctx.GlobalBool(flags.AllowNonFinalizedFlag.Name), AllowNonFinalized: ctx.GlobalBool(flags.AllowNonFinalizedFlag.Name),
RPCConfig: oprpc.ReadCLIConfig(ctx), RPCConfig: oprpc.ReadCLIConfig(ctx),
LogConfig: oplog.ReadCLIConfig(ctx), LogConfig: oplog.ReadCLIConfig(ctx),
MetricsConfig: opmetrics.ReadCLIConfig(ctx), MetricsConfig: opmetrics.ReadCLIConfig(ctx),
PprofConfig: oppprof.ReadCLIConfig(ctx), PprofConfig: oppprof.ReadCLIConfig(ctx),
SignerConfig: opsigner.ReadCLIConfig(ctx),
} }
} }
...@@ -8,7 +8,6 @@ import ( ...@@ -8,7 +8,6 @@ import (
_ "net/http/pprof" _ "net/http/pprof"
"os" "os"
"os/signal" "os/signal"
"strings"
"sync" "sync"
"syscall" "syscall"
"time" "time"
...@@ -16,17 +15,14 @@ import ( ...@@ -16,17 +15,14 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli" "github.com/urfave/cli"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/sources" "github.com/ethereum-optimism/optimism/op-node/sources"
opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" "github.com/ethereum-optimism/optimism/op-proposer/metrics"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof" oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
...@@ -49,9 +45,16 @@ func Main(version string, cliCtx *cli.Context) error { ...@@ -49,9 +45,16 @@ func Main(version string, cliCtx *cli.Context) error {
} }
l := oplog.NewLogger(cfg.LogConfig) l := oplog.NewLogger(cfg.LogConfig)
m := metrics.NewMetrics("default")
l.Info("Initializing L2 Output Submitter") l.Info("Initializing L2 Output Submitter")
l2OutputSubmitter, err := NewL2OutputSubmitterFromCLIConfig(cfg, l) proposerConfig, err := NewL2OutputSubmitterConfigFromCLIConfig(cfg, l)
if err != nil {
l.Error("Unable to create the L2 Output Submitter", "error", err)
return err
}
l2OutputSubmitter, err := NewL2OutputSubmitter(*proposerConfig, l, m)
if err != nil { if err != nil {
l.Error("Unable to create the L2 Output Submitter", "error", err) l.Error("Unable to create the L2 Output Submitter", "error", err)
return err return err
...@@ -59,7 +62,6 @@ func Main(version string, cliCtx *cli.Context) error { ...@@ -59,7 +62,6 @@ func Main(version string, cliCtx *cli.Context) error {
l.Info("Starting L2 Output Submitter") l.Info("Starting L2 Output Submitter")
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
if err := l2OutputSubmitter.Start(); err != nil { if err := l2OutputSubmitter.Start(); err != nil {
cancel() cancel()
l.Error("Unable to start L2 Output Submitter", "error", err) l.Error("Unable to start L2 Output Submitter", "error", err)
...@@ -78,17 +80,15 @@ func Main(version string, cliCtx *cli.Context) error { ...@@ -78,17 +80,15 @@ func Main(version string, cliCtx *cli.Context) error {
}() }()
} }
registry := opmetrics.NewRegistry()
metricsCfg := cfg.MetricsConfig metricsCfg := cfg.MetricsConfig
if metricsCfg.Enabled { if metricsCfg.Enabled {
l.Info("starting metrics server", "addr", metricsCfg.ListenAddr, "port", metricsCfg.ListenPort) l.Info("starting metrics server", "addr", metricsCfg.ListenAddr, "port", metricsCfg.ListenPort)
go func() { go func() {
if err := opmetrics.ListenAndServe(ctx, registry, metricsCfg.ListenAddr, metricsCfg.ListenPort); err != nil { if err := m.Serve(ctx, metricsCfg.ListenAddr, metricsCfg.ListenPort); err != nil {
l.Error("error starting metrics server", err) l.Error("error starting metrics server", err)
} }
}() }()
addr := l2OutputSubmitter.from m.StartBalanceMetrics(ctx, l, proposerConfig.L1Client, proposerConfig.TxManager.From())
opmetrics.LaunchBalanceMetrics(ctx, l, registry, "", l2OutputSubmitter.l1Client, addr)
} }
rpcCfg := cfg.RPCConfig rpcCfg := cfg.RPCConfig
...@@ -98,6 +98,9 @@ func Main(version string, cliCtx *cli.Context) error { ...@@ -98,6 +98,9 @@ func Main(version string, cliCtx *cli.Context) error {
return fmt.Errorf("error starting RPC server: %w", err) return fmt.Errorf("error starting RPC server: %w", err)
} }
m.RecordInfo(version)
m.RecordUp()
interruptChannel := make(chan os.Signal, 1) interruptChannel := make(chan os.Signal, 1)
signal.Notify(interruptChannel, []os.Signal{ signal.Notify(interruptChannel, []os.Signal{
os.Interrupt, os.Interrupt,
...@@ -117,43 +120,49 @@ type L2OutputSubmitter struct { ...@@ -117,43 +120,49 @@ type L2OutputSubmitter struct {
wg sync.WaitGroup wg sync.WaitGroup
done chan struct{} done chan struct{}
log log.Logger log log.Logger
metr metrics.Metricer
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
// L1Client is used to submit transactions to
l1Client *ethclient.Client
// RollupClient is used to retrieve output roots from // RollupClient is used to retrieve output roots from
rollupClient *sources.RollupClient rollupClient *sources.RollupClient
l2ooContract *bindings.L2OutputOracle l2ooContract *bindings.L2OutputOracleCaller
rawL2ooContract *bind.BoundContract l2ooContractAddr common.Address
l2ooABI *abi.ABI
// AllowNonFinalized enables the proposal of safe, but non-finalized L2 blocks. // AllowNonFinalized enables the proposal of safe, but non-finalized L2 blocks.
// The L1 block-hash embedded in the proposal TX is checked and should ensure the proposal // The L1 block-hash embedded in the proposal TX is checked and should ensure the proposal
// is never valid on an alternative L1 chain that would produce different L2 data. // is never valid on an alternative L1 chain that would produce different L2 data.
// This option is not necessary when higher proposal latency is acceptable and L1 is healthy. // This option is not necessary when higher proposal latency is acceptable and L1 is healthy.
allowNonFinalized bool allowNonFinalized bool
// From is the address to send transactions from
from common.Address
// SignerFn is the function used to sign transactions
signerFn opcrypto.SignerFn
// How frequently to poll L2 for new finalized outputs // How frequently to poll L2 for new finalized outputs
pollInterval time.Duration pollInterval time.Duration
} }
// NewL2OutputSubmitterFromCLIConfig creates a new L2 Output Submitter given the CLI Config // NewL2OutputSubmitterFromCLIConfig creates a new L2 Output Submitter given the CLI Config
func NewL2OutputSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger) (*L2OutputSubmitter, error) { func NewL2OutputSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger, m metrics.Metricer) (*L2OutputSubmitter, error) {
signer, fromAddress, err := opcrypto.SignerFactoryFromConfig(l, cfg.PrivateKey, cfg.Mnemonic, cfg.L2OutputHDPath, cfg.SignerConfig) proposerConfig, err := NewL2OutputSubmitterConfigFromCLIConfig(cfg, l)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return NewL2OutputSubmitter(*proposerConfig, l, m)
}
// NewL2OutputSubmitterConfigFromCLIConfig creates the proposer config from the CLI config.
func NewL2OutputSubmitterConfigFromCLIConfig(cfg CLIConfig, l log.Logger) (*Config, error) {
l2ooAddress, err := parseAddress(cfg.L2OOAddress) l2ooAddress, err := parseAddress(cfg.L2OOAddress)
if err != nil { if err != nil {
return nil, err return nil, err
} }
txManagerConfig, err := txmgr.NewConfig(cfg.TxMgrConfig, l)
if err != nil {
return nil, err
}
txManager := txmgr.NewSimpleTxManager("proposer", l, txManagerConfig)
// Connect to L1 and L2 providers. Perform these last since they are the most expensive. // Connect to L1 and L2 providers. Perform these last since they are the most expensive.
ctx := context.Background() ctx := context.Background()
l1Client, err := dialEthClientWithTimeout(ctx, cfg.L1EthRpc) l1Client, err := dialEthClientWithTimeout(ctx, cfg.L1EthRpc)
...@@ -166,78 +175,57 @@ func NewL2OutputSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger) (*L2OutputSu ...@@ -166,78 +175,57 @@ func NewL2OutputSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger) (*L2OutputSu
return nil, err return nil, err
} }
txMgrConfg := txmgr.Config{ return &Config{
ResubmissionTimeout: cfg.ResubmissionTimeout,
ReceiptQueryInterval: time.Second,
NumConfirmations: cfg.NumConfirmations,
SafeAbortNonceTooLowCount: cfg.SafeAbortNonceTooLowCount,
From: fromAddress,
}
proposerCfg := Config{
L2OutputOracleAddr: l2ooAddress, L2OutputOracleAddr: l2ooAddress,
PollInterval: cfg.PollInterval, PollInterval: cfg.PollInterval,
TxManagerConfig: txMgrConfg,
L1Client: l1Client, L1Client: l1Client,
RollupClient: rollupClient, RollupClient: rollupClient,
AllowNonFinalized: cfg.AllowNonFinalized, AllowNonFinalized: cfg.AllowNonFinalized,
From: fromAddress, TxManager: txManager,
SignerFnFactory: signer, }, nil
}
return NewL2OutputSubmitter(proposerCfg, l)
} }
// NewL2OutputSubmitter creates a new L2 Output Submitter // NewL2OutputSubmitter creates a new L2 Output Submitter
func NewL2OutputSubmitter(cfg Config, l log.Logger) (*L2OutputSubmitter, error) { func NewL2OutputSubmitter(cfg Config, l log.Logger, m metrics.Metricer) (*L2OutputSubmitter, error) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
cCtx, cCancel := context.WithTimeout(ctx, defaultDialTimeout) l2ooContract, err := bindings.NewL2OutputOracleCaller(cfg.L2OutputOracleAddr, cfg.L1Client)
chainID, err := cfg.L1Client.ChainID(cCtx)
cCancel()
if err != nil { if err != nil {
cancel() cancel()
return nil, err return nil, err
} }
signer := cfg.SignerFnFactory(chainID)
cfg.TxManagerConfig.Signer = signer
l2ooContract, err := bindings.NewL2OutputOracle(cfg.L2OutputOracleAddr, cfg.L1Client) cCtx, cCancel := context.WithTimeout(ctx, defaultDialTimeout)
if err != nil { defer cCancel()
cancel() version, err := l2ooContract.Version(&bind.CallOpts{Context: cCtx})
return nil, err
}
version, err := l2ooContract.Version(&bind.CallOpts{})
if err != nil { if err != nil {
cancel() cancel()
return nil, err return nil, err
} }
log.Info("Connected to L2OutputOracle", "address", cfg.L2OutputOracleAddr, "version", version) log.Info("Connected to L2OutputOracle", "address", cfg.L2OutputOracleAddr, "version", version)
parsed, err := abi.JSON(strings.NewReader(bindings.L2OutputOracleMetaData.ABI)) parsed, err := bindings.L2OutputOracleMetaData.GetAbi()
if err != nil { if err != nil {
cancel() cancel()
return nil, err return nil, err
} }
rawL2ooContract := bind.NewBoundContract(cfg.L2OutputOracleAddr, parsed, cfg.L1Client, cfg.L1Client, cfg.L1Client)
return &L2OutputSubmitter{ return &L2OutputSubmitter{
txMgr: txmgr.NewSimpleTxManager("proposer", l, cfg.TxManagerConfig, cfg.L1Client), txMgr: cfg.TxManager,
done: make(chan struct{}), done: make(chan struct{}),
log: l, log: l,
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
metr: m,
l1Client: cfg.L1Client,
rollupClient: cfg.RollupClient, rollupClient: cfg.RollupClient,
l2ooContract: l2ooContract, l2ooContract: l2ooContract,
rawL2ooContract: rawL2ooContract, l2ooContractAddr: cfg.L2OutputOracleAddr,
l2ooABI: parsed,
allowNonFinalized: cfg.AllowNonFinalized, allowNonFinalized: cfg.AllowNonFinalized,
from: cfg.From,
signerFn: signer,
pollInterval: cfg.PollInterval, pollInterval: cfg.PollInterval,
}, nil }, nil
} }
...@@ -254,29 +242,14 @@ func (l *L2OutputSubmitter) Stop() { ...@@ -254,29 +242,14 @@ func (l *L2OutputSubmitter) Stop() {
l.wg.Wait() l.wg.Wait()
} }
// UpdateGasPrice signs an otherwise identical txn to the one provided but with
// updated gas prices sampled from the existing network conditions.
//
// NOTE: This method SHOULD NOT publish the resulting transaction.
func (l *L2OutputSubmitter) UpdateGasPrice(ctx context.Context, tx *types.Transaction) (*types.Transaction, error) {
opts := &bind.TransactOpts{
From: l.from,
Signer: func(addr common.Address, tx *types.Transaction) (*types.Transaction, error) {
return l.signerFn(ctx, addr, tx)
},
Context: ctx,
Nonce: new(big.Int).SetUint64(tx.Nonce()),
NoSend: true,
}
return l.rawL2ooContract.RawTransact(opts, tx.Data())
}
// FetchNextOutputInfo gets the block number of the next proposal. // FetchNextOutputInfo gets the block number of the next proposal.
// It returns: the next block number, if the proposal should be made, error // It returns: the next block number, if the proposal should be made, error
func (l *L2OutputSubmitter) FetchNextOutputInfo(ctx context.Context) (*eth.OutputResponse, bool, error) { func (l *L2OutputSubmitter) FetchNextOutputInfo(ctx context.Context) (*eth.OutputResponse, bool, error) {
cCtx, cancel := context.WithTimeout(ctx, defaultDialTimeout)
defer cancel()
callOpts := &bind.CallOpts{ callOpts := &bind.CallOpts{
From: l.from, From: l.txMgr.From(),
Context: ctx, Context: cCtx,
} }
nextCheckpointBlock, err := l.l2ooContract.NextBlockNumber(callOpts) nextCheckpointBlock, err := l.l2ooContract.NextBlockNumber(callOpts)
if err != nil { if err != nil {
...@@ -284,7 +257,9 @@ func (l *L2OutputSubmitter) FetchNextOutputInfo(ctx context.Context) (*eth.Outpu ...@@ -284,7 +257,9 @@ func (l *L2OutputSubmitter) FetchNextOutputInfo(ctx context.Context) (*eth.Outpu
return nil, false, err return nil, false, err
} }
// Fetch the current L2 heads // Fetch the current L2 heads
status, err := l.rollupClient.SyncStatus(ctx) cCtx, cancel = context.WithTimeout(ctx, defaultDialTimeout)
defer cancel()
status, err := l.rollupClient.SyncStatus(cCtx)
if err != nil { if err != nil {
l.log.Error("proposer unable to get sync status", "err", err) l.log.Error("proposer unable to get sync status", "err", err)
return nil, false, err return nil, false, err
...@@ -302,17 +277,23 @@ func (l *L2OutputSubmitter) FetchNextOutputInfo(ctx context.Context) (*eth.Outpu ...@@ -302,17 +277,23 @@ func (l *L2OutputSubmitter) FetchNextOutputInfo(ctx context.Context) (*eth.Outpu
return nil, false, nil return nil, false, nil
} }
output, err := l.rollupClient.OutputAtBlock(ctx, nextCheckpointBlock.Uint64()) return l.fetchOuput(ctx, nextCheckpointBlock)
}
func (l *L2OutputSubmitter) fetchOuput(ctx context.Context, block *big.Int) (*eth.OutputResponse, bool, error) {
ctx, cancel := context.WithTimeout(ctx, defaultDialTimeout)
defer cancel()
output, err := l.rollupClient.OutputAtBlock(ctx, block.Uint64())
if err != nil { if err != nil {
l.log.Error("failed to fetch output at block %d: %w", nextCheckpointBlock, err) l.log.Error("failed to fetch output at block %d: %w", block, err)
return nil, false, err return nil, false, err
} }
if output.Version != supportedL2OutputVersion { if output.Version != supportedL2OutputVersion {
l.log.Error("unsupported l2 output version: %s", output.Version) l.log.Error("unsupported l2 output version: %s", output.Version)
return nil, false, errors.New("unsupported l2 output version") return nil, false, errors.New("unsupported l2 output version")
} }
if output.BlockRef.Number != nextCheckpointBlock.Uint64() { // sanity check, e.g. in case of bad RPC caching if output.BlockRef.Number != block.Uint64() { // sanity check, e.g. in case of bad RPC caching
l.log.Error("invalid blockNumber: next blockNumber is %v, blockNumber of block is %v", nextCheckpointBlock, output.BlockRef.Number) l.log.Error("invalid blockNumber: next blockNumber is %v, blockNumber of block is %v", block, output.BlockRef.Number)
return nil, false, errors.New("invalid blockNumber") return nil, false, errors.New("invalid blockNumber")
} }
...@@ -328,54 +309,36 @@ func (l *L2OutputSubmitter) FetchNextOutputInfo(ctx context.Context) (*eth.Outpu ...@@ -328,54 +309,36 @@ func (l *L2OutputSubmitter) FetchNextOutputInfo(ctx context.Context) (*eth.Outpu
return output, true, nil return output, true, nil
} }
// CreateProposalTx transforms an output response into a signed output transaction. // ProposeL2OutputTxData creates the transaction data for the ProposeL2Output function
// It does not send the transaction to the transaction pool. func (l *L2OutputSubmitter) ProposeL2OutputTxData(output *eth.OutputResponse) ([]byte, error) {
func (l *L2OutputSubmitter) CreateProposalTx(ctx context.Context, output *eth.OutputResponse) (*types.Transaction, error) { return proposeL2OutputTxData(l.l2ooABI, output)
nonce, err := l.l1Client.NonceAt(ctx, l.from, nil) }
if err != nil {
l.log.Error("Failed to get nonce", "err", err, "from", l.from)
return nil, err
}
opts := &bind.TransactOpts{
From: l.from,
Signer: func(addr common.Address, tx *types.Transaction) (*types.Transaction, error) {
return l.signerFn(ctx, addr, tx)
},
Context: ctx,
Nonce: new(big.Int).SetUint64(nonce),
NoSend: true,
}
tx, err := l.l2ooContract.ProposeL2Output( // proposeL2OutputTxData creates the transaction data for the ProposeL2Output function
opts, func proposeL2OutputTxData(abi *abi.ABI, output *eth.OutputResponse) ([]byte, error) {
return abi.Pack(
"proposeL2Output",
output.OutputRoot, output.OutputRoot,
new(big.Int).SetUint64(output.BlockRef.Number), new(big.Int).SetUint64(output.BlockRef.Number),
output.Status.CurrentL1.Hash, output.Status.CurrentL1.Hash,
new(big.Int).SetUint64(output.Status.CurrentL1.Number)) new(big.Int).SetUint64(output.Status.CurrentL1.Number))
if err != nil {
l.log.Error("failed to create the ProposeL2Output transaction", "err", err)
return nil, err
}
return tx, nil
} }
// SendTransaction sends a transaction through the transaction manager which handles automatic // sendTransaction creates & sends transactions through the underlying transaction manager.
// price bumping. func (l *L2OutputSubmitter) sendTransaction(ctx context.Context, output *eth.OutputResponse) error {
// It also hardcodes a timeout of 100s. data, err := l.ProposeL2OutputTxData(output)
func (l *L2OutputSubmitter) SendTransaction(ctx context.Context, tx *types.Transaction) error { if err != nil {
// Wait until one of our submitted transactions confirms. If no return err
// receipt is received it's likely our gas price was too low. }
cCtx, cancel := context.WithTimeout(ctx, 100*time.Second) receipt, err := l.txMgr.Send(ctx, txmgr.TxCandidate{
defer cancel() TxData: data,
l.log.Info("Sending transaction", "tx_hash", tx.Hash()) To: l.l2ooContractAddr,
receipt, err := l.txMgr.Send(cCtx, tx) GasLimit: 0,
From: l.txMgr.From(),
})
if err != nil { if err != nil {
l.log.Error("proposer unable to publish tx", "err", err)
return err return err
} }
// The transaction was successfully submitted
l.log.Info("proposer tx successfully published", "tx_hash", receipt.TxHash) l.log.Info("proposer tx successfully published", "tx_hash", receipt.TxHash)
return nil return nil
} }
...@@ -391,9 +354,7 @@ func (l *L2OutputSubmitter) loop() { ...@@ -391,9 +354,7 @@ func (l *L2OutputSubmitter) loop() {
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
cCtx, cancel := context.WithTimeout(ctx, 30*time.Second) output, shouldPropose, err := l.FetchNextOutputInfo(ctx)
output, shouldPropose, err := l.FetchNextOutputInfo(cCtx)
cancel()
if err != nil { if err != nil {
break break
} }
...@@ -401,21 +362,14 @@ func (l *L2OutputSubmitter) loop() { ...@@ -401,21 +362,14 @@ func (l *L2OutputSubmitter) loop() {
break break
} }
cCtx, cancel = context.WithTimeout(ctx, 30*time.Second) cCtx, cancel := context.WithTimeout(ctx, 10*time.Minute)
tx, err := l.CreateProposalTx(cCtx, output) if err := l.sendTransaction(cCtx, output); err != nil {
cancel()
if err != nil {
l.log.Error("Failed to create proposal transaction", "err", err)
break
}
cCtx, cancel = context.WithTimeout(ctx, 10*time.Minute)
if err := l.SendTransaction(cCtx, tx); err != nil {
l.log.Error("Failed to send proposal transaction", "err", err) l.log.Error("Failed to send proposal transaction", "err", err)
cancel() cancel()
break break
} else {
cancel()
} }
l.metr.RecordL2BlocksProposed(output.BlockRef)
cancel()
case <-l.done: case <-l.done:
return return
......
...@@ -29,6 +29,9 @@ func Do(maxAttempts int, strategy Strategy, op Operation) error { ...@@ -29,6 +29,9 @@ func Do(maxAttempts int, strategy Strategy, op Operation) error {
} }
func DoCtx(ctx context.Context, maxAttempts int, strategy Strategy, op Operation) error { func DoCtx(ctx context.Context, maxAttempts int, strategy Strategy, op Operation) error {
if maxAttempts < 1 {
return fmt.Errorf("need at least 1 attempt to run op, but have %d max attempts", maxAttempts)
}
var attempt int var attempt int
reattemptCh := make(chan struct{}, 1) reattemptCh := make(chan struct{}, 1)
......
package txmgr
import (
"context"
"errors"
"math/big"
"time"
opservice "github.com/ethereum-optimism/optimism/op-service"
opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
"github.com/ethereum-optimism/optimism/op-signer/client"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli"
)
const (
// Duplicated L1 RPC flag
L1RPCFlagName = "l1-eth-rpc"
// Key Management Flags (also have op-signer client flags)
MnemonicFlagName = "mnemonic"
HDPathFlagName = "hd-path"
PrivateKeyFlagName = "private-key"
// Legacy TxMgr Flags
NumConfirmationsFlagName = "num-confirmations"
SafeAbortNonceTooLowCountFlagName = "safe-abort-nonce-too-low-count"
ResubmissionTimeoutFlagName = "resubmission-timeout"
)
var (
SequencerHDPathFlag = cli.StringFlag{
Name: "sequencer-hd-path",
Usage: "DEPRECATED: The HD path used to derive the sequencer wallet from the " +
"mnemonic. The mnemonic flag must also be set.",
EnvVar: "OP_BATCHER_SEQUENCER_HD_PATH",
}
L2OutputHDPathFlag = cli.StringFlag{
Name: "l2-output-hd-path",
Usage: "DEPRECATED:The HD path used to derive the l2output wallet from the " +
"mnemonic. The mnemonic flag must also be set.",
EnvVar: "OP_PROPOSER_L2_OUTPUT_HD_PATH",
}
)
func CLIFlags(envPrefix string) []cli.Flag {
return append([]cli.Flag{
cli.StringFlag{
Name: MnemonicFlagName,
Usage: "The mnemonic used to derive the wallets for either the service",
EnvVar: opservice.PrefixEnvVar(envPrefix, "MNEMONIC"),
},
cli.StringFlag{
Name: HDPathFlagName,
Usage: "The HD path used to derive the sequencer wallet from the mnemonic. The mnemonic flag must also be set.",
EnvVar: opservice.PrefixEnvVar(envPrefix, "HD_PATH"),
},
SequencerHDPathFlag,
L2OutputHDPathFlag,
cli.StringFlag{
Name: "private-key",
Usage: "The private key to use with the service. Must not be used with mnemonic.",
EnvVar: opservice.PrefixEnvVar(envPrefix, "PRIVATE_KEY"),
},
cli.Uint64Flag{
Name: NumConfirmationsFlagName,
Usage: "Number of confirmations which we will wait after sending a transaction",
Value: 10,
EnvVar: opservice.PrefixEnvVar(envPrefix, "NUM_CONFIRMATIONS"),
},
cli.Uint64Flag{
Name: "safe-abort-nonce-too-low-count",
Usage: "Number of ErrNonceTooLow observations required to give up on a tx at a particular nonce without receiving confirmation",
Value: 3,
EnvVar: opservice.PrefixEnvVar(envPrefix, "SAFE_ABORT_NONCE_TOO_LOW_COUNT"),
},
cli.DurationFlag{
Name: "resubmission-timeout",
Usage: "Duration we will wait before resubmitting a transaction to L1",
Value: 30 * time.Second,
EnvVar: opservice.PrefixEnvVar(envPrefix, "RESUBMISSION_TIMEOUT"),
},
}, client.CLIFlags(envPrefix)...)
}
type CLIConfig struct {
L1RPCURL string
Mnemonic string
HDPath string
SequencerHDPath string
L2OutputHDPath string
PrivateKey string
SignerCLIConfig client.CLIConfig
NumConfirmations uint64
SafeAbortNonceTooLowCount uint64
ResubmissionTimeout time.Duration
ReceiptQueryInterval time.Duration
}
func (m CLIConfig) Check() error {
if m.L1RPCURL == "" {
return errors.New("must provide a L1 RPC url")
}
if m.NumConfirmations == 0 {
return errors.New("num confirmations must not be 0")
}
if err := m.SignerCLIConfig.Check(); err != nil {
return err
}
return nil
}
func ReadCLIConfig(ctx *cli.Context) CLIConfig {
return CLIConfig{
L1RPCURL: ctx.GlobalString(L1RPCFlagName),
Mnemonic: ctx.GlobalString(MnemonicFlagName),
HDPath: ctx.GlobalString(HDPathFlagName),
SequencerHDPath: ctx.GlobalString(SequencerHDPathFlag.Name),
L2OutputHDPath: ctx.GlobalString(L2OutputHDPathFlag.Name),
PrivateKey: ctx.GlobalString(PrivateKeyFlagName),
SignerCLIConfig: client.ReadCLIConfig(ctx),
NumConfirmations: ctx.GlobalUint64(NumConfirmationsFlagName),
SafeAbortNonceTooLowCount: ctx.GlobalUint64(SafeAbortNonceTooLowCountFlagName),
ResubmissionTimeout: ctx.GlobalDuration(ResubmissionTimeoutFlagName),
}
}
func NewConfig(cfg CLIConfig, l log.Logger) (Config, error) {
if err := cfg.Check(); err != nil {
return Config{}, err
}
networkTimeout := 2 * time.Second
ctx, cancel := context.WithTimeout(context.Background(), networkTimeout)
defer cancel()
l1, err := ethclient.DialContext(ctx, cfg.L1RPCURL)
if err != nil {
return Config{}, err
}
ctx, cancel = context.WithTimeout(context.Background(), networkTimeout)
defer cancel()
chainID, err := l1.ChainID(ctx)
if err != nil {
return Config{}, err
}
hdPath := cfg.HDPath
if hdPath == "" && cfg.SequencerHDPath != "" {
hdPath = cfg.SequencerHDPath
} else if hdPath == "" && cfg.L2OutputHDPath != "" {
hdPath = cfg.L2OutputHDPath
}
signerFactory, from, err := opcrypto.SignerFactoryFromConfig(l, cfg.PrivateKey, cfg.Mnemonic, hdPath, cfg.SignerCLIConfig)
if err != nil {
return Config{}, err
}
receiptQueryInterval := 30 * time.Second
if cfg.ReceiptQueryInterval != 0 {
receiptQueryInterval = cfg.ReceiptQueryInterval
}
return Config{
Backend: l1,
ResubmissionTimeout: cfg.ResubmissionTimeout,
ChainID: chainID,
NetworkTimeout: networkTimeout,
ReceiptQueryInterval: receiptQueryInterval,
NumConfirmations: cfg.NumConfirmations,
SafeAbortNonceTooLowCount: cfg.SafeAbortNonceTooLowCount,
Signer: signerFactory(chainID),
From: from,
}, nil
}
// Config houses parameters for altering the behavior of a SimpleTxManager.
type Config struct {
Backend ETHBackend
// ResubmissionTimeout is the interval at which, if no previously
// published transaction has been mined, the new tx with a bumped gas
// price will be published. Only one publication at MaxGasPrice will be
// attempted.
ResubmissionTimeout time.Duration
// ChainID is the chain ID of the L1 chain.
ChainID *big.Int
// NetworkTimeout is the allowed duration for a single network request.
// This is intended to be used for network requests that can be replayed.
//
// If not set, this will default to 2 seconds.
NetworkTimeout time.Duration
// RequireQueryInterval is the interval at which the tx manager will
// query the backend to check for confirmations after a tx at a
// specific gas price has been published.
ReceiptQueryInterval time.Duration
// NumConfirmations specifies how many blocks are need to consider a
// transaction confirmed.
NumConfirmations uint64
// SafeAbortNonceTooLowCount specifies how many ErrNonceTooLow observations
// are required to give up on a tx at a particular nonce without receiving
// confirmation.
SafeAbortNonceTooLowCount uint64
// Signer is used to sign transactions when the gas price is increased.
Signer opcrypto.SignerFn
From common.Address
}
// Code generated by mockery v2.23.1. DO NOT EDIT.
package mocks
import (
context "context"
common "github.com/ethereum/go-ethereum/common"
mock "github.com/stretchr/testify/mock"
txmgr "github.com/ethereum-optimism/optimism/op-service/txmgr"
types "github.com/ethereum/go-ethereum/core/types"
)
// TxManager is an autogenerated mock type for the TxManager type
type TxManager struct {
mock.Mock
}
// From provides a mock function with given fields:
func (_m *TxManager) From() common.Address {
ret := _m.Called()
var r0 common.Address
if rf, ok := ret.Get(0).(func() common.Address); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(common.Address)
}
}
return r0
}
// Send provides a mock function with given fields: ctx, candidate
func (_m *TxManager) Send(ctx context.Context, candidate txmgr.TxCandidate) (*types.Receipt, error) {
ret := _m.Called(ctx, candidate)
var r0 *types.Receipt
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, txmgr.TxCandidate) (*types.Receipt, error)); ok {
return rf(ctx, candidate)
}
if rf, ok := ret.Get(0).(func(context.Context, txmgr.TxCandidate) *types.Receipt); ok {
r0 = rf(ctx, candidate)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.Receipt)
}
}
if rf, ok := ret.Get(1).(func(context.Context, txmgr.TxCandidate) error); ok {
r1 = rf(ctx, candidate)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
type mockConstructorTestingTNewTxManager interface {
mock.TestingT
Cleanup(func())
}
// NewTxManager creates a new instance of TxManager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewTxManager(t mockConstructorTestingTNewTxManager) *TxManager {
mock := &TxManager{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
package txmgr
import (
"fmt"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
type priceBumpTest struct {
prevGasTip int64
prevBasefee int64
newGasTip int64
newBasefee int64
expectedTip int64
expectedFC int64
}
func (tc *priceBumpTest) run(t *testing.T) {
prevFC := calcGasFeeCap(big.NewInt(tc.prevBasefee), big.NewInt(tc.prevGasTip))
lgr := testlog.Logger(t, log.LvlCrit)
tip, fc := updateFees(big.NewInt(tc.prevGasTip), prevFC, big.NewInt(tc.newGasTip), big.NewInt(tc.newBasefee), lgr)
require.Equal(t, tc.expectedTip, tip.Int64(), "tip must be as expected")
require.Equal(t, tc.expectedFC, fc.Int64(), "fee cap must be as expected")
}
func TestUpdateFees(t *testing.T) {
tests := []priceBumpTest{
{
prevGasTip: 100, prevBasefee: 1000,
newGasTip: 90, newBasefee: 900,
expectedTip: 100, expectedFC: 2100,
},
{
prevGasTip: 100, prevBasefee: 1000,
newGasTip: 101, newBasefee: 1000,
expectedTip: 115, expectedFC: 2415,
},
{
prevGasTip: 100, prevBasefee: 1000,
newGasTip: 100, newBasefee: 1001,
expectedTip: 115, expectedFC: 2415,
},
{
prevGasTip: 100, prevBasefee: 1000,
newGasTip: 101, newBasefee: 900,
expectedTip: 115, expectedFC: 2415,
},
{
prevGasTip: 100, prevBasefee: 1000,
newGasTip: 90, newBasefee: 1010,
expectedTip: 115, expectedFC: 2415,
},
{
prevGasTip: 100, prevBasefee: 1000,
newGasTip: 101, newBasefee: 2000,
expectedTip: 115, expectedFC: 4115,
},
{
prevGasTip: 100, prevBasefee: 1000,
newGasTip: 120, newBasefee: 900,
expectedTip: 120, expectedFC: 2415,
},
{
prevGasTip: 100, prevBasefee: 1000,
newGasTip: 120, newBasefee: 1100,
expectedTip: 120, expectedFC: 2415,
},
{
prevGasTip: 100, prevBasefee: 1000,
newGasTip: 120, newBasefee: 1140,
expectedTip: 120, expectedFC: 2415,
},
{
prevGasTip: 100, prevBasefee: 1000,
newGasTip: 120, newBasefee: 1200,
expectedTip: 120, expectedFC: 2520,
},
}
for i, test := range tests {
i := i
test := test
t.Run(fmt.Sprint(i), test.run)
}
}
...@@ -3,16 +3,16 @@ package txmgr ...@@ -3,16 +3,16 @@ package txmgr
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"math/big" "math/big"
"sync" "sync"
"time" "time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
) )
// Geth defaults the priceBump to 10 // Geth defaults the priceBump to 10
...@@ -23,52 +23,22 @@ const priceBump int64 = 15 ...@@ -23,52 +23,22 @@ const priceBump int64 = 15
var priceBumpPercent = big.NewInt(100 + priceBump) var priceBumpPercent = big.NewInt(100 + priceBump)
var oneHundred = big.NewInt(100) var oneHundred = big.NewInt(100)
// UpdateGasPriceSendTxFunc defines a function signature for publishing a
// desired tx with a specific gas price. Implementations of this signature
// should also return promptly when the context is canceled.
type UpdateGasPriceFunc = func(ctx context.Context) (*types.Transaction, error)
type SendTransactionFunc = func(ctx context.Context, tx *types.Transaction) error
// Config houses parameters for altering the behavior of a SimpleTxManager.
type Config struct {
// ResubmissionTimeout is the interval at which, if no previously
// published transaction has been mined, the new tx with a bumped gas
// price will be published. Only one publication at MaxGasPrice will be
// attempted.
ResubmissionTimeout time.Duration
// RequireQueryInterval is the interval at which the tx manager will
// query the backend to check for confirmations after a tx at a
// specific gas price has been published.
ReceiptQueryInterval time.Duration
// NumConfirmations specifies how many blocks are need to consider a
// transaction confirmed.
NumConfirmations uint64
// SafeAbortNonceTooLowCount specifies how many ErrNonceTooLow observations
// are required to give up on a tx at a particular nonce without receiving
// confirmation.
SafeAbortNonceTooLowCount uint64
// Signer is used to sign transactions when the gas price is increased.
Signer opcrypto.SignerFn
From common.Address
}
// TxManager is an interface that allows callers to reliably publish txs, // TxManager is an interface that allows callers to reliably publish txs,
// bumping the gas price if needed, and obtain the receipt of the resulting tx. // bumping the gas price if needed, and obtain the receipt of the resulting tx.
//
//go:generate mockery --name TxManager --output ./mocks
type TxManager interface { type TxManager interface {
// Send is used to publish a transaction with incrementally higher gas // Send is used to create & send a transaction. It will handle increasing
// prices until the transaction eventually confirms. This method blocks // the gas price & ensuring that the transaction remains in the transaction pool.
// until an invocation of sendTx returns (called with differing gas // It can be stopped by cancelling the provided context; however, the transaction
// prices). The method may be canceled using the passed context. // may be included on L1 even if the context is cancelled.
//
// The initial transaction MUST be signed & ready to submit.
// //
// NOTE: Send should be called by AT MOST one caller at a time. // NOTE: Send should be called by AT MOST one caller at a time.
Send(ctx context.Context, tx *types.Transaction) (*types.Receipt, error) Send(ctx context.Context, candidate TxCandidate) (*types.Receipt, error)
// From returns the sending address associated with the instance of the transaction manager.
// It is static for a single instance of a TxManager.
From() common.Address
} }
// ETHBackend is the set of methods that the transaction manager uses to resubmit gas & determine // ETHBackend is the set of methods that the transaction manager uses to resubmit gas & determine
...@@ -89,122 +59,62 @@ type ETHBackend interface { ...@@ -89,122 +59,62 @@ type ETHBackend interface {
// TODO(CLI-3318): Maybe need a generic interface to support different RPC providers // TODO(CLI-3318): Maybe need a generic interface to support different RPC providers
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
SuggestGasTipCap(ctx context.Context) (*big.Int, error) SuggestGasTipCap(ctx context.Context) (*big.Int, error)
// NonceAt returns the account nonce of the given account.
// The block number can be nil, in which case the nonce is taken from the latest known block.
NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error)
// PendingNonce returns the pending nonce.
PendingNonceAt(ctx context.Context, account common.Address) (uint64, error)
/// EstimateGas returns an estimate of the amount of gas needed to execute the given
/// transaction against the current pending block.
EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error)
} }
// SimpleTxManager is a implementation of TxManager that performs linear fee // SimpleTxManager is a implementation of TxManager that performs linear fee
// bumping of a tx until it confirms. // bumping of a tx until it confirms.
type SimpleTxManager struct { type SimpleTxManager struct {
Config // embed the config directly cfg Config // embed the config directly
name string name string
chainID *big.Int
backend ETHBackend backend ETHBackend
l log.Logger l log.Logger
} }
// IncreaseGasPrice takes the previous transaction & potentially clones then signs it with a higher tip.
// If the tip + basefee suggested by the network are not greater than the previous values, the same transaction
// will be returned. If they are greater, this function will ensure that they are at least greater by 15% than
// the previous transaction's value to ensure that the price bump is large enough.
//
// We do not re-estimate the amount of gas used because for some stateful transactions (like output proposals) the
// act of including the transaction renders the repeat of the transaction invalid.
func (m *SimpleTxManager) IncreaseGasPrice(ctx context.Context, tx *types.Transaction) (*types.Transaction, error) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
var gasTipCap, gasFeeCap *big.Int
if tip, err := m.backend.SuggestGasTipCap(ctx); err != nil {
return nil, err
} else if tip == nil {
return nil, errors.New("the suggested tip was nil")
} else {
gasTipCap = tip
}
// Return the same transaction if we don't update any fields.
// We do this because ethereum signatures are not deterministic and therefore the transaction hash will change
// when we re-sign the tx. We don't want to do that because we want to see ErrAlreadyKnown instead of ErrReplacementUnderpriced
var reusedTip, reusedFeeCap bool
// new = old * (100 + priceBump) / 100
// Enforce a min priceBump on the tip. Do this before the feeCap is calculated
thresholdTip := new(big.Int).Mul(priceBumpPercent, tx.GasTipCap())
thresholdTip = thresholdTip.Div(thresholdTip, oneHundred)
if tx.GasTipCapIntCmp(gasTipCap) >= 0 {
m.l.Debug("Reusing the previous tip", "previous", tx.GasTipCap(), "suggested", gasTipCap)
gasTipCap = tx.GasTipCap()
reusedTip = true
} else if thresholdTip.Cmp(gasTipCap) > 0 {
m.l.Debug("Overriding the tip to enforce a price bump", "previous", tx.GasTipCap(), "suggested", gasTipCap, "new", thresholdTip)
gasTipCap = thresholdTip
}
if head, err := m.backend.HeaderByNumber(ctx, nil); err != nil {
return nil, err
} else if head.BaseFee == nil {
return nil, errors.New("txmgr does not support pre-london blocks that do not have a basefee")
} else {
// CalcGasFeeCap ensure that the fee cap is large enough for the tip.
gasFeeCap = CalcGasFeeCap(head.BaseFee, gasTipCap)
}
// new = old * (100 + priceBump) / 100
// Enforce a min priceBump on the feeCap
thresholdFeeCap := new(big.Int).Mul(priceBumpPercent, tx.GasFeeCap())
thresholdFeeCap = thresholdFeeCap.Div(thresholdFeeCap, oneHundred)
if tx.GasFeeCapIntCmp(gasFeeCap) >= 0 {
if reusedTip {
m.l.Debug("Reusing the previous fee cap", "previous", tx.GasFeeCap(), "suggested", gasFeeCap)
gasFeeCap = tx.GasFeeCap()
reusedFeeCap = true
} else {
m.l.Debug("Overriding the fee cap to enforce a price bump because we increased the tip", "previous", tx.GasFeeCap(), "suggested", gasFeeCap, "new", thresholdFeeCap)
gasFeeCap = thresholdFeeCap
}
} else if thresholdFeeCap.Cmp(gasFeeCap) > 0 {
if reusedTip {
// TODO (CLI-3620): Increase the basefee then recompute the feecap
m.l.Warn("Overriding the fee cap to enforce a price bump without increasing the tip. Will likely result in ErrReplacementUnderpriced",
"previous", tx.GasFeeCap(), "suggested", gasFeeCap, "new", thresholdFeeCap)
} else {
m.l.Debug("Overriding the fee cap to enforce a price bump", "previous", tx.GasFeeCap(), "suggested", gasFeeCap, "new", thresholdFeeCap)
}
gasFeeCap = thresholdFeeCap
}
if reusedTip && reusedFeeCap {
return tx, nil
}
rawTx := &types.DynamicFeeTx{
ChainID: tx.ChainId(),
Nonce: tx.Nonce(),
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Gas: tx.Gas(),
To: tx.To(),
Value: tx.Value(),
Data: tx.Data(),
AccessList: tx.AccessList(),
}
return m.Signer(ctx, m.From, types.NewTx(rawTx))
}
// NewSimpleTxManager initializes a new SimpleTxManager with the passed Config. // NewSimpleTxManager initializes a new SimpleTxManager with the passed Config.
func NewSimpleTxManager(name string, l log.Logger, cfg Config, backend ETHBackend) *SimpleTxManager { func NewSimpleTxManager(name string, l log.Logger, cfg Config) *SimpleTxManager {
if cfg.NumConfirmations == 0 { if cfg.NumConfirmations == 0 {
panic("txmgr: NumConfirmations cannot be zero") panic("txmgr: NumConfirmations cannot be zero")
} }
if cfg.NetworkTimeout == 0 {
cfg.NetworkTimeout = 2 * time.Second
}
return &SimpleTxManager{ return &SimpleTxManager{
chainID: cfg.ChainID,
name: name, name: name,
Config: cfg, cfg: cfg,
backend: backend, backend: cfg.Backend,
l: l.New("service", name), l: l.New("service", name),
} }
} }
func (m *SimpleTxManager) From() common.Address {
return m.cfg.From
}
// TxCandidate is a transaction candidate that can be submitted to ask the
// [TxManager] to construct a transaction with gas price bounds.
type TxCandidate struct {
// TxData is the transaction data to be used in the constructed tx.
TxData []byte
// To is the recipient of the constructed tx.
To common.Address
// GasLimit is the gas limit to be used in the constructed tx.
GasLimit uint64
// From is the sender (or `from`) of the constructed tx.
From common.Address
}
// Send is used to publish a transaction with incrementally higher gas prices // Send is used to publish a transaction with incrementally higher gas prices
// until the transaction eventually confirms. This method blocks until an // until the transaction eventually confirms. This method blocks until an
// invocation of sendTx returns (called with differing gas prices). The method // invocation of sendTx returns (called with differing gas prices). The method
...@@ -215,8 +125,70 @@ func NewSimpleTxManager(name string, l log.Logger, cfg Config, backend ETHBacken ...@@ -215,8 +125,70 @@ func NewSimpleTxManager(name string, l log.Logger, cfg Config, backend ETHBacken
// but retain the gas used, the nonce, and the data. // but retain the gas used, the nonce, and the data.
// //
// NOTE: Send should be called by AT MOST one caller at a time. // NOTE: Send should be called by AT MOST one caller at a time.
func (m *SimpleTxManager) Send(ctx context.Context, tx *types.Transaction) (*types.Receipt, error) { func (m *SimpleTxManager) Send(ctx context.Context, candidate TxCandidate) (*types.Receipt, error) {
tx, err := m.craftTx(ctx, candidate)
if err != nil {
m.l.Error("Failed to create the transaction", "err", err)
return nil, err
}
return m.send(ctx, tx)
}
// craftTx creates the signed transaction
// It queries L1 for the current fee market conditions as well as for the nonce.
// NOTE: This method SHOULD NOT publish the resulting transaction.
// NOTE: If the [TxCandidate.GasLimit] is non-zero, it will be used as the transaction's gas.
// NOTE: Otherwise, the [SimpleTxManager] will query the specified backend for an estimate.
func (m *SimpleTxManager) craftTx(ctx context.Context, candidate TxCandidate) (*types.Transaction, error) {
gasTipCap, basefee, err := m.suggestGasPriceCaps(ctx)
if err != nil {
return nil, err
}
gasFeeCap := calcGasFeeCap(basefee, gasTipCap)
// Fetch the sender's nonce from the latest known block (nil `blockNumber`)
childCtx, cancel := context.WithTimeout(ctx, m.cfg.NetworkTimeout)
defer cancel()
nonce, err := m.backend.NonceAt(childCtx, candidate.From, nil)
if err != nil {
return nil, fmt.Errorf("failed to get nonce: %w", err)
}
rawTx := &types.DynamicFeeTx{
ChainID: m.chainID,
Nonce: nonce,
To: &candidate.To,
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Data: candidate.TxData,
}
m.l.Info("creating tx", "to", rawTx.To, "from", candidate.From)
// If the gas limit is set, we can use that as the gas
if candidate.GasLimit != 0 {
rawTx.Gas = candidate.GasLimit
} else {
// Calculate the intrinsic gas for the transaction
gas, err := m.backend.EstimateGas(ctx, ethereum.CallMsg{
From: candidate.From,
To: &candidate.To,
GasFeeCap: gasFeeCap,
GasTipCap: gasTipCap,
Data: rawTx.Data,
})
if err != nil {
return nil, fmt.Errorf("failed to estimate gas: %w", err)
}
rawTx.Gas = gas
}
ctx, cancel = context.WithTimeout(ctx, m.cfg.NetworkTimeout)
defer cancel()
return m.cfg.Signer(ctx, candidate.From, types.NewTx(rawTx))
}
func (m *SimpleTxManager) send(ctx context.Context, tx *types.Transaction) (*types.Receipt, error) {
// Initialize a wait group to track any spawned goroutines, and ensure // Initialize a wait group to track any spawned goroutines, and ensure
// we properly clean up any dangling resources this method generates. // we properly clean up any dangling resources this method generates.
// We assert that this is the case thoroughly in our unit tests. // We assert that this is the case thoroughly in our unit tests.
...@@ -229,7 +201,7 @@ func (m *SimpleTxManager) Send(ctx context.Context, tx *types.Transaction) (*typ ...@@ -229,7 +201,7 @@ func (m *SimpleTxManager) Send(ctx context.Context, tx *types.Transaction) (*typ
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
defer cancel() defer cancel()
sendState := NewSendState(m.SafeAbortNonceTooLowCount) sendState := NewSendState(m.cfg.SafeAbortNonceTooLowCount)
// Create a closure that will block on submitting the tx in the // Create a closure that will block on submitting the tx in the
// background, returning the first successfully mined receipt back to // background, returning the first successfully mined receipt back to
...@@ -289,7 +261,7 @@ func (m *SimpleTxManager) Send(ctx context.Context, tx *types.Transaction) (*typ ...@@ -289,7 +261,7 @@ func (m *SimpleTxManager) Send(ctx context.Context, tx *types.Transaction) (*typ
wg.Add(1) wg.Add(1)
go sendTxAsync(tx) go sendTxAsync(tx)
ticker := time.NewTicker(m.ResubmissionTimeout) ticker := time.NewTicker(m.cfg.ResubmissionTimeout)
defer ticker.Stop() defer ticker.Stop()
for { for {
...@@ -333,7 +305,7 @@ func (m *SimpleTxManager) Send(ctx context.Context, tx *types.Transaction) (*typ ...@@ -333,7 +305,7 @@ func (m *SimpleTxManager) Send(ctx context.Context, tx *types.Transaction) (*typ
// waitMined implements the core functionality of WaitMined, with the option to // waitMined implements the core functionality of WaitMined, with the option to
// pass in a SendState to record whether or not the transaction is mined. // pass in a SendState to record whether or not the transaction is mined.
func (m *SimpleTxManager) waitMined(ctx context.Context, tx *types.Transaction, sendState *SendState) (*types.Receipt, error) { func (m *SimpleTxManager) waitMined(ctx context.Context, tx *types.Transaction, sendState *SendState) (*types.Receipt, error) {
queryTicker := time.NewTicker(m.ReceiptQueryInterval) queryTicker := time.NewTicker(m.cfg.ReceiptQueryInterval)
defer queryTicker.Stop() defer queryTicker.Stop()
txHash := tx.Hash() txHash := tx.Hash()
...@@ -354,7 +326,7 @@ func (m *SimpleTxManager) waitMined(ctx context.Context, tx *types.Transaction, ...@@ -354,7 +326,7 @@ func (m *SimpleTxManager) waitMined(ctx context.Context, tx *types.Transaction,
} }
m.l.Debug("Transaction mined, checking confirmations", "txHash", txHash, "txHeight", txHeight, m.l.Debug("Transaction mined, checking confirmations", "txHash", txHash, "txHeight", txHeight,
"tipHeight", tipHeight, "numConfirmations", m.NumConfirmations) "tipHeight", tipHeight, "numConfirmations", m.cfg.NumConfirmations)
// The transaction is considered confirmed when // The transaction is considered confirmed when
// txHeight+numConfirmations-1 <= tipHeight. Note that the -1 is // txHeight+numConfirmations-1 <= tipHeight. Note that the -1 is
...@@ -363,13 +335,13 @@ func (m *SimpleTxManager) waitMined(ctx context.Context, tx *types.Transaction, ...@@ -363,13 +335,13 @@ func (m *SimpleTxManager) waitMined(ctx context.Context, tx *types.Transaction,
// transaction should be confirmed when txHeight is equal to // transaction should be confirmed when txHeight is equal to
// tipHeight. The equation is rewritten in this form to avoid // tipHeight. The equation is rewritten in this form to avoid
// underflows. // underflows.
if txHeight+m.NumConfirmations <= tipHeight+1 { if txHeight+m.cfg.NumConfirmations <= tipHeight+1 {
m.l.Info("Transaction confirmed", "txHash", txHash) m.l.Info("Transaction confirmed", "txHash", txHash)
return receipt, nil return receipt, nil
} }
// Safe to subtract since we know the LHS above is greater. // Safe to subtract since we know the LHS above is greater.
confsRemaining := (txHeight + m.NumConfirmations) - (tipHeight + 1) confsRemaining := (txHeight + m.cfg.NumConfirmations) - (tipHeight + 1)
m.l.Debug("Transaction not yet confirmed", "txHash", txHash, "confsRemaining", confsRemaining) m.l.Debug("Transaction not yet confirmed", "txHash", txHash, "confsRemaining", confsRemaining)
case err != nil: case err != nil:
...@@ -391,11 +363,106 @@ func (m *SimpleTxManager) waitMined(ctx context.Context, tx *types.Transaction, ...@@ -391,11 +363,106 @@ func (m *SimpleTxManager) waitMined(ctx context.Context, tx *types.Transaction,
} }
} }
// CalcGasFeeCap deterministically computes the recommended gas fee cap given // suggestGasPriceCaps suggests what the new tip & new basefee should be based on the current L1 conditions
func (m *SimpleTxManager) suggestGasPriceCaps(ctx context.Context) (*big.Int, *big.Int, error) {
cCtx, cancel := context.WithTimeout(ctx, m.cfg.NetworkTimeout)
defer cancel()
tip, err := m.backend.SuggestGasTipCap(cCtx)
if err != nil {
return nil, nil, fmt.Errorf("failed to fetch the suggested gas tip cap: %w", err)
} else if tip == nil {
return nil, nil, errors.New("the suggested tip was nil")
}
cCtx, cancel = context.WithTimeout(ctx, m.cfg.NetworkTimeout)
defer cancel()
head, err := m.backend.HeaderByNumber(cCtx, nil)
if err != nil {
return nil, nil, fmt.Errorf("failed to fetch the suggested basefee: %w", err)
} else if head.BaseFee == nil {
return nil, nil, errors.New("txmgr does not support pre-london blocks that do not have a basefee")
}
return tip, head.BaseFee, nil
}
// IncreaseGasPrice takes the previous transaction & potentially clones then signs it with a higher tip.
// If the tip + basefee suggested by the network are not greater than the previous values, the same transaction
// will be returned. If they are greater, this function will ensure that they are at least greater by 15% than
// the previous transaction's value to ensure that the price bump is large enough.
//
// We do not re-estimate the amount of gas used because for some stateful transactions (like output proposals) the
// act of including the transaction renders the repeat of the transaction invalid.
func (m *SimpleTxManager) IncreaseGasPrice(ctx context.Context, tx *types.Transaction) (*types.Transaction, error) {
tip, basefee, err := m.suggestGasPriceCaps(ctx)
if err != nil {
return nil, err
}
gasTipCap, gasFeeCap := updateFees(tx.GasTipCap(), tx.GasFeeCap(), tip, basefee, m.l)
if tx.GasTipCapIntCmp(gasTipCap) == 0 && tx.GasFeeCapIntCmp(gasFeeCap) == 0 {
return tx, nil
}
rawTx := &types.DynamicFeeTx{
ChainID: tx.ChainId(),
Nonce: tx.Nonce(),
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Gas: tx.Gas(),
To: tx.To(),
Value: tx.Value(),
Data: tx.Data(),
AccessList: tx.AccessList(),
}
return m.cfg.Signer(ctx, m.cfg.From, types.NewTx(rawTx))
}
// calcThresholdValue returns x * priceBumpPercent / 100
func calcThresholdValue(x *big.Int) *big.Int {
threshold := new(big.Int).Mul(priceBumpPercent, x)
threshold = threshold.Div(threshold, oneHundred)
return threshold
}
// updateFees takes the old tip/basefee & the new tip/basefee and then suggests
// a gasTipCap and gasFeeCap that satisfies geth's required fee bumps
// Geth: FC and Tip must be bumped if any increase
func updateFees(oldTip, oldFeeCap, newTip, newBaseFee *big.Int, lgr log.Logger) (*big.Int, *big.Int) {
newFeeCap := calcGasFeeCap(newBaseFee, newTip)
lgr = lgr.New("old_tip", oldTip, "old_feecap", oldFeeCap, "new_tip", newTip, "new_feecap", newFeeCap)
// If the new prices are less than the old price, reuse the old prices
if oldTip.Cmp(newTip) >= 0 && oldFeeCap.Cmp(newFeeCap) >= 0 {
lgr.Debug("Reusing old tip and feecap")
return oldTip, oldFeeCap
}
// Determine if we need to increase the suggested values
thresholdTip := calcThresholdValue(oldTip)
thresholdFeeCap := calcThresholdValue(oldFeeCap)
if newTip.Cmp(thresholdTip) >= 0 && newFeeCap.Cmp(thresholdFeeCap) >= 0 {
lgr.Debug("Using new tip and feecap")
return newTip, newFeeCap
} else if newTip.Cmp(thresholdTip) >= 0 && newFeeCap.Cmp(thresholdFeeCap) < 0 {
// Tip has gone up, but basefee is flat or down.
// TODO(CLI-3714): Do we need to recalculate the FC here?
lgr.Debug("Using new tip and threshold feecap")
return newTip, thresholdFeeCap
} else if newTip.Cmp(thresholdTip) < 0 && newFeeCap.Cmp(thresholdFeeCap) >= 0 {
// Basefee has gone up, but the tip hasn't. Recalculate the feecap because if the tip went up a lot
// not enough of the feecap may be dedicated to paying the basefee.
lgr.Debug("Using threshold tip and recalculated feecap")
return thresholdTip, calcGasFeeCap(newBaseFee, thresholdTip)
} else {
// TODO(CLI-3713): Should we skip the bump in this case?
lgr.Debug("Using threshold tip and threshold feecap")
return thresholdTip, thresholdFeeCap
}
}
// calcGasFeeCap deterministically computes the recommended gas fee cap given
// the base fee and gasTipCap. The resulting gasFeeCap is equal to: // the base fee and gasTipCap. The resulting gasFeeCap is equal to:
// //
// gasTipCap + 2*baseFee. // gasTipCap + 2*baseFee.
func CalcGasFeeCap(baseFee, gasTipCap *big.Int) *big.Int { func calcGasFeeCap(baseFee, gasTipCap *big.Int) *big.Int {
return new(big.Int).Add( return new(big.Int).Add(
gasTipCap, gasTipCap,
new(big.Int).Mul(baseFee, big.NewInt(2)), new(big.Int).Mul(baseFee, big.NewInt(2)),
......
...@@ -4,7 +4,6 @@ import ( ...@@ -4,7 +4,6 @@ import (
"context" "context"
"errors" "errors"
"math/big" "math/big"
"math/rand"
"sync" "sync"
"testing" "testing"
"time" "time"
...@@ -12,15 +11,15 @@ import ( ...@@ -12,15 +11,15 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-node/testlog" "github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum-optimism/optimism/op-node/testutils" "github.com/ethereum/go-ethereum"
opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
type sendTransactionFunc func(ctx context.Context, tx *types.Transaction) error
// testHarness houses the necessary resources to test the SimpleTxManager. // testHarness houses the necessary resources to test the SimpleTxManager.
type testHarness struct { type testHarness struct {
cfg Config cfg Config
...@@ -34,7 +33,8 @@ type testHarness struct { ...@@ -34,7 +33,8 @@ type testHarness struct {
func newTestHarnessWithConfig(t *testing.T, cfg Config) *testHarness { func newTestHarnessWithConfig(t *testing.T, cfg Config) *testHarness {
g := newGasPricer(3) g := newGasPricer(3)
backend := newMockBackend(g) backend := newMockBackend(g)
mgr := NewSimpleTxManager("TEST", testlog.Logger(t, log.LvlCrit), cfg, backend) cfg.Backend = backend
mgr := NewSimpleTxManager("TEST", testlog.Logger(t, log.LvlCrit), cfg)
return &testHarness{ return &testHarness{
cfg: cfg, cfg: cfg,
...@@ -50,6 +50,18 @@ func newTestHarness(t *testing.T) *testHarness { ...@@ -50,6 +50,18 @@ func newTestHarness(t *testing.T) *testHarness {
return newTestHarnessWithConfig(t, configWithNumConfs(1)) return newTestHarnessWithConfig(t, configWithNumConfs(1))
} }
// createTxCandidate creates a mock [TxCandidate].
func (h testHarness) createTxCandidate() TxCandidate {
inbox := common.HexToAddress("0x42000000000000000000000000000000000000ff")
sender := common.HexToAddress("0xdeadbeef")
return TxCandidate{
To: inbox,
TxData: []byte{0x00, 0x01, 0x02},
From: sender,
GasLimit: uint64(1337),
}
}
func configWithNumConfs(numConfirmations uint64) Config { func configWithNumConfs(numConfirmations uint64) Config {
return Config{ return Config{
ResubmissionTimeout: time.Second, ResubmissionTimeout: time.Second,
...@@ -91,7 +103,7 @@ func (g *gasPricer) shouldMine(gasFeeCap *big.Int) bool { ...@@ -91,7 +103,7 @@ func (g *gasPricer) shouldMine(gasFeeCap *big.Int) bool {
func (g *gasPricer) feesForEpoch(epoch int64) (*big.Int, *big.Int) { func (g *gasPricer) feesForEpoch(epoch int64) (*big.Int, *big.Int) {
epochBaseFee := new(big.Int).Mul(g.baseBaseFee, big.NewInt(epoch)) epochBaseFee := new(big.Int).Mul(g.baseBaseFee, big.NewInt(epoch))
epochGasTipCap := new(big.Int).Mul(g.baseGasTipFee, big.NewInt(epoch)) epochGasTipCap := new(big.Int).Mul(g.baseGasTipFee, big.NewInt(epoch))
epochGasFeeCap := CalcGasFeeCap(epochBaseFee, epochGasTipCap) epochGasFeeCap := calcGasFeeCap(epochBaseFee, epochGasTipCap)
return epochGasTipCap, epochGasFeeCap return epochGasTipCap, epochGasFeeCap
} }
...@@ -123,7 +135,7 @@ type mockBackend struct { ...@@ -123,7 +135,7 @@ type mockBackend struct {
mu sync.RWMutex mu sync.RWMutex
g *gasPricer g *gasPricer
send SendTransactionFunc send sendTransactionFunc
// blockHeight tracks the current height of the chain. // blockHeight tracks the current height of the chain.
blockHeight uint64 blockHeight uint64
...@@ -140,8 +152,8 @@ func newMockBackend(g *gasPricer) *mockBackend { ...@@ -140,8 +152,8 @@ func newMockBackend(g *gasPricer) *mockBackend {
} }
} }
// setTxSender sets the implementation for the SendTransactionFunction // setTxSender sets the implementation for the sendTransactionFunction
func (b *mockBackend) setTxSender(s SendTransactionFunc) { func (b *mockBackend) setTxSender(s sendTransactionFunc) {
b.send = s b.send = s
} }
...@@ -175,6 +187,10 @@ func (b *mockBackend) HeaderByNumber(ctx context.Context, number *big.Int) (*typ ...@@ -175,6 +187,10 @@ func (b *mockBackend) HeaderByNumber(ctx context.Context, number *big.Int) (*typ
}, nil }, nil
} }
func (b *mockBackend) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) {
return b.g.basefee().Uint64(), nil
}
func (b *mockBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { func (b *mockBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
tip, _ := b.g.sample() tip, _ := b.g.sample()
return tip, nil return tip, nil
...@@ -185,7 +201,18 @@ func (b *mockBackend) SendTransaction(ctx context.Context, tx *types.Transaction ...@@ -185,7 +201,18 @@ func (b *mockBackend) SendTransaction(ctx context.Context, tx *types.Transaction
panic("set sender function was not set") panic("set sender function was not set")
} }
return b.send(ctx, tx) return b.send(ctx, tx)
}
func (b *mockBackend) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) {
return 0, nil
}
func (b *mockBackend) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) {
return 0, nil
}
func (*mockBackend) ChainID(ctx context.Context) (*big.Int, error) {
return big.NewInt(1), nil
} }
// TransactionReceipt queries the mockBackend for a mined txHash. If none is // TransactionReceipt queries the mockBackend for a mined txHash. If none is
...@@ -237,7 +264,7 @@ func TestTxMgrConfirmAtMinGasPrice(t *testing.T) { ...@@ -237,7 +264,7 @@ func TestTxMgrConfirmAtMinGasPrice(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() defer cancel()
receipt, err := h.mgr.Send(ctx, tx) receipt, err := h.mgr.send(ctx, tx)
require.Nil(t, err) require.Nil(t, err)
require.NotNil(t, receipt) require.NotNil(t, receipt)
require.Equal(t, gasPricer.expGasFeeCap().Uint64(), receipt.GasUsed) require.Equal(t, gasPricer.expGasFeeCap().Uint64(), receipt.GasUsed)
...@@ -265,7 +292,7 @@ func TestTxMgrNeverConfirmCancel(t *testing.T) { ...@@ -265,7 +292,7 @@ func TestTxMgrNeverConfirmCancel(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() defer cancel()
receipt, err := h.mgr.Send(ctx, tx) receipt, err := h.mgr.send(ctx, tx)
require.Equal(t, err, context.DeadlineExceeded) require.Equal(t, err, context.DeadlineExceeded)
require.Nil(t, receipt) require.Nil(t, receipt)
} }
...@@ -294,7 +321,7 @@ func TestTxMgrConfirmsAtHigherGasPrice(t *testing.T) { ...@@ -294,7 +321,7 @@ func TestTxMgrConfirmsAtHigherGasPrice(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() defer cancel()
receipt, err := h.mgr.Send(ctx, tx) receipt, err := h.mgr.send(ctx, tx)
require.Nil(t, err) require.Nil(t, err)
require.NotNil(t, receipt) require.NotNil(t, receipt)
require.Equal(t, h.gasPricer.expGasFeeCap().Uint64(), receipt.GasUsed) require.Equal(t, h.gasPricer.expGasFeeCap().Uint64(), receipt.GasUsed)
...@@ -325,11 +352,56 @@ func TestTxMgrBlocksOnFailingRpcCalls(t *testing.T) { ...@@ -325,11 +352,56 @@ func TestTxMgrBlocksOnFailingRpcCalls(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() defer cancel()
receipt, err := h.mgr.Send(ctx, tx) receipt, err := h.mgr.send(ctx, tx)
require.Equal(t, err, context.DeadlineExceeded) require.Equal(t, err, context.DeadlineExceeded)
require.Nil(t, receipt) require.Nil(t, receipt)
} }
// TestTxMgr_CraftTx ensures that the tx manager will create transactions as expected.
func TestTxMgr_CraftTx(t *testing.T) {
t.Parallel()
h := newTestHarness(t)
candidate := h.createTxCandidate()
// Craft the transaction.
gasTipCap, gasFeeCap := h.gasPricer.feesForEpoch(h.gasPricer.epoch + 1)
tx, err := h.mgr.craftTx(context.Background(), candidate)
require.Nil(t, err)
require.NotNil(t, tx)
// Validate the gas tip cap and fee cap.
require.Equal(t, gasTipCap, tx.GasTipCap())
require.Equal(t, gasFeeCap, tx.GasFeeCap())
// Validate the nonce was set correctly using the backend.
require.Zero(t, tx.Nonce())
// Check that the gas was set using the gas limit.
require.Equal(t, candidate.GasLimit, tx.Gas())
}
// TestTxMgr_EstimateGas ensures that the tx manager will estimate
// the gas when candidate gas limit is zero in [CraftTx].
func TestTxMgr_EstimateGas(t *testing.T) {
t.Parallel()
h := newTestHarness(t)
candidate := h.createTxCandidate()
// Set the gas limit to zero to trigger gas estimation.
candidate.GasLimit = 0
// Gas estimate
gasEstimate := h.gasPricer.baseBaseFee.Uint64()
// Craft the transaction.
tx, err := h.mgr.craftTx(context.Background(), candidate)
require.Nil(t, err)
require.NotNil(t, tx)
// Check that the gas was estimated correctly.
require.Equal(t, gasEstimate, tx.Gas())
}
// TestTxMgrOnlyOnePublicationSucceeds asserts that the tx manager will return a // TestTxMgrOnlyOnePublicationSucceeds asserts that the tx manager will return a
// receipt so long as at least one of the publications is able to succeed with a // receipt so long as at least one of the publications is able to succeed with a
// simulated rpc failure. // simulated rpc failure.
...@@ -358,7 +430,7 @@ func TestTxMgrOnlyOnePublicationSucceeds(t *testing.T) { ...@@ -358,7 +430,7 @@ func TestTxMgrOnlyOnePublicationSucceeds(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() defer cancel()
receipt, err := h.mgr.Send(ctx, tx) receipt, err := h.mgr.send(ctx, tx)
require.Nil(t, err) require.Nil(t, err)
require.NotNil(t, receipt) require.NotNil(t, receipt)
...@@ -393,7 +465,7 @@ func TestTxMgrConfirmsMinGasPriceAfterBumping(t *testing.T) { ...@@ -393,7 +465,7 @@ func TestTxMgrConfirmsMinGasPriceAfterBumping(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() defer cancel()
receipt, err := h.mgr.Send(ctx, tx) receipt, err := h.mgr.send(ctx, tx)
require.Nil(t, err) require.Nil(t, err)
require.NotNil(t, receipt) require.NotNil(t, receipt)
require.Equal(t, h.gasPricer.expGasFeeCap().Uint64(), receipt.GasUsed) require.Equal(t, h.gasPricer.expGasFeeCap().Uint64(), receipt.GasUsed)
...@@ -438,7 +510,7 @@ func TestTxMgrDoesntAbortNonceTooLowAfterMiningTx(t *testing.T) { ...@@ -438,7 +510,7 @@ func TestTxMgrDoesntAbortNonceTooLowAfterMiningTx(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() defer cancel()
receipt, err := h.mgr.Send(ctx, tx) receipt, err := h.mgr.send(ctx, tx)
require.Nil(t, err) require.Nil(t, err)
require.NotNil(t, receipt) require.NotNil(t, receipt)
require.Equal(t, h.gasPricer.expGasFeeCap().Uint64(), receipt.GasUsed) require.Equal(t, h.gasPricer.expGasFeeCap().Uint64(), receipt.GasUsed)
...@@ -577,6 +649,22 @@ func (b *failingBackend) SuggestGasTipCap(_ context.Context) (*big.Int, error) { ...@@ -577,6 +649,22 @@ func (b *failingBackend) SuggestGasTipCap(_ context.Context) (*big.Int, error) {
return b.gasTip, nil return b.gasTip, nil
} }
func (b *failingBackend) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) {
return b.baseFee.Uint64(), nil
}
func (b *failingBackend) NonceAt(_ context.Context, _ common.Address, _ *big.Int) (uint64, error) {
return 0, errors.New("unimplemented")
}
func (b *failingBackend) PendingNonceAt(_ context.Context, _ common.Address) (uint64, error) {
return 0, errors.New("unimplemented")
}
func (b *failingBackend) ChainID(ctx context.Context) (*big.Int, error) {
return nil, errors.New("unimplemented")
}
// TestWaitMinedReturnsReceiptAfterFailure asserts that WaitMined is able to // TestWaitMinedReturnsReceiptAfterFailure asserts that WaitMined is able to
// recover from failed calls to the backend. It uses the failedBackend to // recover from failed calls to the backend. It uses the failedBackend to
// simulate an rpc call failure, followed by the successful return of a receipt. // simulate an rpc call failure, followed by the successful return of a receipt.
...@@ -586,7 +674,7 @@ func TestWaitMinedReturnsReceiptAfterFailure(t *testing.T) { ...@@ -586,7 +674,7 @@ func TestWaitMinedReturnsReceiptAfterFailure(t *testing.T) {
var borkedBackend failingBackend var borkedBackend failingBackend
mgr := &SimpleTxManager{ mgr := &SimpleTxManager{
Config: Config{ cfg: Config{
ResubmissionTimeout: time.Second, ResubmissionTimeout: time.Second,
ReceiptQueryInterval: 50 * time.Millisecond, ReceiptQueryInterval: 50 * time.Millisecond,
NumConfirmations: 1, NumConfirmations: 1,
...@@ -610,19 +698,14 @@ func TestWaitMinedReturnsReceiptAfterFailure(t *testing.T) { ...@@ -610,19 +698,14 @@ func TestWaitMinedReturnsReceiptAfterFailure(t *testing.T) {
require.Equal(t, receipt.TxHash, txHash) require.Equal(t, receipt.TxHash, txHash)
} }
// TestIncreaseGasPriceEnforcesMinBump asserts that if the suggest gas tip func doGasPriceIncrease(t *testing.T, txTipCap, txFeeCap, newTip, newBaseFee int64) (*types.Transaction, *types.Transaction) {
// returned from L1 is less than the required price bump the price bump is
// used instead.
func TestIncreaseGasPriceEnforcesMinBump(t *testing.T) {
t.Parallel()
borkedBackend := failingBackend{ borkedBackend := failingBackend{
gasTip: big.NewInt(101), gasTip: big.NewInt(newTip),
baseFee: big.NewInt(460), baseFee: big.NewInt(newBaseFee),
} }
mgr := &SimpleTxManager{ mgr := &SimpleTxManager{
Config: Config{ cfg: Config{
ResubmissionTimeout: time.Second, ResubmissionTimeout: time.Second,
ReceiptQueryInterval: 50 * time.Millisecond, ReceiptQueryInterval: 50 * time.Millisecond,
NumConfirmations: 1, NumConfirmations: 1,
...@@ -634,97 +717,84 @@ func TestIncreaseGasPriceEnforcesMinBump(t *testing.T) { ...@@ -634,97 +717,84 @@ func TestIncreaseGasPriceEnforcesMinBump(t *testing.T) {
}, },
name: "TEST", name: "TEST",
backend: &borkedBackend, backend: &borkedBackend,
l: testlog.Logger(t, log.LvlTrace), l: testlog.Logger(t, log.LvlCrit),
} }
tx := types.NewTx(&types.DynamicFeeTx{ tx := types.NewTx(&types.DynamicFeeTx{
GasTipCap: big.NewInt(100), GasTipCap: big.NewInt(txTipCap),
GasFeeCap: big.NewInt(1000), GasFeeCap: big.NewInt(txFeeCap),
}) })
newTx, err := mgr.IncreaseGasPrice(context.Background(), tx)
ctx := context.Background()
newTx, err := mgr.IncreaseGasPrice(ctx, tx)
require.NoError(t, err) require.NoError(t, err)
return tx, newTx
}
func TestIncreaseGasPrice(t *testing.T) {
// t.Parallel()
tests := []struct {
name string
run func(t *testing.T)
}{
{
name: "enforces min bump",
run: func(t *testing.T) {
tx, newTx := doGasPriceIncrease(t, 100, 1000, 101, 460)
require.True(t, newTx.GasFeeCap().Cmp(tx.GasFeeCap()) > 0, "new tx fee cap must be larger") require.True(t, newTx.GasFeeCap().Cmp(tx.GasFeeCap()) > 0, "new tx fee cap must be larger")
require.True(t, newTx.GasTipCap().Cmp(tx.GasTipCap()) > 0, "new tx tip must be larger") require.True(t, newTx.GasTipCap().Cmp(tx.GasTipCap()) > 0, "new tx tip must be larger")
}
// TestIncreaseGasPriceEnforcesMinBumpForBothOnTipIncrease asserts that if the gasTip goes up,
// but the baseFee doesn't, both values are increased by 10%
func TestIncreaseGasPriceEnforcesMinBumpForBothOnTipIncrease(t *testing.T) {
t.Parallel()
borkedBackend := failingBackend{
gasTip: big.NewInt(101),
baseFee: big.NewInt(440),
}
mgr := &SimpleTxManager{
Config: Config{
ResubmissionTimeout: time.Second,
ReceiptQueryInterval: 50 * time.Millisecond,
NumConfirmations: 1,
SafeAbortNonceTooLowCount: 3,
Signer: func(ctx context.Context, from common.Address, tx *types.Transaction) (*types.Transaction, error) {
return tx, nil
}, },
From: common.Address{},
}, },
name: "TEST", {
backend: &borkedBackend, name: "enforces min bump on only tip incrase",
l: testlog.Logger(t, log.LvlCrit), run: func(t *testing.T) {
} tx, newTx := doGasPriceIncrease(t, 100, 1000, 101, 440)
tx := types.NewTx(&types.DynamicFeeTx{
GasTipCap: big.NewInt(100),
GasFeeCap: big.NewInt(1000),
})
ctx := context.Background()
newTx, err := mgr.IncreaseGasPrice(ctx, tx)
require.NoError(t, err)
require.True(t, newTx.GasFeeCap().Cmp(tx.GasFeeCap()) > 0, "new tx fee cap must be larger") require.True(t, newTx.GasFeeCap().Cmp(tx.GasFeeCap()) > 0, "new tx fee cap must be larger")
require.True(t, newTx.GasTipCap().Cmp(tx.GasTipCap()) > 0, "new tx tip must be larger") require.True(t, newTx.GasTipCap().Cmp(tx.GasTipCap()) > 0, "new tx tip must be larger")
}
// TestIncreaseGasPriceEnforcesMinBumpForBothOnBaseFeeIncrease asserts that if the baseFee goes up,
// but the tip doesn't, both values are increased by 10%
// TODO(CLI-3620): This test will fail until we implemented CLI-3620.
func TestIncreaseGasPriceEnforcesMinBumpForBothOnBaseFeeIncrease(t *testing.T) {
t.Skip("Failing until CLI-3620 is implemented")
t.Parallel()
borkedBackend := failingBackend{
gasTip: big.NewInt(99),
baseFee: big.NewInt(460),
}
mgr := &SimpleTxManager{
Config: Config{
ResubmissionTimeout: time.Second,
ReceiptQueryInterval: 50 * time.Millisecond,
NumConfirmations: 1,
SafeAbortNonceTooLowCount: 3,
Signer: func(ctx context.Context, from common.Address, tx *types.Transaction) (*types.Transaction, error) {
return tx, nil
}, },
From: common.Address{},
}, },
name: "TEST", {
backend: &borkedBackend, name: "enforces min bump on only basefee incrase",
l: testlog.Logger(t, log.LvlCrit), run: func(t *testing.T) {
} tx, newTx := doGasPriceIncrease(t, 100, 1000, 99, 460)
tx := types.NewTx(&types.DynamicFeeTx{
GasTipCap: big.NewInt(100),
GasFeeCap: big.NewInt(1000),
})
ctx := context.Background()
newTx, err := mgr.IncreaseGasPrice(ctx, tx)
require.NoError(t, err)
require.True(t, newTx.GasFeeCap().Cmp(tx.GasFeeCap()) > 0, "new tx fee cap must be larger") require.True(t, newTx.GasFeeCap().Cmp(tx.GasFeeCap()) > 0, "new tx fee cap must be larger")
require.True(t, newTx.GasTipCap().Cmp(tx.GasTipCap()) > 0, "new tx tip must be larger") require.True(t, newTx.GasTipCap().Cmp(tx.GasTipCap()) > 0, "new tx tip must be larger")
},
},
{
name: "uses L1 values when larger",
run: func(t *testing.T) {
_, newTx := doGasPriceIncrease(t, 10, 100, 50, 200)
require.True(t, newTx.GasFeeCap().Cmp(big.NewInt(450)) == 0, "new tx fee cap must be equal L1")
require.True(t, newTx.GasTipCap().Cmp(big.NewInt(50)) == 0, "new tx tip must be equal L1")
},
},
{
name: "uses L1 tip when larger and threshold FC",
run: func(t *testing.T) {
_, newTx := doGasPriceIncrease(t, 100, 2200, 120, 1050)
require.True(t, newTx.GasTipCap().Cmp(big.NewInt(120)) == 0, "new tx tip must be equal L1")
require.True(t, newTx.GasFeeCap().Cmp(big.NewInt(2530)) == 0, "new tx fee cap must be equal to the threshold value")
},
},
{
name: "uses L1 FC when larger and threshold tip",
run: func(t *testing.T) {
_, newTx := doGasPriceIncrease(t, 100, 2200, 100, 2000)
require.True(t, newTx.GasTipCap().Cmp(big.NewInt(115)) == 0, "new tx tip must be equal the threshold value")
require.True(t, newTx.GasFeeCap().Cmp(big.NewInt(4115)) == 0, "new tx fee cap must be equal L1")
},
},
{
name: "reuses tx when no bump",
run: func(t *testing.T) {
tx, newTx := doGasPriceIncrease(t, 10, 100, 10, 45)
require.Equal(t, tx.Hash(), newTx.Hash(), "tx hash must be the same")
},
},
}
for _, test := range tests {
test := test
t.Run(test.name, test.run)
}
} }
// TestIncreaseGasPriceNotExponential asserts that if the L1 basefee & tip remain the // TestIncreaseGasPriceNotExponential asserts that if the L1 basefee & tip remain the
...@@ -736,10 +806,10 @@ func TestIncreaseGasPriceNotExponential(t *testing.T) { ...@@ -736,10 +806,10 @@ func TestIncreaseGasPriceNotExponential(t *testing.T) {
gasTip: big.NewInt(10), gasTip: big.NewInt(10),
baseFee: big.NewInt(45), baseFee: big.NewInt(45),
} }
feeCap := CalcGasFeeCap(borkedBackend.baseFee, borkedBackend.gasTip) feeCap := calcGasFeeCap(borkedBackend.baseFee, borkedBackend.gasTip)
mgr := &SimpleTxManager{ mgr := &SimpleTxManager{
Config: Config{ cfg: Config{
ResubmissionTimeout: time.Second, ResubmissionTimeout: time.Second,
ReceiptQueryInterval: 50 * time.Millisecond, ReceiptQueryInterval: 50 * time.Millisecond,
NumConfirmations: 1, NumConfirmations: 1,
...@@ -769,82 +839,3 @@ func TestIncreaseGasPriceNotExponential(t *testing.T) { ...@@ -769,82 +839,3 @@ func TestIncreaseGasPriceNotExponential(t *testing.T) {
} }
} }
// TestIncreaseGasPriceUseLargeIncrease asserts that if the suggest gas tip
// returned from L1 is much larger than the required price bump the L1 value
// is used instead of the price bump
func TestIncreaseGasPriceUseLargeIncrease(t *testing.T) {
t.Parallel()
borkedBackend := failingBackend{
gasTip: big.NewInt(50),
baseFee: big.NewInt(200),
}
feeCap := CalcGasFeeCap(borkedBackend.baseFee, borkedBackend.gasTip)
mgr := &SimpleTxManager{
Config: Config{
ResubmissionTimeout: time.Second,
ReceiptQueryInterval: 50 * time.Millisecond,
NumConfirmations: 1,
SafeAbortNonceTooLowCount: 3,
Signer: func(ctx context.Context, from common.Address, tx *types.Transaction) (*types.Transaction, error) {
return tx, nil
},
From: common.Address{},
},
name: "TEST",
backend: &borkedBackend,
l: testlog.Logger(t, log.LvlCrit),
}
tx := types.NewTx(&types.DynamicFeeTx{
GasTipCap: big.NewInt(10),
GasFeeCap: big.NewInt(100),
})
ctx := context.Background()
newTx, err := mgr.IncreaseGasPrice(ctx, tx)
require.NoError(t, err)
require.True(t, newTx.GasFeeCap().Cmp(feeCap) == 0, "new tx fee cap must be equal L1")
require.True(t, newTx.GasTipCap().Cmp(borkedBackend.gasTip) == 0, "new tx tip must be equal L1")
}
// TestIncreaseGasPriceReusesTransaction asserts that if the L1 basefee & tip remain the
// same, the transaction is returned with the same signature values. The means that the error
// when submitting the transaction to the network is ErrAlreadyKnown instead of ErrReplacementUnderpriced
func TestIncreaseGasPriceReusesTransaction(t *testing.T) {
t.Parallel()
borkedBackend := failingBackend{
gasTip: big.NewInt(10),
baseFee: big.NewInt(45),
}
pk := testutils.InsecureRandomKey(rand.New(rand.NewSource(123)))
signer := opcrypto.PrivateKeySignerFn(pk, big.NewInt(10))
mgr := &SimpleTxManager{
Config: Config{
ResubmissionTimeout: time.Second,
ReceiptQueryInterval: 50 * time.Millisecond,
NumConfirmations: 1,
SafeAbortNonceTooLowCount: 3,
Signer: func(ctx context.Context, from common.Address, tx *types.Transaction) (*types.Transaction, error) {
return signer(from, tx)
},
From: crypto.PubkeyToAddress(pk.PublicKey),
},
name: "TEST",
backend: &borkedBackend,
l: testlog.Logger(t, log.LvlCrit),
}
tx := types.NewTx(&types.DynamicFeeTx{
GasTipCap: big.NewInt(10),
GasFeeCap: big.NewInt(100),
})
ctx := context.Background()
newTx, err := mgr.IncreaseGasPrice(ctx, tx)
require.NoError(t, err)
require.Equal(t, tx.Hash(), newTx.Hash())
}
...@@ -61,6 +61,8 @@ services: ...@@ -61,6 +61,8 @@ services:
--p2p.listen.ip=0.0.0.0 --p2p.listen.ip=0.0.0.0
--p2p.listen.tcp=9003 --p2p.listen.tcp=9003
--p2p.listen.udp=9003 --p2p.listen.udp=9003
--p2p.scoring.peers=light
--p2p.ban.peers=true
--snapshotlog.file=/op_log/snapshot.log --snapshotlog.file=/op_log/snapshot.log
--p2p.priv.path=/config/p2p-node-key.txt --p2p.priv.path=/config/p2p-node-key.txt
--metrics.enabled --metrics.enabled
...@@ -121,6 +123,8 @@ services: ...@@ -121,6 +123,8 @@ services:
OP_BATCHER_L1_ETH_RPC: http://l1:8545 OP_BATCHER_L1_ETH_RPC: http://l1:8545
OP_BATCHER_L2_ETH_RPC: http://l2:8545 OP_BATCHER_L2_ETH_RPC: http://l2:8545
OP_BATCHER_ROLLUP_RPC: http://op-node:8545 OP_BATCHER_ROLLUP_RPC: http://op-node:8545
TX_MANAGER_TIMEOUT: 10m
OFFLINE_GAS_ESTIMATION: false
OP_BATCHER_MAX_CHANNEL_DURATION: 1 OP_BATCHER_MAX_CHANNEL_DURATION: 1
OP_BATCHER_MAX_L1_TX_SIZE_BYTES: 120000 OP_BATCHER_MAX_L1_TX_SIZE_BYTES: 120000
OP_BATCHER_TARGET_L1_TX_SIZE_BYTES: 100000 OP_BATCHER_TARGET_L1_TX_SIZE_BYTES: 100000
......
...@@ -117,6 +117,10 @@ FROM base as replica-healthcheck ...@@ -117,6 +117,10 @@ FROM base as replica-healthcheck
WORKDIR /opt/optimism/packages/replica-healthcheck WORKDIR /opt/optimism/packages/replica-healthcheck
ENTRYPOINT ["npm", "run", "start"] ENTRYPOINT ["npm", "run", "start"]
FROM base as balance-mon
WORKDIR /opt/optimism/packages/chain-mon
ENTRYPOINT ["npm", "run", "start:balance-mon"]
FROM base as drippie-mon FROM base as drippie-mon
WORKDIR /opt/optimism/packages/chain-mon WORKDIR /opt/optimism/packages/chain-mon
ENTRYPOINT ["npm", "run", "start:drippie-mon"] ENTRYPOINT ["npm", "run", "start:drippie-mon"]
......
...@@ -16,7 +16,7 @@ WORKDIR /opt/foundry ...@@ -16,7 +16,7 @@ WORKDIR /opt/foundry
# Only diff from upstream docker image is this clone instead # Only diff from upstream docker image is this clone instead
# of COPY. We select a specific commit to use. # of COPY. We select a specific commit to use.
RUN git clone https://github.com/foundry-rs/foundry.git . \ RUN git clone https://github.com/foundry-rs/foundry.git . \
&& git checkout 8f3fca9c608d58981daaffe11e7f8076644cb753 && git checkout da2392e58bb8a7fefeba46b40c4df1afad8ccd22
RUN source $HOME/.profile && \ RUN source $HOME/.profile && \
cargo build --release && \ cargo build --release && \
......
###############################################################################
# ↓ balance-mon ↓ #
###############################################################################
# RPC pointing to network to monitor balances on
BALANCE_MON__RPC=
# JSON array in the format [{ "address": <address>, "nickname": <nickname> }, ... ]
BALANCE_MON__ACCOUNTS=
############################################################################### ###############################################################################
# ↓ drippie-mon ↓ # # ↓ drippie-mon ↓ #
############################################################################### ###############################################################################
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
"dist/*" "dist/*"
], ],
"scripts": { "scripts": {
"start:balance-mon": "ts-node ./src/balance-mon/service.ts",
"start:drippie-mon": "ts-node ./src/drippie-mon/service.ts", "start:drippie-mon": "ts-node ./src/drippie-mon/service.ts",
"start:wd-mon": "ts-node ./src/wd-mon/service.ts", "start:wd-mon": "ts-node ./src/wd-mon/service.ts",
"test:coverage": "echo 'No tests defined.'", "test:coverage": "echo 'No tests defined.'",
......
import {
BaseServiceV2,
StandardOptions,
Gauge,
Counter,
validators,
} from '@eth-optimism/common-ts'
import { Provider } from '@ethersproject/abstract-provider'
import { ethers } from 'ethers'
import { version } from '../../package.json'
type BalanceMonOptions = {
rpc: Provider
accounts: string
}
type BalanceMonMetrics = {
balances: Gauge
unexpectedRpcErrors: Counter
}
type BalanceMonState = {
accounts: Array<{ address: string; nickname: string }>
}
export class BalanceMonService extends BaseServiceV2<
BalanceMonOptions,
BalanceMonMetrics,
BalanceMonState
> {
constructor(options?: Partial<BalanceMonOptions & StandardOptions>) {
super({
version,
name: 'balance-mon',
loop: true,
options: {
loopIntervalMs: 60_000,
...options,
},
optionsSpec: {
rpc: {
validator: validators.provider,
desc: 'Provider for network to monitor balances on',
},
accounts: {
validator: validators.str,
desc: 'JSON array of [{ address, nickname }] to monitor balances of',
public: true,
},
},
metricsSpec: {
balances: {
type: Gauge,
desc: 'Balances of addresses',
labels: ['address', 'nickname'],
},
unexpectedRpcErrors: {
type: Counter,
desc: 'Number of unexpected RPC errors',
labels: ['section', 'name'],
},
},
})
}
protected async init(): Promise<void> {
this.state.accounts = JSON.parse(this.options.accounts)
}
protected async main(): Promise<void> {
for (const account of this.state.accounts) {
let balance: ethers.BigNumber
try {
balance = await this.options.rpc.getBalance(account.address)
} catch (err) {
this.logger.info(`got unexpected RPC error`, {
section: 'balances',
name: 'getBalance',
err,
})
this.metrics.unexpectedRpcErrors.inc({
section: 'balances',
name: 'getBalance',
})
continue
}
this.logger.info(`got balance`, {
address: account.address,
nickname: account.nickname,
balance: balance.toString(),
})
// Parse the balance as an integer instead of via toNumber() to avoid ethers throwing an
// an error. We might get rounding errors but we don't need perfect precision here, just a
// generally accurate sense for what the current balance is.
this.metrics.balances.set(
{ address: account.address, nickname: account.nickname },
parseInt(balance.toString(), 10)
)
}
}
}
if (require.main === module) {
const service = new BalanceMonService()
service.run()
}
export * from './balance-mon/service'
export * from './drippie-mon/service' export * from './drippie-mon/service'
export * from './wd-mon/service' export * from './wd-mon/service'
...@@ -22,23 +22,23 @@ CrossDomainOwnable3_Test:test_transferOwnershipNoLocal_succeeds() (gas: 48610) ...@@ -22,23 +22,23 @@ CrossDomainOwnable3_Test:test_transferOwnershipNoLocal_succeeds() (gas: 48610)
CrossDomainOwnable3_Test:test_transferOwnership_noLocalZeroAddress_reverts() (gas: 12015) CrossDomainOwnable3_Test:test_transferOwnership_noLocalZeroAddress_reverts() (gas: 12015)
CrossDomainOwnable3_Test:test_transferOwnership_notOwner_reverts() (gas: 13437) CrossDomainOwnable3_Test:test_transferOwnership_notOwner_reverts() (gas: 13437)
CrossDomainOwnable3_Test:test_transferOwnership_zeroAddress_reverts() (gas: 12081) CrossDomainOwnable3_Test:test_transferOwnership_zeroAddress_reverts() (gas: 12081)
CrossDomainOwnableThroughPortal_Test:test_depositTransaction_crossDomainOwner_succeeds() (gas: 72502) CrossDomainOwnableThroughPortal_Test:test_depositTransaction_crossDomainOwner_succeeds() (gas: 72497)
CrossDomainOwnable_Test:test_onlyOwner_notOwner_reverts() (gas: 10597) CrossDomainOwnable_Test:test_onlyOwner_notOwner_reverts() (gas: 10597)
CrossDomainOwnable_Test:test_onlyOwner_succeeds() (gas: 34883) CrossDomainOwnable_Test:test_onlyOwner_succeeds() (gas: 34883)
DeployerWhitelist_Test:test_owner_succeeds() (gas: 7582) DeployerWhitelist_Test:test_owner_succeeds() (gas: 7582)
DeployerWhitelist_Test:test_storageSlots_succeeds() (gas: 33395) DeployerWhitelist_Test:test_storageSlots_succeeds() (gas: 33395)
FeeVault_Test:test_constructor_succeeds() (gas: 10736) FeeVault_Test:test_constructor_succeeds() (gas: 10736)
FeeVault_Test:test_minWithdrawalAmount_succeeds() (gas: 10713) FeeVault_Test:test_minWithdrawalAmount_succeeds() (gas: 10713)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 266896) GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 266854)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2100047) GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2100005)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 452675) GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 452633)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 3199812) GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 3199770)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 353932) GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 353890)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 2634775) GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 2634733)
GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 40409) GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 40409)
GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 88513) GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 88513)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 75083) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 75078)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 77807) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 75687)
GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 169237) GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 169237)
GasPriceOracle_Test:test_baseFee_succeeds() (gas: 8325) GasPriceOracle_Test:test_baseFee_succeeds() (gas: 8325)
GasPriceOracle_Test:test_decimals_succeeds() (gas: 6167) GasPriceOracle_Test:test_decimals_succeeds() (gas: 6167)
...@@ -81,35 +81,35 @@ L1CrossDomainMessenger_Test:test_relayMessage_succeeds() (gas: 76665) ...@@ -81,35 +81,35 @@ L1CrossDomainMessenger_Test:test_relayMessage_succeeds() (gas: 76665)
L1CrossDomainMessenger_Test:test_relayMessage_toSystemContract_reverts() (gas: 101282) L1CrossDomainMessenger_Test:test_relayMessage_toSystemContract_reverts() (gas: 101282)
L1CrossDomainMessenger_Test:test_relayMessage_v2_reverts() (gas: 12365) L1CrossDomainMessenger_Test:test_relayMessage_v2_reverts() (gas: 12365)
L1CrossDomainMessenger_Test:test_replayMessage_withValue_reverts() (gas: 53445) L1CrossDomainMessenger_Test:test_replayMessage_withValue_reverts() (gas: 53445)
L1CrossDomainMessenger_Test:test_sendMessage_succeeds() (gas: 304766) L1CrossDomainMessenger_Test:test_sendMessage_succeeds() (gas: 304740)
L1CrossDomainMessenger_Test:test_sendMessage_twice_succeeds() (gas: 1496204) L1CrossDomainMessenger_Test:test_sendMessage_twice_succeeds() (gas: 1496124)
L1CrossDomainMessenger_Test:test_xDomainMessageSender_reset_succeeds() (gas: 87194) L1CrossDomainMessenger_Test:test_xDomainMessageSender_reset_succeeds() (gas: 87194)
L1CrossDomainMessenger_Test:test_xDomainSender_notSet_reverts() (gas: 24296) L1CrossDomainMessenger_Test:test_xDomainSender_notSet_reverts() (gas: 24296)
L1ERC721Bridge_Test:test_bridgeERC721To_localTokenZeroAddress_reverts() (gas: 52707) L1ERC721Bridge_Test:test_bridgeERC721To_localTokenZeroAddress_reverts() (gas: 52707)
L1ERC721Bridge_Test:test_bridgeERC721To_remoteTokenZeroAddress_reverts() (gas: 27310) L1ERC721Bridge_Test:test_bridgeERC721To_remoteTokenZeroAddress_reverts() (gas: 27310)
L1ERC721Bridge_Test:test_bridgeERC721To_succeeds() (gas: 359861) L1ERC721Bridge_Test:test_bridgeERC721To_succeeds() (gas: 359991)
L1ERC721Bridge_Test:test_bridgeERC721To_wrongOwner_reverts() (gas: 60934) L1ERC721Bridge_Test:test_bridgeERC721To_wrongOwner_reverts() (gas: 60934)
L1ERC721Bridge_Test:test_bridgeERC721_fromContract_reverts() (gas: 25666) L1ERC721Bridge_Test:test_bridgeERC721_fromContract_reverts() (gas: 25666)
L1ERC721Bridge_Test:test_bridgeERC721_localTokenZeroAddress_reverts() (gas: 50564) L1ERC721Bridge_Test:test_bridgeERC721_localTokenZeroAddress_reverts() (gas: 50564)
L1ERC721Bridge_Test:test_bridgeERC721_remoteTokenZeroAddress_reverts() (gas: 25124) L1ERC721Bridge_Test:test_bridgeERC721_remoteTokenZeroAddress_reverts() (gas: 25124)
L1ERC721Bridge_Test:test_bridgeERC721_succeeds() (gas: 357441) L1ERC721Bridge_Test:test_bridgeERC721_succeeds() (gas: 357571)
L1ERC721Bridge_Test:test_bridgeERC721_wrongOwner_reverts() (gas: 60830) L1ERC721Bridge_Test:test_bridgeERC721_wrongOwner_reverts() (gas: 60830)
L1ERC721Bridge_Test:test_constructor_succeeds() (gas: 10200) L1ERC721Bridge_Test:test_constructor_succeeds() (gas: 10200)
L1ERC721Bridge_Test:test_finalizeBridgeERC721_notEscrowed_reverts() (gas: 22119) L1ERC721Bridge_Test:test_finalizeBridgeERC721_notEscrowed_reverts() (gas: 22119)
L1ERC721Bridge_Test:test_finalizeBridgeERC721_notFromRemoteMessenger_reverts() (gas: 19797) L1ERC721Bridge_Test:test_finalizeBridgeERC721_notFromRemoteMessenger_reverts() (gas: 19797)
L1ERC721Bridge_Test:test_finalizeBridgeERC721_notViaLocalMessenger_reverts() (gas: 16049) L1ERC721Bridge_Test:test_finalizeBridgeERC721_notViaLocalMessenger_reverts() (gas: 16049)
L1ERC721Bridge_Test:test_finalizeBridgeERC721_selfToken_reverts() (gas: 17615) L1ERC721Bridge_Test:test_finalizeBridgeERC721_selfToken_reverts() (gas: 17615)
L1ERC721Bridge_Test:test_finalizeBridgeERC721_succeeds() (gas: 328953) L1ERC721Bridge_Test:test_finalizeBridgeERC721_succeeds() (gas: 329083)
L1StandardBridge_BridgeETHTo_Test:test_bridgeETHTo_succeeds() (gas: 424690) L1StandardBridge_BridgeETHTo_Test:test_bridgeETHTo_succeeds() (gas: 424648)
L1StandardBridge_BridgeETH_Test:test_bridgeETH_succeeds() (gas: 411911) L1StandardBridge_BridgeETH_Test:test_bridgeETH_succeeds() (gas: 411869)
L1StandardBridge_DepositERC20To_Test:test_depositERC20To_succeeds() (gas: 629994) L1StandardBridge_DepositERC20To_Test:test_depositERC20To_succeeds() (gas: 630124)
L1StandardBridge_DepositERC20_Test:test_depositERC20_succeeds() (gas: 627695) L1StandardBridge_DepositERC20_Test:test_depositERC20_succeeds() (gas: 627825)
L1StandardBridge_DepositERC20_TestFail:test_depositERC20_notEoa_reverts() (gas: 22320) L1StandardBridge_DepositERC20_TestFail:test_depositERC20_notEoa_reverts() (gas: 22320)
L1StandardBridge_DepositETHTo_Test:test_depositETHTo_succeeds() (gas: 424767) L1StandardBridge_DepositETHTo_Test:test_depositETHTo_succeeds() (gas: 424725)
L1StandardBridge_DepositETH_Test:test_depositETH_succeeds() (gas: 412005) L1StandardBridge_DepositETH_Test:test_depositETH_succeeds() (gas: 411963)
L1StandardBridge_DepositETH_TestFail:test_depositETH_notEoa_reverts() (gas: 40780) L1StandardBridge_DepositETH_TestFail:test_depositETH_notEoa_reverts() (gas: 40780)
L1StandardBridge_FinalizeBridgeETH_Test:test_finalizeBridgeETH_succeeds() (gas: 51674) L1StandardBridge_FinalizeBridgeETH_Test:test_finalizeBridgeETH_succeeds() (gas: 51674)
L1StandardBridge_FinalizeBridgeETH_TestFail:test_finalizeBridgeETH_incorrectValue_reverts() (gas: 34207) L1StandardBridge_FinalizeBridgeETH_TestFail:test_finalizeBridgeETH_incorrectValue_reverts() (gas: 34204)
L1StandardBridge_FinalizeBridgeETH_TestFail:test_finalizeBridgeETH_sendToMessenger_reverts() (gas: 34310) L1StandardBridge_FinalizeBridgeETH_TestFail:test_finalizeBridgeETH_sendToMessenger_reverts() (gas: 34310)
L1StandardBridge_FinalizeBridgeETH_TestFail:test_finalizeBridgeETH_sendToSelf_reverts() (gas: 34279) L1StandardBridge_FinalizeBridgeETH_TestFail:test_finalizeBridgeETH_sendToSelf_reverts() (gas: 34279)
L1StandardBridge_FinalizeERC20Withdrawal_Test:test_finalizeERC20Withdrawal_succeeds() (gas: 496128) L1StandardBridge_FinalizeERC20Withdrawal_Test:test_finalizeERC20Withdrawal_succeeds() (gas: 496128)
...@@ -118,7 +118,7 @@ L1StandardBridge_FinalizeERC20Withdrawal_TestFail:test_finalizeERC20Withdrawal_n ...@@ -118,7 +118,7 @@ L1StandardBridge_FinalizeERC20Withdrawal_TestFail:test_finalizeERC20Withdrawal_n
L1StandardBridge_FinalizeETHWithdrawal_Test:test_finalizeETHWithdrawal_succeeds() (gas: 61722) L1StandardBridge_FinalizeETHWithdrawal_Test:test_finalizeETHWithdrawal_succeeds() (gas: 61722)
L1StandardBridge_Getter_Test:test_getters_succeeds() (gas: 32173) L1StandardBridge_Getter_Test:test_getters_succeeds() (gas: 32173)
L1StandardBridge_Initialize_Test:test_initialize_succeeds() (gas: 22050) L1StandardBridge_Initialize_Test:test_initialize_succeeds() (gas: 22050)
L1StandardBridge_Receive_Test:test_receive_succeeds() (gas: 525308) L1StandardBridge_Receive_Test:test_receive_succeeds() (gas: 525438)
L2CrossDomainMessenger_Test:test_messageVersion_succeeds() (gas: 8411) L2CrossDomainMessenger_Test:test_messageVersion_succeeds() (gas: 8411)
L2CrossDomainMessenger_Test:test_relayMessage_reentrancyDiffMessage_succeeds() (gas: 680395) L2CrossDomainMessenger_Test:test_relayMessage_reentrancyDiffMessage_succeeds() (gas: 680395)
L2CrossDomainMessenger_Test:test_relayMessage_reentrancySameMessage_reverts() (gas: 626456) L2CrossDomainMessenger_Test:test_relayMessage_reentrancySameMessage_reverts() (gas: 626456)
...@@ -182,7 +182,7 @@ L2StandardBridge_BridgeERC20_Test:test_bridgeLegacyERC20_succeeds() (gas: 393552 ...@@ -182,7 +182,7 @@ L2StandardBridge_BridgeERC20_Test:test_bridgeLegacyERC20_succeeds() (gas: 393552
L2StandardBridge_BridgeERC20_Test:test_withdrawLegacyERC20_succeeds() (gas: 393878) L2StandardBridge_BridgeERC20_Test:test_withdrawLegacyERC20_succeeds() (gas: 393878)
L2StandardBridge_BridgeERC20_Test:test_withdraw_notEOA_reverts() (gas: 251758) L2StandardBridge_BridgeERC20_Test:test_withdraw_notEOA_reverts() (gas: 251758)
L2StandardBridge_BridgeERC20_Test:test_withdraw_withdrawingERC20_succeeds() (gas: 385508) L2StandardBridge_BridgeERC20_Test:test_withdraw_withdrawingERC20_succeeds() (gas: 385508)
L2StandardBridge_Bridge_Test:test_finalizeBridgeETH_incorrectValue_reverts() (gas: 23840) L2StandardBridge_Bridge_Test:test_finalizeBridgeETH_incorrectValue_reverts() (gas: 23843)
L2StandardBridge_Bridge_Test:test_finalizeBridgeETH_sendToMessenger_reverts() (gas: 23982) L2StandardBridge_Bridge_Test:test_finalizeBridgeETH_sendToMessenger_reverts() (gas: 23982)
L2StandardBridge_Bridge_Test:test_finalizeBridgeETH_sendToSelf_reverts() (gas: 23870) L2StandardBridge_Bridge_Test:test_finalizeBridgeETH_sendToSelf_reverts() (gas: 23870)
L2StandardBridge_Bridge_Test:test_finalizeDeposit_depositingERC20_succeeds() (gas: 93824) L2StandardBridge_Bridge_Test:test_finalizeDeposit_depositingERC20_succeeds() (gas: 93824)
...@@ -259,48 +259,48 @@ OptimismMintableTokenFactory_Test:test_bridge_succeeds() (gas: 7580) ...@@ -259,48 +259,48 @@ OptimismMintableTokenFactory_Test:test_bridge_succeeds() (gas: 7580)
OptimismMintableTokenFactory_Test:test_createStandardL2Token_remoteIsZero_succeeds() (gas: 9390) OptimismMintableTokenFactory_Test:test_createStandardL2Token_remoteIsZero_succeeds() (gas: 9390)
OptimismMintableTokenFactory_Test:test_createStandardL2Token_sameTwice_succeeds() (gas: 2523203) OptimismMintableTokenFactory_Test:test_createStandardL2Token_sameTwice_succeeds() (gas: 2523203)
OptimismMintableTokenFactory_Test:test_createStandardL2Token_succeeds() (gas: 1268564) OptimismMintableTokenFactory_Test:test_createStandardL2Token_succeeds() (gas: 1268564)
OptimismPortalUpgradeable_Test:test_initialize_cannotInitImpl_reverts() (gas: 10971) OptimismPortalUpgradeable_Test:test_initialize_cannotInitImpl_reverts() (gas: 10994)
OptimismPortalUpgradeable_Test:test_initialize_cannotInitProxy_reverts() (gas: 15895) OptimismPortalUpgradeable_Test:test_initialize_cannotInitProxy_reverts() (gas: 15918)
OptimismPortalUpgradeable_Test:test_params_initValuesOnProxy_succeeds() (gas: 16078) OptimismPortalUpgradeable_Test:test_params_initValuesOnProxy_succeeds() (gas: 21774)
OptimismPortalUpgradeable_Test:test_upgradeToAndCall_upgrading_succeeds() (gas: 180547) OptimismPortalUpgradeable_Test:test_upgradeToAndCall_upgrading_succeeds() (gas: 180547)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifOutputRootChanges_reverts() (gas: 204067) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifOutputRootChanges_reverts() (gas: 204044)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifOutputTimestampIsNotFinalized_reverts() (gas: 207520) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifOutputTimestampIsNotFinalized_reverts() (gas: 207497)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifWithdrawalNotProven_reverts() (gas: 41753) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifWithdrawalNotProven_reverts() (gas: 41753)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifWithdrawalProofNotOldEnough_reverts() (gas: 199464) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifWithdrawalProofNotOldEnough_reverts() (gas: 199441)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onInsufficientGas_reverts() (gas: 205818) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onInsufficientGas_reverts() (gas: 205787)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onRecentWithdrawal_reverts() (gas: 180229) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onRecentWithdrawal_reverts() (gas: 180206)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onReentrancy_reverts() (gas: 243835) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onReentrancy_reverts() (gas: 243804)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onReplay_reverts() (gas: 245528) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onReplay_reverts() (gas: 245528)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_paused_reverts() (gas: 53555) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_paused_reverts() (gas: 53576)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_provenWithdrawalHash_succeeds() (gas: 234941) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_provenWithdrawalHash_succeeds() (gas: 234941)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_targetFails_fails() (gas: 8797746687696163864) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_targetFails_fails() (gas: 8797746687696163864)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_timestampLessThanL2OracleStart_reverts() (gas: 197042) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_timestampLessThanL2OracleStart_reverts() (gas: 197019)
OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_onInvalidOutputRootProof_reverts() (gas: 85690) OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_onInvalidOutputRootProof_reverts() (gas: 85690)
OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_onInvalidWithdrawalProof_reverts() (gas: 137350) OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_onInvalidWithdrawalProof_reverts() (gas: 137350)
OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_onSelfCall_reverts() (gas: 52947) OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_onSelfCall_reverts() (gas: 52947)
OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_paused_reverts() (gas: 73696) OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_paused_reverts() (gas: 73717)
OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_replayProveChangedOutputRootAndOutputIndex_succeeds() (gas: 346853) OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_replayProveChangedOutputRootAndOutputIndex_succeeds() (gas: 346739)
OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_replayProveChangedOutputRoot_succeeds() (gas: 279593) OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_replayProveChangedOutputRoot_succeeds() (gas: 279571)
OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_replayProve_reverts() (gas: 192548) OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_replayProve_reverts() (gas: 192548)
OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_validWithdrawalProof_succeeds() (gas: 180486) OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_validWithdrawalProof_succeeds() (gas: 180486)
OptimismPortal_Test:test_constructor_succeeds() (gas: 19440) OptimismPortal_Test:test_constructor_succeeds() (gas: 19372)
OptimismPortal_Test:test_depositTransaction_contractCreation_reverts() (gas: 14330) OptimismPortal_Test:test_depositTransaction_contractCreation_reverts() (gas: 14320)
OptimismPortal_Test:test_depositTransaction_createWithZeroValueForContract_succeeds() (gas: 76756) OptimismPortal_Test:test_depositTransaction_createWithZeroValueForContract_succeeds() (gas: 76751)
OptimismPortal_Test:test_depositTransaction_createWithZeroValueForEOA_succeeds() (gas: 77122) OptimismPortal_Test:test_depositTransaction_createWithZeroValueForEOA_succeeds() (gas: 77117)
OptimismPortal_Test:test_depositTransaction_noValueContract_succeeds() (gas: 76774) OptimismPortal_Test:test_depositTransaction_noValueContract_succeeds() (gas: 76769)
OptimismPortal_Test:test_depositTransaction_noValueEOA_succeeds() (gas: 77119) OptimismPortal_Test:test_depositTransaction_noValueEOA_succeeds() (gas: 77114)
OptimismPortal_Test:test_depositTransaction_smallGasLimit_reverts() (gas: 14276) OptimismPortal_Test:test_depositTransaction_smallGasLimit_reverts() (gas: 14266)
OptimismPortal_Test:test_depositTransaction_withEthValueAndContractContractCreation_succeeds() (gas: 83780) OptimismPortal_Test:test_depositTransaction_withEthValueAndContractContractCreation_succeeds() (gas: 83775)
OptimismPortal_Test:test_depositTransaction_withEthValueAndEOAContractCreation_succeeds() (gas: 75933) OptimismPortal_Test:test_depositTransaction_withEthValueAndEOAContractCreation_succeeds() (gas: 75931)
OptimismPortal_Test:test_depositTransaction_withEthValueFromContract_succeeds() (gas: 83460) OptimismPortal_Test:test_depositTransaction_withEthValueFromContract_succeeds() (gas: 83455)
OptimismPortal_Test:test_depositTransaction_withEthValueFromEOA_succeeds() (gas: 84076) OptimismPortal_Test:test_depositTransaction_withEthValueFromEOA_succeeds() (gas: 84071)
OptimismPortal_Test:test_isOutputFinalized_succeeds() (gas: 121711) OptimismPortal_Test:test_isOutputFinalized_succeeds() (gas: 121623)
OptimismPortal_Test:test_pause_onlyGuardian_reverts() (gas: 22205) OptimismPortal_Test:test_pause_onlyGuardian_reverts() (gas: 22136)
OptimismPortal_Test:test_pause_succeeds() (gas: 42184) OptimismPortal_Test:test_pause_succeeds() (gas: 42115)
OptimismPortal_Test:test_receive_succeeds() (gas: 127552) OptimismPortal_Test:test_receive_succeeds() (gas: 127560)
OptimismPortal_Test:test_simple_isOutputFinalized_succeeds() (gas: 32934) OptimismPortal_Test:test_simple_isOutputFinalized_succeeds() (gas: 32867)
OptimismPortal_Test:test_unpause_onlyGuardian_reverts() (gas: 46151) OptimismPortal_Test:test_unpause_onlyGuardian_reverts() (gas: 46060)
OptimismPortal_Test:test_unpause_succeeds() (gas: 31763) OptimismPortal_Test:test_unpause_succeeds() (gas: 31708)
ProxyAdmin_Test:test_chugsplashChangeProxyAdmin_succeeds() (gas: 35586) ProxyAdmin_Test:test_chugsplashChangeProxyAdmin_succeeds() (gas: 35586)
ProxyAdmin_Test:test_chugsplashGetProxyAdmin_succeeds() (gas: 15675) ProxyAdmin_Test:test_chugsplashGetProxyAdmin_succeeds() (gas: 15675)
ProxyAdmin_Test:test_chugsplashGetProxyImplementation_succeeds() (gas: 51084) ProxyAdmin_Test:test_chugsplashGetProxyImplementation_succeeds() (gas: 51084)
...@@ -398,17 +398,14 @@ RLPWriter_writeUint_Test:test_writeUint_smallint_succeeds() (gas: 7280) ...@@ -398,17 +398,14 @@ RLPWriter_writeUint_Test:test_writeUint_smallint_succeeds() (gas: 7280)
RLPWriter_writeUint_Test:test_writeUint_zero_succeeds() (gas: 7749) RLPWriter_writeUint_Test:test_writeUint_zero_succeeds() (gas: 7749)
ResolvedDelegateProxy_Test:test_fallback_addressManagerNotSet_reverts() (gas: 605906) ResolvedDelegateProxy_Test:test_fallback_addressManagerNotSet_reverts() (gas: 605906)
ResolvedDelegateProxy_Test:test_fallback_delegateCallBar_reverts() (gas: 24783) ResolvedDelegateProxy_Test:test_fallback_delegateCallBar_reverts() (gas: 24783)
ResourceMetering_Test:test_meter_initialBaseFee_succeeds() (gas: 7003) ResourceMetering_Test:test_meter_initialResourceParams_succeeds() (gas: 10368)
ResourceMetering_Test:test_meter_initialResourceParams_succeeds() (gas: 9028) ResourceMetering_Test:test_meter_updateNoGasDelta_succeeds() (gas: 2009696)
ResourceMetering_Test:test_meter_maxValue_succeeds() (gas: 7015) ResourceMetering_Test:test_meter_updateOneEmptyBlock_succeeds() (gas: 18860)
ResourceMetering_Test:test_meter_minBaseFeeLessThanMaxBaseFee_succeeds() (gas: 6194) ResourceMetering_Test:test_meter_updateParamsNoChange_succeeds() (gas: 15149)
ResourceMetering_Test:test_meter_updateNoGasDelta_succeeds() (gas: 2008204) ResourceMetering_Test:test_meter_updateTenEmptyBlocks_succeeds() (gas: 21713)
ResourceMetering_Test:test_meter_updateOneEmptyBlock_succeeds() (gas: 18441) ResourceMetering_Test:test_meter_updateTwoEmptyBlocks_succeeds() (gas: 21669)
ResourceMetering_Test:test_meter_updateParamsNoChange_succeeds() (gas: 14005) ResourceMetering_Test:test_meter_useMax_succeeds() (gas: 20018715)
ResourceMetering_Test:test_meter_updateTenEmptyBlocks_succeeds() (gas: 21243) ResourceMetering_Test:test_meter_useMoreThanMax_reverts() (gas: 17505)
ResourceMetering_Test:test_meter_updateTwoEmptyBlocks_succeeds() (gas: 21199)
ResourceMetering_Test:test_meter_useMax_succeeds() (gas: 20017464)
ResourceMetering_Test:test_meter_useMoreThanMax_reverts() (gas: 16120)
SafeCall_call_Test:test_callWithMinGas_noLeakageHigh_succeeds() (gas: 2075873614) SafeCall_call_Test:test_callWithMinGas_noLeakageHigh_succeeds() (gas: 2075873614)
SafeCall_call_Test:test_callWithMinGas_noLeakageLow_succeeds() (gas: 753665282) SafeCall_call_Test:test_callWithMinGas_noLeakageLow_succeeds() (gas: 753665282)
Semver_Test:test_behindProxy_succeeds() (gas: 506748) Semver_Test:test_behindProxy_succeeds() (gas: 506748)
...@@ -418,13 +415,18 @@ SequencerFeeVault_Test:test_minWithdrawalAmount_succeeds() (gas: 5442) ...@@ -418,13 +415,18 @@ SequencerFeeVault_Test:test_minWithdrawalAmount_succeeds() (gas: 5442)
SequencerFeeVault_Test:test_receive_succeeds() (gas: 17373) SequencerFeeVault_Test:test_receive_succeeds() (gas: 17373)
SequencerFeeVault_Test:test_withdraw_notEnough_reverts() (gas: 9331) SequencerFeeVault_Test:test_withdraw_notEnough_reverts() (gas: 9331)
SequencerFeeVault_Test:test_withdraw_succeeds() (gas: 163228) SequencerFeeVault_Test:test_withdraw_succeeds() (gas: 163228)
SetPrevBaseFee_Test:test_setPrevBaseFee_succeeds() (gas: 11559) SetPrevBaseFee_Test:test_setPrevBaseFee_succeeds() (gas: 11515)
StandardBridge_Stateless_Test:test_isCorrectTokenPair_succeeds() (gas: 49936) StandardBridge_Stateless_Test:test_isCorrectTokenPair_succeeds() (gas: 49936)
StandardBridge_Stateless_Test:test_isOptimismMintableERC20_succeeds() (gas: 33072) StandardBridge_Stateless_Test:test_isOptimismMintableERC20_succeeds() (gas: 33072)
SystemConfig_Initialize_TestFail:test_initialize_lowGasLimit_reverts() (gas: 62012) SystemConfig_Initialize_TestFail:test_initialize_lowGasLimit_reverts() (gas: 148858)
SystemConfig_Setters_TestFail:test_setBatcherHash_notOwner_reverts() (gas: 10612) SystemConfig_Setters_TestFail:test_setBatcherHash_notOwner_reverts() (gas: 10546)
SystemConfig_Setters_TestFail:test_setGasConfig_notOwner_reverts() (gas: 10555) SystemConfig_Setters_TestFail:test_setGasConfig_notOwner_reverts() (gas: 10622)
SystemConfig_Setters_TestFail:test_setGasLimit_notOwner_reverts() (gas: 10658) SystemConfig_Setters_TestFail:test_setGasLimit_notOwner_reverts() (gas: 10615)
SystemConfig_Setters_TestFail:test_setUnsafeBlockSigner_notOwner_reverts() (gas: 10660) SystemConfig_Setters_TestFail:test_setResourceConfig_badMinMax_reverts() (gas: 13002)
SystemConfig_Setters_TestFail:test_setResourceConfig_badPrecision_reverts() (gas: 15603)
SystemConfig_Setters_TestFail:test_setResourceConfig_lowGasLimit_reverts() (gas: 16082)
SystemConfig_Setters_TestFail:test_setResourceConfig_notOwner_reverts() (gas: 11790)
SystemConfig_Setters_TestFail:test_setResourceConfig_zeroDenominator_reverts() (gas: 13039)
SystemConfig_Setters_TestFail:test_setUnsafeBlockSigner_notOwner_reverts() (gas: 10639)
TransferOnionTest:test_constructor_succeeds() (gas: 564855) TransferOnionTest:test_constructor_succeeds() (gas: 564855)
TransferOnionTest:test_unwrap_succeeds() (gas: 724958) TransferOnionTest:test_unwrap_succeeds() (gas: 724958)
\ No newline at end of file
...@@ -70,7 +70,7 @@ ...@@ -70,7 +70,7 @@
======================= =======================
| Name | Type | Slot | Offset | Bytes | Contract | | Name | Type | Slot | Offset | Bytes | Contract |
|---------------|-------------|------|--------|-------|--------------------------------------------| |-----------------|----------------------------------------|------|--------|-------|--------------------------------------------|
| _initialized | uint8 | 0 | 0 | 1 | contracts/L1/SystemConfig.sol:SystemConfig | | _initialized | uint8 | 0 | 0 | 1 | contracts/L1/SystemConfig.sol:SystemConfig |
| _initializing | bool | 0 | 1 | 1 | contracts/L1/SystemConfig.sol:SystemConfig | | _initializing | bool | 0 | 1 | 1 | contracts/L1/SystemConfig.sol:SystemConfig |
| __gap | uint256[50] | 1 | 0 | 1600 | contracts/L1/SystemConfig.sol:SystemConfig | | __gap | uint256[50] | 1 | 0 | 1600 | contracts/L1/SystemConfig.sol:SystemConfig |
...@@ -80,6 +80,7 @@ ...@@ -80,6 +80,7 @@
| scalar | uint256 | 102 | 0 | 32 | contracts/L1/SystemConfig.sol:SystemConfig | | scalar | uint256 | 102 | 0 | 32 | contracts/L1/SystemConfig.sol:SystemConfig |
| batcherHash | bytes32 | 103 | 0 | 32 | contracts/L1/SystemConfig.sol:SystemConfig | | batcherHash | bytes32 | 103 | 0 | 32 | contracts/L1/SystemConfig.sol:SystemConfig |
| gasLimit | uint64 | 104 | 0 | 8 | contracts/L1/SystemConfig.sol:SystemConfig | | gasLimit | uint64 | 104 | 0 | 8 | contracts/L1/SystemConfig.sol:SystemConfig |
| _resourceConfig | struct ResourceMetering.ResourceConfig | 105 | 0 | 32 | contracts/L1/SystemConfig.sol:SystemConfig |
======================= =======================
➡ contracts/legacy/DeployerWhitelist.sol:DeployerWhitelist ➡ contracts/legacy/DeployerWhitelist.sol:DeployerWhitelist
......
...@@ -57,7 +57,7 @@ We work on this repository with a combination of [Hardhat](https://hardhat.org) ...@@ -57,7 +57,7 @@ We work on this repository with a combination of [Hardhat](https://hardhat.org)
1. Install Foundry by following [the instructions located here](https://getfoundry.sh/). 1. Install Foundry by following [the instructions located here](https://getfoundry.sh/).
A specific version must be used. A specific version must be used.
```shell ```shell
foundryup -C 2ff99025abade470a795724c10648c800a41025e foundryup -C da2392e58bb8a7fefeba46b40c4df1afad8ccd22
``` ```
2. Install node modules with yarn (v1) and Node.js (16+): 2. Install node modules with yarn (v1) and Node.js (16+):
......
...@@ -4,6 +4,7 @@ pragma solidity 0.8.15; ...@@ -4,6 +4,7 @@ pragma solidity 0.8.15;
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import { SafeCall } from "../libraries/SafeCall.sol"; import { SafeCall } from "../libraries/SafeCall.sol";
import { L2OutputOracle } from "./L2OutputOracle.sol"; import { L2OutputOracle } from "./L2OutputOracle.sol";
import { SystemConfig } from "./SystemConfig.sol";
import { Constants } from "../libraries/Constants.sol"; import { Constants } from "../libraries/Constants.sol";
import { Types } from "../libraries/Types.sol"; import { Types } from "../libraries/Types.sol";
import { Hashing } from "../libraries/Hashing.sol"; import { Hashing } from "../libraries/Hashing.sol";
...@@ -44,10 +45,15 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver { ...@@ -44,10 +45,15 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
uint64 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 100_000; uint64 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 100_000;
/** /**
* @notice Address of the L2OutputOracle. * @notice Address of the L2OutputOracle contract.
*/ */
L2OutputOracle public immutable L2_ORACLE; L2OutputOracle public immutable L2_ORACLE;
/**
* @notice Address of the SystemConfig contract.
*/
SystemConfig public immutable SYSTEM_CONFIG;
/** /**
* @notice Address that has the ability to pause and unpause withdrawals. * @notice Address that has the ability to pause and unpause withdrawals.
*/ */
...@@ -135,19 +141,22 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver { ...@@ -135,19 +141,22 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
} }
/** /**
* @custom:semver 1.2.0 * @custom:semver 1.3.0
* *
* @param _l2Oracle Address of the L2OutputOracle contract. * @param _l2Oracle Address of the L2OutputOracle contract.
* @param _guardian Address that can pause deposits and withdrawals. * @param _guardian Address that can pause deposits and withdrawals.
* @param _paused Sets the contract's pausability state. * @param _paused Sets the contract's pausability state.
* @param _config Address of the SystemConfig contract.
*/ */
constructor( constructor(
L2OutputOracle _l2Oracle, L2OutputOracle _l2Oracle,
address _guardian, address _guardian,
bool _paused bool _paused,
) Semver(1, 2, 0) { SystemConfig _config
) Semver(1, 3, 0) {
L2_ORACLE = _l2Oracle; L2_ORACLE = _l2Oracle;
GUARDIAN = _guardian; GUARDIAN = _guardian;
SYSTEM_CONFIG = _config;
initialize(_paused); initialize(_paused);
} }
...@@ -197,6 +206,21 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver { ...@@ -197,6 +206,21 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
// Intentionally empty. // Intentionally empty.
} }
/**
* @notice Getter for the resource config. Used internally by the ResourceMetering
* contract. The SystemConfig is the source of truth for the resource config.
*
* @return ResourceMetering.ResourceConfig
*/
function _resourceConfig()
internal
view
override
returns (ResourceMetering.ResourceConfig memory)
{
return SYSTEM_CONFIG.resourceConfig();
}
/** /**
* @notice Proves a withdrawal transaction. * @notice Proves a withdrawal transaction.
* *
......
...@@ -28,44 +28,34 @@ abstract contract ResourceMetering is Initializable { ...@@ -28,44 +28,34 @@ abstract contract ResourceMetering is Initializable {
} }
/** /**
* @notice Maximum amount of the resource that can be used within this block. * @notice Represents the configuration for the EIP-1559 based curve for the deposit gas
* This value cannot be larger than the L2 block gas limit. * market. These values should be set with care as it is possible to set them in
*/ * a way that breaks the deposit gas market. The target resource limit is defined as
int256 public constant MAX_RESOURCE_LIMIT = 20_000_000; * maxResourceLimit / elasticityMultiplier. This struct was designed to fit within a
* single word. There is additional space for additions in the future.
/** *
* @notice Along with the resource limit, determines the target resource limit. * @custom:field maxResourceLimit Represents the maximum amount of deposit gas that
*/ * can be purchased per block.
int256 public constant ELASTICITY_MULTIPLIER = 10; * @custom:field elasticityMultiplier Determines the target resource limit along with
* the resource limit.
/** * @custom:field baseFeeMaxChangeDenominator Determines max change on fee per block.
* @notice Target amount of the resource that should be used within this block. * @custom:field minimumBaseFee The min deposit base fee, it is clamped to this
*/ * value.
int256 public constant TARGET_RESOURCE_LIMIT = MAX_RESOURCE_LIMIT / ELASTICITY_MULTIPLIER; * @custom:field systemTxMaxGas The amount of gas supplied to the system
* transaction. This should be set to the same number
/** * that the op-node sets as the gas limit for the
* @notice Denominator that determines max change on fee per block. * system transaction.
*/ * @custom:field maximumBaseFee The max deposit base fee, it is clamped to this
int256 public constant BASE_FEE_MAX_CHANGE_DENOMINATOR = 8; * value.
/**
* @notice Minimum base fee value, cannot go lower than this.
*/
int256 public constant MINIMUM_BASE_FEE = 1 gwei;
/**
* @notice Maximum base fee value, cannot go higher than this.
* It is possible for the MAXIMUM_BASE_FEE to raise to a value
* that is so large it will consume the entire gas limit of
* an L1 block.
*/
int256 public constant MAXIMUM_BASE_FEE = int256(uint256(type(uint128).max));
/**
* @notice Initial base fee value. This value must be smaller than the
* MAXIMUM_BASE_FEE.
*/ */
uint128 public constant INITIAL_BASE_FEE = 1 gwei; struct ResourceConfig {
uint32 maxResourceLimit;
uint8 elasticityMultiplier;
uint8 baseFeeMaxChangeDenominator;
uint32 minimumBaseFee;
uint32 systemTxMaxGas;
uint128 maximumBaseFee;
}
/** /**
* @notice EIP-1559 style gas parameters. * @notice EIP-1559 style gas parameters.
...@@ -102,20 +92,25 @@ abstract contract ResourceMetering is Initializable { ...@@ -102,20 +92,25 @@ abstract contract ResourceMetering is Initializable {
function _metered(uint64 _amount, uint256 _initialGas) internal { function _metered(uint64 _amount, uint256 _initialGas) internal {
// Update block number and base fee if necessary. // Update block number and base fee if necessary.
uint256 blockDiff = block.number - params.prevBlockNum; uint256 blockDiff = block.number - params.prevBlockNum;
ResourceConfig memory config = _resourceConfig();
int256 targetResourceLimit = int256(uint256(config.maxResourceLimit)) /
int256(uint256(config.elasticityMultiplier));
if (blockDiff > 0) { if (blockDiff > 0) {
// Handle updating EIP-1559 style gas parameters. We use EIP-1559 to restrict the rate // Handle updating EIP-1559 style gas parameters. We use EIP-1559 to restrict the rate
// at which deposits can be created and therefore limit the potential for deposits to // at which deposits can be created and therefore limit the potential for deposits to
// spam the L2 system. Fee scheme is very similar to EIP-1559 with minor changes. // spam the L2 system. Fee scheme is very similar to EIP-1559 with minor changes.
int256 gasUsedDelta = int256(uint256(params.prevBoughtGas)) - TARGET_RESOURCE_LIMIT; int256 gasUsedDelta = int256(uint256(params.prevBoughtGas)) - targetResourceLimit;
int256 baseFeeDelta = (int256(uint256(params.prevBaseFee)) * gasUsedDelta) / int256 baseFeeDelta = (int256(uint256(params.prevBaseFee)) * gasUsedDelta) /
(TARGET_RESOURCE_LIMIT * BASE_FEE_MAX_CHANGE_DENOMINATOR); (targetResourceLimit * int256(uint256(config.baseFeeMaxChangeDenominator)));
// Update base fee by adding the base fee delta and clamp the resulting value between // Update base fee by adding the base fee delta and clamp the resulting value between
// min and max. // min and max.
int256 newBaseFee = Arithmetic.clamp({ int256 newBaseFee = Arithmetic.clamp({
_value: int256(uint256(params.prevBaseFee)) + baseFeeDelta, _value: int256(uint256(params.prevBaseFee)) + baseFeeDelta,
_min: MINIMUM_BASE_FEE, _min: int256(uint256(config.minimumBaseFee)),
_max: MAXIMUM_BASE_FEE _max: int256(uint256(config.maximumBaseFee))
}); });
// If we skipped more than one block, we also need to account for every empty block. // If we skipped more than one block, we also need to account for every empty block.
...@@ -128,11 +123,11 @@ abstract contract ResourceMetering is Initializable { ...@@ -128,11 +123,11 @@ abstract contract ResourceMetering is Initializable {
newBaseFee = Arithmetic.clamp({ newBaseFee = Arithmetic.clamp({
_value: Arithmetic.cdexp({ _value: Arithmetic.cdexp({
_coefficient: newBaseFee, _coefficient: newBaseFee,
_denominator: BASE_FEE_MAX_CHANGE_DENOMINATOR, _denominator: int256(uint256(config.baseFeeMaxChangeDenominator)),
_exponent: int256(blockDiff - 1) _exponent: int256(blockDiff - 1)
}), }),
_min: MINIMUM_BASE_FEE, _min: int256(uint256(config.minimumBaseFee)),
_max: MAXIMUM_BASE_FEE _max: int256(uint256(config.maximumBaseFee))
}); });
} }
...@@ -145,7 +140,7 @@ abstract contract ResourceMetering is Initializable { ...@@ -145,7 +140,7 @@ abstract contract ResourceMetering is Initializable {
// Make sure we can actually buy the resource amount requested by the user. // Make sure we can actually buy the resource amount requested by the user.
params.prevBoughtGas += _amount; params.prevBoughtGas += _amount;
require( require(
int256(uint256(params.prevBoughtGas)) <= MAX_RESOURCE_LIMIT, int256(uint256(params.prevBoughtGas)) <= int256(uint256(config.maxResourceLimit)),
"ResourceMetering: cannot buy more gas than available gas limit" "ResourceMetering: cannot buy more gas than available gas limit"
); );
...@@ -168,6 +163,14 @@ abstract contract ResourceMetering is Initializable { ...@@ -168,6 +163,14 @@ abstract contract ResourceMetering is Initializable {
} }
} }
/**
* @notice Virtual function that returns the resource config. Contracts that inherit this
* contract must implement this function.
*
* @return ResourceConfig
*/
function _resourceConfig() internal virtual returns (ResourceConfig memory);
/** /**
* @notice Sets initial resource parameter values. This function must either be called by the * @notice Sets initial resource parameter values. This function must either be called by the
* initializer function of an upgradeable child contract. * initializer function of an upgradeable child contract.
...@@ -175,7 +178,7 @@ abstract contract ResourceMetering is Initializable { ...@@ -175,7 +178,7 @@ abstract contract ResourceMetering is Initializable {
// solhint-disable-next-line func-name-mixedcase // solhint-disable-next-line func-name-mixedcase
function __ResourceMetering_init() internal onlyInitializing { function __ResourceMetering_init() internal onlyInitializing {
params = ResourceParams({ params = ResourceParams({
prevBaseFee: INITIAL_BASE_FEE, prevBaseFee: 1 gwei,
prevBoughtGas: 0, prevBoughtGas: 0,
prevBlockNum: uint64(block.number) prevBlockNum: uint64(block.number)
}); });
......
...@@ -5,6 +5,7 @@ import { ...@@ -5,6 +5,7 @@ import {
OwnableUpgradeable OwnableUpgradeable
} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { Semver } from "../universal/Semver.sol"; import { Semver } from "../universal/Semver.sol";
import { ResourceMetering } from "./ResourceMetering.sol";
/** /**
* @title SystemConfig * @title SystemConfig
...@@ -49,12 +50,12 @@ contract SystemConfig is OwnableUpgradeable, Semver { ...@@ -49,12 +50,12 @@ contract SystemConfig is OwnableUpgradeable, Semver {
uint64 public constant MINIMUM_GAS_LIMIT = 8_000_000; uint64 public constant MINIMUM_GAS_LIMIT = 8_000_000;
/** /**
* @notice Fixed L2 gas overhead. * @notice Fixed L2 gas overhead. Used as part of the L2 fee calculation.
*/ */
uint256 public overhead; uint256 public overhead;
/** /**
* @notice Dynamic L2 gas overhead. * @notice Dynamic L2 gas overhead. Used as part of the L2 fee calculation.
*/ */
uint256 public scalar; uint256 public scalar;
...@@ -65,10 +66,17 @@ contract SystemConfig is OwnableUpgradeable, Semver { ...@@ -65,10 +66,17 @@ contract SystemConfig is OwnableUpgradeable, Semver {
bytes32 public batcherHash; bytes32 public batcherHash;
/** /**
* @notice L2 gas limit. * @notice L2 block gas limit.
*/ */
uint64 public gasLimit; uint64 public gasLimit;
/**
* @notice The configuration for the deposit fee market. Used by the OptimismPortal
* to meter the cost of buying L2 gas on L1. Set as internal and wrapped with a getter
* so that the struct is returned instead of a tuple.
*/
ResourceMetering.ResourceConfig internal _resourceConfig;
/** /**
* @notice Emitted when configuration is updated * @notice Emitted when configuration is updated
* *
...@@ -79,7 +87,7 @@ contract SystemConfig is OwnableUpgradeable, Semver { ...@@ -79,7 +87,7 @@ contract SystemConfig is OwnableUpgradeable, Semver {
event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data); event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data);
/** /**
* @custom:semver 1.0.1 * @custom:semver 1.1.0
* *
* @param _owner Initial owner of the contract. * @param _owner Initial owner of the contract.
* @param _overhead Initial overhead value. * @param _overhead Initial overhead value.
...@@ -87,6 +95,7 @@ contract SystemConfig is OwnableUpgradeable, Semver { ...@@ -87,6 +95,7 @@ contract SystemConfig is OwnableUpgradeable, Semver {
* @param _batcherHash Initial batcher hash. * @param _batcherHash Initial batcher hash.
* @param _gasLimit Initial gas limit. * @param _gasLimit Initial gas limit.
* @param _unsafeBlockSigner Initial unsafe block signer address. * @param _unsafeBlockSigner Initial unsafe block signer address.
* @param _config Initial resource config.
*/ */
constructor( constructor(
address _owner, address _owner,
...@@ -94,13 +103,23 @@ contract SystemConfig is OwnableUpgradeable, Semver { ...@@ -94,13 +103,23 @@ contract SystemConfig is OwnableUpgradeable, Semver {
uint256 _scalar, uint256 _scalar,
bytes32 _batcherHash, bytes32 _batcherHash,
uint64 _gasLimit, uint64 _gasLimit,
address _unsafeBlockSigner address _unsafeBlockSigner,
) Semver(1, 0, 1) { ResourceMetering.ResourceConfig memory _config
initialize(_owner, _overhead, _scalar, _batcherHash, _gasLimit, _unsafeBlockSigner); ) Semver(1, 1, 0) {
initialize({
_owner: _owner,
_overhead: _overhead,
_scalar: _scalar,
_batcherHash: _batcherHash,
_gasLimit: _gasLimit,
_unsafeBlockSigner: _unsafeBlockSigner,
_config: _config
});
} }
/** /**
* @notice Initializer. * @notice Initializer. The resource config must be set before the
* require check.
* *
* @param _owner Initial owner of the contract. * @param _owner Initial owner of the contract.
* @param _overhead Initial overhead value. * @param _overhead Initial overhead value.
...@@ -108,6 +127,7 @@ contract SystemConfig is OwnableUpgradeable, Semver { ...@@ -108,6 +127,7 @@ contract SystemConfig is OwnableUpgradeable, Semver {
* @param _batcherHash Initial batcher hash. * @param _batcherHash Initial batcher hash.
* @param _gasLimit Initial gas limit. * @param _gasLimit Initial gas limit.
* @param _unsafeBlockSigner Initial unsafe block signer address. * @param _unsafeBlockSigner Initial unsafe block signer address.
* @param _config Initial ResourceConfig.
*/ */
function initialize( function initialize(
address _owner, address _owner,
...@@ -115,9 +135,9 @@ contract SystemConfig is OwnableUpgradeable, Semver { ...@@ -115,9 +135,9 @@ contract SystemConfig is OwnableUpgradeable, Semver {
uint256 _scalar, uint256 _scalar,
bytes32 _batcherHash, bytes32 _batcherHash,
uint64 _gasLimit, uint64 _gasLimit,
address _unsafeBlockSigner address _unsafeBlockSigner,
ResourceMetering.ResourceConfig memory _config
) public initializer { ) public initializer {
require(_gasLimit >= MINIMUM_GAS_LIMIT, "SystemConfig: gas limit too low");
__Ownable_init(); __Ownable_init();
transferOwnership(_owner); transferOwnership(_owner);
overhead = _overhead; overhead = _overhead;
...@@ -125,6 +145,21 @@ contract SystemConfig is OwnableUpgradeable, Semver { ...@@ -125,6 +145,21 @@ contract SystemConfig is OwnableUpgradeable, Semver {
batcherHash = _batcherHash; batcherHash = _batcherHash;
gasLimit = _gasLimit; gasLimit = _gasLimit;
_setUnsafeBlockSigner(_unsafeBlockSigner); _setUnsafeBlockSigner(_unsafeBlockSigner);
_setResourceConfig(_config);
require(_gasLimit >= minimumGasLimit(), "SystemConfig: gas limit too low");
}
/**
* @notice Returns the minimum L2 gas limit that can be safely set for the system to
* operate. The L2 gas limit must be larger than or equal to the amount of
* gas that is allocated for deposits per block plus the amount of gas that
* is allocated for the system transaction.
* This function is used to determine if changes to parameters are safe.
*
* @return uint64
*/
function minimumGasLimit() public view returns (uint64) {
return uint64(_resourceConfig.maxResourceLimit) + uint64(_resourceConfig.systemTxMaxGas);
} }
/** /**
...@@ -188,7 +223,7 @@ contract SystemConfig is OwnableUpgradeable, Semver { ...@@ -188,7 +223,7 @@ contract SystemConfig is OwnableUpgradeable, Semver {
* @param _gasLimit New gas limit. * @param _gasLimit New gas limit.
*/ */
function setGasLimit(uint64 _gasLimit) external onlyOwner { function setGasLimit(uint64 _gasLimit) external onlyOwner {
require(_gasLimit >= MINIMUM_GAS_LIMIT, "SystemConfig: gas limit too low"); require(_gasLimit >= minimumGasLimit(), "SystemConfig: gas limit too low");
gasLimit = _gasLimit; gasLimit = _gasLimit;
bytes memory data = abi.encode(_gasLimit); bytes memory data = abi.encode(_gasLimit);
...@@ -207,4 +242,60 @@ contract SystemConfig is OwnableUpgradeable, Semver { ...@@ -207,4 +242,60 @@ contract SystemConfig is OwnableUpgradeable, Semver {
sstore(slot, _unsafeBlockSigner) sstore(slot, _unsafeBlockSigner)
} }
} }
/**
* @notice A getter for the resource config. Ensures that the struct is
* returned instead of a tuple.
*
* @return ResourceConfig
*/
function resourceConfig() external view returns (ResourceMetering.ResourceConfig memory) {
return _resourceConfig;
}
/**
* @notice An external setter for the resource config. In the future, this
* method may emit an event that the `op-node` picks up for when the
* resource config is changed.
*
* @param _config The new resource config values.
*/
function setResourceConfig(ResourceMetering.ResourceConfig memory _config) external onlyOwner {
_setResourceConfig(_config);
}
/**
* @notice An internal setter for the resource config. Ensures that the
* config is sane before storing it by checking for invariants.
*
* @param _config The new resource config.
*/
function _setResourceConfig(ResourceMetering.ResourceConfig memory _config) internal {
// Min base fee must be less than or equal to max base fee.
require(
_config.minimumBaseFee <= _config.maximumBaseFee,
"SystemConfig: min base fee must be less than max base"
);
// Base fee change denominator must be greater than 0.
require(_config.baseFeeMaxChangeDenominator > 0, "SystemConfig: denominator cannot be 0");
// Max resource limit plus system tx gas must be less than or equal to the L2 gas limit.
// The gas limit must be increased before these values can be increased.
require(
_config.maxResourceLimit + _config.systemTxMaxGas <= gasLimit,
"SystemConfig: gas limit too low"
);
// Elasticity multiplier must be greater than 0.
require(
_config.elasticityMultiplier > 0,
"SystemConfig: elasticity multiplier cannot be 0"
);
// No precision loss when computing target resource limit.
require(
((_config.maxResourceLimit / _config.elasticityMultiplier) *
_config.elasticityMultiplier) == _config.maxResourceLimit,
"SystemConfig: precision loss with target resource limit"
);
_resourceConfig = _config;
}
} }
...@@ -16,6 +16,8 @@ import { ProxyAdmin } from "../universal/ProxyAdmin.sol"; ...@@ -16,6 +16,8 @@ import { ProxyAdmin } from "../universal/ProxyAdmin.sol";
import { OptimismMintableERC20Factory } from "../universal/OptimismMintableERC20Factory.sol"; import { OptimismMintableERC20Factory } from "../universal/OptimismMintableERC20Factory.sol";
import { PortalSender } from "./PortalSender.sol"; import { PortalSender } from "./PortalSender.sol";
import { SystemConfig } from "../L1/SystemConfig.sol"; import { SystemConfig } from "../L1/SystemConfig.sol";
import { ResourceMetering } from "../L1/ResourceMetering.sol";
import { Constants } from "../libraries/Constants.sol";
/** /**
* @title SystemDictator * @title SystemDictator
...@@ -79,6 +81,7 @@ contract SystemDictator is OwnableUpgradeable { ...@@ -79,6 +81,7 @@ contract SystemDictator is OwnableUpgradeable {
bytes32 batcherHash; bytes32 batcherHash;
uint64 gasLimit; uint64 gasLimit;
address unsafeBlockSigner; address unsafeBlockSigner;
ResourceMetering.ResourceConfig resourceConfig;
} }
/** /**
...@@ -160,6 +163,8 @@ contract SystemDictator is OwnableUpgradeable { ...@@ -160,6 +163,8 @@ contract SystemDictator is OwnableUpgradeable {
* initialized upon deployment. * initialized upon deployment.
*/ */
constructor() { constructor() {
ResourceMetering.ResourceConfig memory rcfg = Constants.DEFAULT_RESOURCE_CONFIG();
// Using this shorter variable as an alias for address(0) just prevents us from having to // Using this shorter variable as an alias for address(0) just prevents us from having to
// to use a new line for every single parameter. // to use a new line for every single parameter.
address zero = address(0); address zero = address(0);
...@@ -177,7 +182,7 @@ contract SystemDictator is OwnableUpgradeable { ...@@ -177,7 +182,7 @@ contract SystemDictator is OwnableUpgradeable {
PortalSender(zero), PortalSender(zero),
SystemConfig(zero) SystemConfig(zero)
), ),
SystemConfigConfig(zero, 0, 0, bytes32(0), 0, zero) SystemConfigConfig(zero, 0, 0, bytes32(0), 0, zero, rcfg)
) )
); );
} }
...@@ -244,7 +249,8 @@ contract SystemDictator is OwnableUpgradeable { ...@@ -244,7 +249,8 @@ contract SystemDictator is OwnableUpgradeable {
config.systemConfigConfig.scalar, config.systemConfigConfig.scalar,
config.systemConfigConfig.batcherHash, config.systemConfigConfig.batcherHash,
config.systemConfigConfig.gasLimit, config.systemConfigConfig.gasLimit,
config.systemConfigConfig.unsafeBlockSigner config.systemConfigConfig.unsafeBlockSigner,
config.systemConfigConfig.resourceConfig
) )
) )
); );
......
...@@ -3,16 +3,32 @@ pragma solidity 0.8.15; ...@@ -3,16 +3,32 @@ pragma solidity 0.8.15;
import { OptimismPortal } from "../L1/OptimismPortal.sol"; import { OptimismPortal } from "../L1/OptimismPortal.sol";
import { L2OutputOracle } from "../L1/L2OutputOracle.sol"; import { L2OutputOracle } from "../L1/L2OutputOracle.sol";
import { AddressAliasHelper } from "../vendor/AddressAliasHelper.sol"; import { AddressAliasHelper } from "../vendor/AddressAliasHelper.sol";
import { SystemConfig } from "../L1/SystemConfig.sol";
import { ResourceMetering } from "../L1/ResourceMetering.sol";
import { Constants } from "../libraries/Constants.sol";
contract EchidnaFuzzOptimismPortal { contract EchidnaFuzzOptimismPortal {
OptimismPortal internal portal; OptimismPortal internal portal;
bool internal failedToComplete; bool internal failedToComplete;
constructor() { constructor() {
ResourceMetering.ResourceConfig memory rcfg = Constants.DEFAULT_RESOURCE_CONFIG();
SystemConfig systemConfig = new SystemConfig({
_owner: address(1),
_overhead: 0,
_scalar: 10000,
_batcherHash: bytes32(0),
_gasLimit: 30_000_000,
_unsafeBlockSigner: address(0),
_config: rcfg
});
portal = new OptimismPortal({ portal = new OptimismPortal({
_l2Oracle: L2OutputOracle(address(0)), _l2Oracle: L2OutputOracle(address(0)),
_guardian: address(0), _guardian: address(0),
_paused: false _paused: false,
_config: systemConfig
}); });
} }
......
...@@ -3,6 +3,7 @@ pragma solidity 0.8.15; ...@@ -3,6 +3,7 @@ pragma solidity 0.8.15;
import { ResourceMetering } from "../L1/ResourceMetering.sol"; import { ResourceMetering } from "../L1/ResourceMetering.sol";
import { Arithmetic } from "../libraries/Arithmetic.sol"; import { Arithmetic } from "../libraries/Arithmetic.sol";
import { StdUtils } from "forge-std/Test.sol"; import { StdUtils } from "forge-std/Test.sol";
import { Constants } from "../libraries/Constants.sol";
contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils { contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils {
bool internal failedMaxGasPerBlock; bool internal failedMaxGasPerBlock;
...@@ -24,6 +25,20 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils { ...@@ -24,6 +25,20 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils {
__ResourceMetering_init(); __ResourceMetering_init();
} }
function resourceConfig() public pure returns (ResourceMetering.ResourceConfig memory) {
return _resourceConfig();
}
function _resourceConfig()
internal
pure
override
returns (ResourceMetering.ResourceConfig memory)
{
ResourceMetering.ResourceConfig memory rcfg = Constants.DEFAULT_RESOURCE_CONFIG();
return rcfg;
}
/** /**
* @notice Takes the necessary parameters to allow us to burn arbitrary amounts of gas to test * @notice Takes the necessary parameters to allow us to burn arbitrary amounts of gas to test
* the underlying resource metering/gas market logic * the underlying resource metering/gas market logic
...@@ -34,12 +49,16 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils { ...@@ -34,12 +49,16 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils {
uint256 cachedPrevBoughtGas = uint256(params.prevBoughtGas); uint256 cachedPrevBoughtGas = uint256(params.prevBoughtGas);
uint256 cachedPrevBlockNum = uint256(params.prevBlockNum); uint256 cachedPrevBlockNum = uint256(params.prevBlockNum);
ResourceMetering.ResourceConfig memory rcfg = resourceConfig();
uint256 targetResourceLimit = uint256(rcfg.maxResourceLimit) /
uint256(rcfg.elasticityMultiplier);
// check that the last block's base fee hasn't dropped below the minimum // check that the last block's base fee hasn't dropped below the minimum
if (cachedPrevBaseFee < uint256(MINIMUM_BASE_FEE)) { if (cachedPrevBaseFee < uint256(rcfg.minimumBaseFee)) {
failedNeverBelowMinBaseFee = true; failedNeverBelowMinBaseFee = true;
} }
// check that the last block didn't consume more than the max amount of gas // check that the last block didn't consume more than the max amount of gas
if (cachedPrevBoughtGas > uint256(MAX_RESOURCE_LIMIT)) { if (cachedPrevBoughtGas > uint256(rcfg.maxResourceLimit)) {
failedMaxGasPerBlock = true; failedMaxGasPerBlock = true;
} }
...@@ -51,11 +70,11 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils { ...@@ -51,11 +70,11 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils {
if (_raiseBaseFee) { if (_raiseBaseFee) {
gasToBurn = bound( gasToBurn = bound(
_gasToBurn, _gasToBurn,
uint256(TARGET_RESOURCE_LIMIT), uint256(targetResourceLimit),
uint256(MAX_RESOURCE_LIMIT) uint256(rcfg.maxResourceLimit)
); );
} else { } else {
gasToBurn = bound(_gasToBurn, 0, uint256(TARGET_RESOURCE_LIMIT)); gasToBurn = bound(_gasToBurn, 0, targetResourceLimit);
} }
_burnInternal(uint64(gasToBurn)); _burnInternal(uint64(gasToBurn));
...@@ -63,13 +82,13 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils { ...@@ -63,13 +82,13 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils {
// Part 3: we run checks and modify our invariant flags based on the updated params values // Part 3: we run checks and modify our invariant flags based on the updated params values
// Calculate the maximum allowed baseFee change (per block) // Calculate the maximum allowed baseFee change (per block)
uint256 maxBaseFeeChange = cachedPrevBaseFee / uint256(BASE_FEE_MAX_CHANGE_DENOMINATOR); uint256 maxBaseFeeChange = cachedPrevBaseFee / uint256(rcfg.baseFeeMaxChangeDenominator);
// If the last block used more than the target amount of gas (and there were no // If the last block used more than the target amount of gas (and there were no
// empty blocks in between), ensure this block's baseFee increased, but not by // empty blocks in between), ensure this block's baseFee increased, but not by
// more than the max amount per block // more than the max amount per block
if ( if (
(cachedPrevBoughtGas > uint256(TARGET_RESOURCE_LIMIT)) && (cachedPrevBoughtGas > uint256(targetResourceLimit)) &&
(uint256(params.prevBlockNum) - cachedPrevBlockNum == 1) (uint256(params.prevBlockNum) - cachedPrevBlockNum == 1)
) { ) {
failedRaiseBaseFee = failedRaiseBaseFee || (params.prevBaseFee <= cachedPrevBaseFee); failedRaiseBaseFee = failedRaiseBaseFee || (params.prevBaseFee <= cachedPrevBaseFee);
...@@ -81,7 +100,7 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils { ...@@ -81,7 +100,7 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils {
// If the last block used less than the target amount of gas, (or was empty), // If the last block used less than the target amount of gas, (or was empty),
// ensure that: this block's baseFee was decreased, but not by more than the max amount // ensure that: this block's baseFee was decreased, but not by more than the max amount
if ( if (
(cachedPrevBoughtGas < uint256(TARGET_RESOURCE_LIMIT)) || (cachedPrevBoughtGas < uint256(targetResourceLimit)) ||
(uint256(params.prevBlockNum) - cachedPrevBlockNum > 1) (uint256(params.prevBlockNum) - cachedPrevBlockNum > 1)
) { ) {
// Invariant: baseFee should decrease // Invariant: baseFee should decrease
...@@ -104,11 +123,11 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils { ...@@ -104,11 +123,11 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils {
Arithmetic.clamp( Arithmetic.clamp(
Arithmetic.cdexp( Arithmetic.cdexp(
int256(cachedPrevBaseFee), int256(cachedPrevBaseFee),
BASE_FEE_MAX_CHANGE_DENOMINATOR, int256(uint256(rcfg.baseFeeMaxChangeDenominator)),
int256(uint256(params.prevBlockNum) - cachedPrevBlockNum) int256(uint256(params.prevBlockNum) - cachedPrevBlockNum)
), ),
MINIMUM_BASE_FEE, int256(uint256(rcfg.minimumBaseFee)),
MAXIMUM_BASE_FEE int256(uint256(rcfg.maximumBaseFee))
) )
); );
} }
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
import { ResourceMetering } from "../L1/ResourceMetering.sol";
/** /**
* @title Constants * @title Constants
* @notice Constants is a library for storing constants. Simple! Don't put everything in here, just * @notice Constants is a library for storing constants. Simple! Don't put everything in here, just
...@@ -24,4 +26,24 @@ library Constants { ...@@ -24,4 +26,24 @@ library Constants {
* non-zero to reduce the gas cost of message passing transactions. * non-zero to reduce the gas cost of message passing transactions.
*/ */
address internal constant DEFAULT_L2_SENDER = 0x000000000000000000000000000000000000dEaD; address internal constant DEFAULT_L2_SENDER = 0x000000000000000000000000000000000000dEaD;
/**
* @notice Returns the default values for the ResourceConfig. These are the recommended values
* for a production network.
*/
function DEFAULT_RESOURCE_CONFIG()
internal
pure
returns (ResourceMetering.ResourceConfig memory)
{
ResourceMetering.ResourceConfig memory config = ResourceMetering.ResourceConfig({
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
minimumBaseFee: 1 gwei,
systemTxMaxGas: 1_000_000,
maximumBaseFee: type(uint128).max
});
return config;
}
} }
...@@ -32,8 +32,6 @@ contract SetPrevBaseFee_Test is Portal_Initializer { ...@@ -32,8 +32,6 @@ contract SetPrevBaseFee_Test is Portal_Initializer {
// In order to achieve this we make no assertions, and handle everything else in the setUp() // In order to achieve this we make no assertions, and handle everything else in the setUp()
// function. // function.
contract GasBenchMark_OptimismPortal is Portal_Initializer { contract GasBenchMark_OptimismPortal is Portal_Initializer {
uint128 internal INITIAL_BASE_FEE;
// Reusable default values for a test withdrawal // Reusable default values for a test withdrawal
Types.WithdrawalTransaction _defaultTx; Types.WithdrawalTransaction _defaultTx;
...@@ -86,8 +84,6 @@ contract GasBenchMark_OptimismPortal is Portal_Initializer { ...@@ -86,8 +84,6 @@ contract GasBenchMark_OptimismPortal is Portal_Initializer {
1 1
); );
INITIAL_BASE_FEE = op.INITIAL_BASE_FEE();
// Fund the portal so that we can withdraw ETH. // Fund the portal so that we can withdraw ETH.
vm.deal(address(op), 0xFFFFFFFF); vm.deal(address(op), 0xFFFFFFFF);
} }
...@@ -103,7 +99,7 @@ contract GasBenchMark_OptimismPortal is Portal_Initializer { ...@@ -103,7 +99,7 @@ contract GasBenchMark_OptimismPortal is Portal_Initializer {
} }
function test_depositTransaction_benchmark_1() external { function test_depositTransaction_benchmark_1() external {
setPrevBaseFee(vm, address(op), INITIAL_BASE_FEE); setPrevBaseFee(vm, address(op), 1 gwei);
op.depositTransaction{ value: NON_ZERO_VALUE }( op.depositTransaction{ value: NON_ZERO_VALUE }(
NON_ZERO_ADDRESS, NON_ZERO_ADDRESS,
ZERO_VALUE, ZERO_VALUE,
...@@ -124,16 +120,9 @@ contract GasBenchMark_OptimismPortal is Portal_Initializer { ...@@ -124,16 +120,9 @@ contract GasBenchMark_OptimismPortal is Portal_Initializer {
} }
contract GasBenchMark_L1CrossDomainMessenger is Messenger_Initializer { contract GasBenchMark_L1CrossDomainMessenger is Messenger_Initializer {
uint128 internal INITIAL_BASE_FEE;
function setUp() public virtual override {
super.setUp();
INITIAL_BASE_FEE = op.INITIAL_BASE_FEE();
}
function test_sendMessage_benchmark_0() external { function test_sendMessage_benchmark_0() external {
vm.pauseGasMetering(); vm.pauseGasMetering();
setPrevBaseFee(vm, address(op), INITIAL_BASE_FEE); setPrevBaseFee(vm, address(op), 1 gwei);
// The amount of data typically sent during a bridge deposit. // The amount of data typically sent during a bridge deposit.
bytes bytes
memory data = hex"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; memory data = hex"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
...@@ -153,11 +142,8 @@ contract GasBenchMark_L1CrossDomainMessenger is Messenger_Initializer { ...@@ -153,11 +142,8 @@ contract GasBenchMark_L1CrossDomainMessenger is Messenger_Initializer {
} }
contract GasBenchMark_L1StandardBridge_Deposit is Bridge_Initializer { contract GasBenchMark_L1StandardBridge_Deposit is Bridge_Initializer {
uint128 internal INITIAL_BASE_FEE;
function setUp() public virtual override { function setUp() public virtual override {
super.setUp(); super.setUp();
INITIAL_BASE_FEE = op.INITIAL_BASE_FEE();
deal(address(L1Token), alice, 100000, true); deal(address(L1Token), alice, 100000, true);
vm.startPrank(alice, alice); vm.startPrank(alice, alice);
L1Token.approve(address(L1Bridge), type(uint256).max); L1Token.approve(address(L1Bridge), type(uint256).max);
...@@ -165,7 +151,7 @@ contract GasBenchMark_L1StandardBridge_Deposit is Bridge_Initializer { ...@@ -165,7 +151,7 @@ contract GasBenchMark_L1StandardBridge_Deposit is Bridge_Initializer {
function test_depositETH_benchmark_0() external { function test_depositETH_benchmark_0() external {
vm.pauseGasMetering(); vm.pauseGasMetering();
setPrevBaseFee(vm, address(op), INITIAL_BASE_FEE); setPrevBaseFee(vm, address(op), 1 gwei);
vm.resumeGasMetering(); vm.resumeGasMetering();
L1Bridge.depositETH{ value: 500 }(50000, hex""); L1Bridge.depositETH{ value: 500 }(50000, hex"");
} }
...@@ -179,7 +165,7 @@ contract GasBenchMark_L1StandardBridge_Deposit is Bridge_Initializer { ...@@ -179,7 +165,7 @@ contract GasBenchMark_L1StandardBridge_Deposit is Bridge_Initializer {
function test_depositERC20_benchmark_0() external { function test_depositERC20_benchmark_0() external {
vm.pauseGasMetering(); vm.pauseGasMetering();
setPrevBaseFee(vm, address(op), INITIAL_BASE_FEE); setPrevBaseFee(vm, address(op), 1 gwei);
vm.resumeGasMetering(); vm.resumeGasMetering();
L1Bridge.bridgeERC20({ L1Bridge.bridgeERC20({
_localToken: address(L1Token), _localToken: address(L1Token),
......
...@@ -139,16 +139,20 @@ contract Bytes_slice_Test is Test { ...@@ -139,16 +139,20 @@ contract Bytes_slice_Test is Test {
vm.assume(_length <= _input.length - _start); vm.assume(_length <= _input.length - _start);
// Grab the free memory pointer before the slice operation // Grab the free memory pointer before the slice operation
uint256 initPtr; uint64 initPtr;
assembly { assembly {
initPtr := mload(0x40) initPtr := mload(0x40)
} }
uint64 expectedPtr = uint64(initPtr + 0x20 + ((_length + 0x1f) & ~uint256(0x1f)));
// Ensure that all memory outside of the expected range is safe.
vm.expectSafeMemory(initPtr, expectedPtr);
// Slice the input bytes array from `_start` to `_start + _length` // Slice the input bytes array from `_start` to `_start + _length`
bytes memory slice = Bytes.slice(_input, _start, _length); bytes memory slice = Bytes.slice(_input, _start, _length);
// Grab the free memory pointer after the slice operation // Grab the free memory pointer after the slice operation
uint256 finalPtr; uint64 finalPtr;
assembly { assembly {
finalPtr := mload(0x40) finalPtr := mload(0x40)
} }
...@@ -165,10 +169,11 @@ contract Bytes_slice_Test is Test { ...@@ -165,10 +169,11 @@ contract Bytes_slice_Test is Test {
// Note that we use a slightly less efficient, but equivalent method of rounding // Note that we use a slightly less efficient, but equivalent method of rounding
// up `_length` to the next multiple of 32 than is used in the `slice` function. // up `_length` to the next multiple of 32 than is used in the `slice` function.
// This is to diff test the method used in `slice`. // This is to diff test the method used in `slice`.
assertEq(finalPtr, initPtr + 0x20 + (((_length + 0x1F) >> 5) << 5)); uint64 _expectedPtr = uint64(initPtr + 0x20 + (((_length + 0x1F) >> 5) << 5));
assertEq(finalPtr, _expectedPtr);
// Sanity check for equivalence of the rounding methods. // Sanity check for equivalence of the rounding methods.
assertEq(((_length + 0x1F) >> 5) << 5, (_length + 0x1F) & ~uint256(0x1F)); assertEq(_expectedPtr, expectedPtr);
} }
// The slice length should be equal to `_length` // The slice length should be equal to `_length`
......
...@@ -28,6 +28,9 @@ import { L1ChugSplashProxy } from "../legacy/L1ChugSplashProxy.sol"; ...@@ -28,6 +28,9 @@ import { L1ChugSplashProxy } from "../legacy/L1ChugSplashProxy.sol";
import { IL1ChugSplashDeployer } from "../legacy/L1ChugSplashProxy.sol"; import { IL1ChugSplashDeployer } from "../legacy/L1ChugSplashProxy.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { LegacyMintableERC20 } from "../legacy/LegacyMintableERC20.sol"; import { LegacyMintableERC20 } from "../legacy/LegacyMintableERC20.sol";
import { SystemConfig } from "../L1/SystemConfig.sol";
import { ResourceMetering } from "../L1/ResourceMetering.sol";
import { Constants } from "../libraries/Constants.sol";
contract CommonTest is Test { contract CommonTest is Test {
address alice = address(128); address alice = address(128);
...@@ -158,6 +161,7 @@ contract Portal_Initializer is L2OutputOracle_Initializer { ...@@ -158,6 +161,7 @@ contract Portal_Initializer is L2OutputOracle_Initializer {
// Test target // Test target
OptimismPortal internal opImpl; OptimismPortal internal opImpl;
OptimismPortal internal op; OptimismPortal internal op;
SystemConfig systemConfig;
event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success); event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success);
event WithdrawalProven( event WithdrawalProven(
...@@ -169,7 +173,25 @@ contract Portal_Initializer is L2OutputOracle_Initializer { ...@@ -169,7 +173,25 @@ contract Portal_Initializer is L2OutputOracle_Initializer {
function setUp() public virtual override { function setUp() public virtual override {
super.setUp(); super.setUp();
opImpl = new OptimismPortal({ _l2Oracle: oracle, _guardian: guardian, _paused: true }); ResourceMetering.ResourceConfig memory config = Constants.DEFAULT_RESOURCE_CONFIG();
systemConfig = new SystemConfig({
_owner: address(1),
_overhead: 0,
_scalar: 10000,
_batcherHash: bytes32(0),
_gasLimit: 30_000_000,
_unsafeBlockSigner: address(0),
_config: config
});
opImpl = new OptimismPortal({
_l2Oracle: oracle,
_guardian: guardian,
_paused: true,
_config: systemConfig
});
Proxy proxy = new Proxy(multisig); Proxy proxy = new Proxy(multisig);
vm.prank(multisig); vm.prank(multisig);
proxy.upgradeToAndCall( proxy.upgradeToAndCall(
......
...@@ -9,6 +9,7 @@ import { OptimismPortal } from "../L1/OptimismPortal.sol"; ...@@ -9,6 +9,7 @@ import { OptimismPortal } from "../L1/OptimismPortal.sol";
import { Types } from "../libraries/Types.sol"; import { Types } from "../libraries/Types.sol";
import { Hashing } from "../libraries/Hashing.sol"; import { Hashing } from "../libraries/Hashing.sol";
import { Proxy } from "../universal/Proxy.sol"; import { Proxy } from "../universal/Proxy.sol";
import { ResourceMetering } from "../L1/ResourceMetering.sol";
contract OptimismPortal_Test is Portal_Initializer { contract OptimismPortal_Test is Portal_Initializer {
event Paused(address); event Paused(address);
...@@ -1045,10 +1046,12 @@ contract OptimismPortalUpgradeable_Test is Portal_Initializer { ...@@ -1045,10 +1046,12 @@ contract OptimismPortalUpgradeable_Test is Portal_Initializer {
} }
function test_params_initValuesOnProxy_succeeds() external { function test_params_initValuesOnProxy_succeeds() external {
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = OptimismPortal( OptimismPortal p = OptimismPortal(payable(address(proxy)));
payable(address(proxy))
).params(); (uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = p.params();
assertEq(prevBaseFee, opImpl.INITIAL_BASE_FEE());
ResourceMetering.ResourceConfig memory rcfg = systemConfig.resourceConfig();
assertEq(prevBaseFee, rcfg.minimumBaseFee);
assertEq(prevBoughtGas, 0); assertEq(prevBoughtGas, 0);
assertEq(prevBlockNum, initialBlockNum); assertEq(prevBlockNum, initialBlockNum);
} }
......
...@@ -4,6 +4,7 @@ pragma solidity 0.8.15; ...@@ -4,6 +4,7 @@ pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
import { ResourceMetering } from "../L1/ResourceMetering.sol"; import { ResourceMetering } from "../L1/ResourceMetering.sol";
import { Proxy } from "../universal/Proxy.sol"; import { Proxy } from "../universal/Proxy.sol";
import { Constants } from "../libraries/Constants.sol";
contract MeterUser is ResourceMetering { contract MeterUser is ResourceMetering {
constructor() { constructor() {
...@@ -14,6 +15,19 @@ contract MeterUser is ResourceMetering { ...@@ -14,6 +15,19 @@ contract MeterUser is ResourceMetering {
__ResourceMetering_init(); __ResourceMetering_init();
} }
function resourceConfig() public pure returns (ResourceMetering.ResourceConfig memory) {
return _resourceConfig();
}
function _resourceConfig()
internal
pure
override
returns (ResourceMetering.ResourceConfig memory)
{
return Constants.DEFAULT_RESOURCE_CONFIG();
}
function use(uint64 _amount) public metered(_amount) {} function use(uint64 _amount) public metered(_amount) {}
function set( function set(
...@@ -29,6 +43,11 @@ contract MeterUser is ResourceMetering { ...@@ -29,6 +43,11 @@ contract MeterUser is ResourceMetering {
} }
} }
/**
* @title ResourceConfig
* @notice The tests are based on the default config values. It is expected that
* the config values used in these tests are ran in production.
*/
contract ResourceMetering_Test is Test { contract ResourceMetering_Test is Test {
MeterUser internal meter; MeterUser internal meter;
uint64 initialBlockNum; uint64 initialBlockNum;
...@@ -38,42 +57,15 @@ contract ResourceMetering_Test is Test { ...@@ -38,42 +57,15 @@ contract ResourceMetering_Test is Test {
initialBlockNum = uint64(block.number); initialBlockNum = uint64(block.number);
} }
/**
* @notice The INITIAL_BASE_FEE must be less than the MAXIMUM_BASE_FEE
* and greater than the MINIMUM_BASE_FEE.
*/
function test_meter_initialBaseFee_succeeds() external {
uint256 max = uint256(meter.MAXIMUM_BASE_FEE());
uint256 min = uint256(meter.MINIMUM_BASE_FEE());
uint256 initial = uint256(meter.INITIAL_BASE_FEE());
assertTrue(max >= initial);
assertTrue(min <= initial);
}
/**
* @notice The MINIMUM_BASE_FEE must be less than the MAXIMUM_BASE_FEE.
*/
function test_meter_minBaseFeeLessThanMaxBaseFee_succeeds() external {
uint256 max = uint256(meter.MAXIMUM_BASE_FEE());
uint256 min = uint256(meter.MINIMUM_BASE_FEE());
assertTrue(max > min);
}
function test_meter_initialResourceParams_succeeds() external { function test_meter_initialResourceParams_succeeds() external {
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params(); (uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params();
ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig();
assertEq(prevBaseFee, meter.INITIAL_BASE_FEE()); assertEq(prevBaseFee, rcfg.minimumBaseFee);
assertEq(prevBoughtGas, 0); assertEq(prevBoughtGas, 0);
assertEq(prevBlockNum, initialBlockNum); assertEq(prevBlockNum, initialBlockNum);
} }
function test_meter_maxValue_succeeds() external {
uint256 max = uint256(meter.MAX_RESOURCE_LIMIT());
uint256 target = uint256(meter.TARGET_RESOURCE_LIMIT());
uint256 elasticity = uint256(meter.ELASTICITY_MULTIPLIER());
assertEq(max / elasticity, target);
}
function test_meter_updateParamsNoChange_succeeds() external { function test_meter_updateParamsNoChange_succeeds() external {
meter.use(0); // equivalent to just updating the base fee and block number meter.use(0); // equivalent to just updating the base fee and block number
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params(); (uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params();
...@@ -116,8 +108,9 @@ contract ResourceMetering_Test is Test { ...@@ -116,8 +108,9 @@ contract ResourceMetering_Test is Test {
} }
function test_meter_updateNoGasDelta_succeeds() external { function test_meter_updateNoGasDelta_succeeds() external {
uint64 target = uint64(uint256(meter.TARGET_RESOURCE_LIMIT())); ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig();
meter.use(target); uint256 target = uint256(rcfg.maxResourceLimit) / uint256(rcfg.elasticityMultiplier);
meter.use(uint64(target));
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params(); (uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params();
assertEq(prevBaseFee, 1000000000); assertEq(prevBaseFee, 1000000000);
...@@ -126,12 +119,14 @@ contract ResourceMetering_Test is Test { ...@@ -126,12 +119,14 @@ contract ResourceMetering_Test is Test {
} }
function test_meter_useMax_succeeds() external { function test_meter_useMax_succeeds() external {
uint64 target = uint64(uint256(meter.TARGET_RESOURCE_LIMIT())); ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig();
uint64 elasticity = uint64(uint256(meter.ELASTICITY_MULTIPLIER())); uint64 target = uint64(rcfg.maxResourceLimit) / uint64(rcfg.elasticityMultiplier);
meter.use(target * elasticity); uint64 elasticityMultiplier = uint64(rcfg.elasticityMultiplier);
meter.use(target * elasticityMultiplier);
(, uint64 prevBoughtGas, ) = meter.params(); (, uint64 prevBoughtGas, ) = meter.params();
assertEq(prevBoughtGas, target * elasticity); assertEq(prevBoughtGas, target * elasticityMultiplier);
vm.roll(initialBlockNum + 1); vm.roll(initialBlockNum + 1);
meter.use(0); meter.use(0);
...@@ -140,10 +135,12 @@ contract ResourceMetering_Test is Test { ...@@ -140,10 +135,12 @@ contract ResourceMetering_Test is Test {
} }
function test_meter_useMoreThanMax_reverts() external { function test_meter_useMoreThanMax_reverts() external {
uint64 target = uint64(uint256(meter.TARGET_RESOURCE_LIMIT())); ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig();
uint64 elasticity = uint64(uint256(meter.ELASTICITY_MULTIPLIER())); uint64 target = uint64(rcfg.maxResourceLimit) / uint64(rcfg.elasticityMultiplier);
uint64 elasticityMultiplier = uint64(rcfg.elasticityMultiplier);
vm.expectRevert("ResourceMetering: cannot buy more gas than available gas limit"); vm.expectRevert("ResourceMetering: cannot buy more gas than available gas limit");
meter.use(target * elasticity + 1); meter.use(target * elasticityMultiplier + 1);
} }
// Demonstrates that the resource metering arithmetic can tolerate very large gaps between // Demonstrates that the resource metering arithmetic can tolerate very large gaps between
...@@ -153,9 +150,11 @@ contract ResourceMetering_Test is Test { ...@@ -153,9 +150,11 @@ contract ResourceMetering_Test is Test {
// At 12 seconds per block, this number is effectively unreachable. // At 12 seconds per block, this number is effectively unreachable.
vm.assume(_blockDiff < 433576281058164217753225238677900874458691); vm.assume(_blockDiff < 433576281058164217753225238677900874458691);
uint64 target = uint64(uint256(meter.TARGET_RESOURCE_LIMIT())); ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig();
uint64 elasticity = uint64(uint256(meter.ELASTICITY_MULTIPLIER())); uint64 target = uint64(rcfg.maxResourceLimit) / uint64(rcfg.elasticityMultiplier);
vm.assume(_amount < target * elasticity); uint64 elasticityMultiplier = uint64(rcfg.elasticityMultiplier);
vm.assume(_amount < target * elasticityMultiplier);
vm.roll(initialBlockNum + _blockDiff); vm.roll(initialBlockNum + _blockDiff);
meter.use(_amount); meter.use(_amount);
} }
...@@ -182,6 +181,15 @@ contract CustomMeterUser is ResourceMetering { ...@@ -182,6 +181,15 @@ contract CustomMeterUser is ResourceMetering {
}); });
} }
function _resourceConfig()
internal
pure
override
returns (ResourceMetering.ResourceConfig memory)
{
return Constants.DEFAULT_RESOURCE_CONFIG();
}
function use(uint64 _amount) public returns (uint256) { function use(uint64 _amount) public returns (uint256) {
uint256 initialGas = gasleft(); uint256 initialGas = gasleft();
_metered(_amount, initialGas); _metered(_amount, initialGas);
...@@ -224,10 +232,11 @@ contract ArtifactResourceMetering_Test is Test { ...@@ -224,10 +232,11 @@ contract ArtifactResourceMetering_Test is Test {
vm.roll(1_000_000); vm.roll(1_000_000);
MeterUser base = new MeterUser(); MeterUser base = new MeterUser();
minimumBaseFee = uint128(uint256(base.MINIMUM_BASE_FEE())); ResourceMetering.ResourceConfig memory rcfg = base.resourceConfig();
maximumBaseFee = uint128(uint256(base.MAXIMUM_BASE_FEE())); minimumBaseFee = uint128(rcfg.minimumBaseFee);
maxResourceLimit = uint64(uint256(base.MAX_RESOURCE_LIMIT())); maximumBaseFee = rcfg.maximumBaseFee;
targetResourceLimit = uint64(uint256(base.TARGET_RESOURCE_LIMIT())); maxResourceLimit = uint64(rcfg.maxResourceLimit);
targetResourceLimit = uint64(rcfg.maxResourceLimit) / uint64(rcfg.elasticityMultiplier);
outfile = string.concat(vm.projectRoot(), "/.resource-metering.csv"); outfile = string.concat(vm.projectRoot(), "/.resource-metering.csv");
try vm.removeFile(outfile) {} catch {} try vm.removeFile(outfile) {} catch {}
......
...@@ -3,36 +3,58 @@ pragma solidity 0.8.15; ...@@ -3,36 +3,58 @@ pragma solidity 0.8.15;
import { CommonTest } from "./CommonTest.t.sol"; import { CommonTest } from "./CommonTest.t.sol";
import { SystemConfig } from "../L1/SystemConfig.sol"; import { SystemConfig } from "../L1/SystemConfig.sol";
import { ResourceMetering } from "../L1/ResourceMetering.sol";
import { Constants } from "../libraries/Constants.sol";
contract SystemConfig_Init is CommonTest { contract SystemConfig_Init is CommonTest {
SystemConfig sysConf; SystemConfig sysConf;
function setUp() public virtual override { function setUp() public virtual override {
super.setUp(); super.setUp();
ResourceMetering.ResourceConfig memory config = ResourceMetering.ResourceConfig({
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
minimumBaseFee: 1 gwei,
systemTxMaxGas: 1_000_000,
maximumBaseFee: type(uint128).max
});
sysConf = new SystemConfig({ sysConf = new SystemConfig({
_owner: alice, _owner: alice,
_overhead: 2100, _overhead: 2100,
_scalar: 1000000, _scalar: 1000000,
_batcherHash: bytes32(hex"abcd"), _batcherHash: bytes32(hex"abcd"),
_gasLimit: 9_000_000, _gasLimit: 30_000_000,
_unsafeBlockSigner: address(1) _unsafeBlockSigner: address(1),
_config: config
}); });
} }
} }
contract SystemConfig_Initialize_TestFail is CommonTest { contract SystemConfig_Initialize_TestFail is SystemConfig_Init {
function test_initialize_lowGasLimit_reverts() external { function test_initialize_lowGasLimit_reverts() external {
vm.expectRevert("SystemConfig: gas limit too low"); uint64 minimumGasLimit = sysConf.minimumGasLimit();
ResourceMetering.ResourceConfig memory cfg = ResourceMetering.ResourceConfig({
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
minimumBaseFee: 1 gwei,
systemTxMaxGas: 1_000_000,
maximumBaseFee: type(uint128).max
});
// The minimum gas limit defined in SystemConfig: vm.expectRevert("SystemConfig: gas limit too low");
uint64 MINIMUM_GAS_LIMIT = 8_000_000;
new SystemConfig({ new SystemConfig({
_owner: alice, _owner: alice,
_overhead: 0, _overhead: 0,
_scalar: 0, _scalar: 0,
_batcherHash: bytes32(hex""), _batcherHash: bytes32(hex""),
_gasLimit: MINIMUM_GAS_LIMIT - 1, _gasLimit: minimumGasLimit - 1,
_unsafeBlockSigner: address(1) _unsafeBlockSigner: address(1),
_config: cfg
}); });
} }
} }
...@@ -57,6 +79,70 @@ contract SystemConfig_Setters_TestFail is SystemConfig_Init { ...@@ -57,6 +79,70 @@ contract SystemConfig_Setters_TestFail is SystemConfig_Init {
vm.expectRevert("Ownable: caller is not the owner"); vm.expectRevert("Ownable: caller is not the owner");
sysConf.setUnsafeBlockSigner(address(0x20)); sysConf.setUnsafeBlockSigner(address(0x20));
} }
function test_setResourceConfig_notOwner_reverts() external {
ResourceMetering.ResourceConfig memory config = Constants.DEFAULT_RESOURCE_CONFIG();
vm.expectRevert("Ownable: caller is not the owner");
sysConf.setResourceConfig(config);
}
function test_setResourceConfig_badMinMax_reverts() external {
ResourceMetering.ResourceConfig memory config = ResourceMetering.ResourceConfig({
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
systemTxMaxGas: 1_000_000,
minimumBaseFee: 2 gwei,
maximumBaseFee: 1 gwei
});
vm.prank(sysConf.owner());
vm.expectRevert("SystemConfig: min base fee must be less than max base");
sysConf.setResourceConfig(config);
}
function test_setResourceConfig_zeroDenominator_reverts() external {
ResourceMetering.ResourceConfig memory config = ResourceMetering.ResourceConfig({
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 0,
systemTxMaxGas: 1_000_000,
minimumBaseFee: 1 gwei,
maximumBaseFee: 2 gwei
});
vm.prank(sysConf.owner());
vm.expectRevert("SystemConfig: denominator cannot be 0");
sysConf.setResourceConfig(config);
}
function test_setResourceConfig_lowGasLimit_reverts() external {
uint64 gasLimit = sysConf.gasLimit();
ResourceMetering.ResourceConfig memory config = ResourceMetering.ResourceConfig({
maxResourceLimit: uint32(gasLimit),
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
systemTxMaxGas: uint32(gasLimit),
minimumBaseFee: 1 gwei,
maximumBaseFee: 2 gwei
});
vm.prank(sysConf.owner());
vm.expectRevert("SystemConfig: gas limit too low");
sysConf.setResourceConfig(config);
}
function test_setResourceConfig_badPrecision_reverts() external {
ResourceMetering.ResourceConfig memory config = ResourceMetering.ResourceConfig({
maxResourceLimit: 20_000_000,
elasticityMultiplier: 11,
baseFeeMaxChangeDenominator: 8,
systemTxMaxGas: 1_000_000,
minimumBaseFee: 1 gwei,
maximumBaseFee: 2 gwei
});
vm.prank(sysConf.owner());
vm.expectRevert("SystemConfig: precision loss with target resource limit");
sysConf.setResourceConfig(config);
}
} }
contract SystemConfig_Setters_Test is SystemConfig_Init { contract SystemConfig_Setters_Test is SystemConfig_Init {
......
...@@ -2,18 +2,23 @@ pragma solidity 0.8.15; ...@@ -2,18 +2,23 @@ pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
import { SystemConfig } from "../../L1/SystemConfig.sol"; import { SystemConfig } from "../../L1/SystemConfig.sol";
import { ResourceMetering } from "../../L1/ResourceMetering.sol";
import { Constants } from "../../libraries/Constants.sol";
contract SystemConfig_GasLimitLowerBound_Invariant is Test { contract SystemConfig_GasLimitLowerBound_Invariant is Test {
SystemConfig public config; SystemConfig public config;
function setUp() public { function setUp() public {
ResourceMetering.ResourceConfig memory cfg = Constants.DEFAULT_RESOURCE_CONFIG();
config = new SystemConfig({ config = new SystemConfig({
_owner: address(0xbeef), _owner: address(0xbeef),
_overhead: 2100, _overhead: 2100,
_scalar: 1000000, _scalar: 1000000,
_batcherHash: bytes32(hex"abcd"), _batcherHash: bytes32(hex"abcd"),
_gasLimit: 8_000_000, _gasLimit: 30_000_000,
_unsafeBlockSigner: address(1) _unsafeBlockSigner: address(1),
_config: cfg
}); });
// Set the target contract to the `config` // Set the target contract to the `config`
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
"l2OutputOracleStartingTimestamp": -1, "l2OutputOracleStartingTimestamp": -1,
"l2OutputOracleProposer": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", "l2OutputOracleProposer": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"l2OutputOracleChallenger": "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65", "l2OutputOracleChallenger": "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65",
"l2GenesisBlockGasLimit": "0xE4E1C0", "l2GenesisBlockGasLimit": "0x1c9c380",
"l1BlockTime": 3, "l1BlockTime": 3,
"cliqueSignerAddress": "0xca062b0fd91172d89bcd4bb084ac4e21972cc467", "cliqueSignerAddress": "0xca062b0fd91172d89bcd4bb084ac4e21972cc467",
"baseFeeVaultRecipient": "0xBcd4042DE499D14e55001CcbB24a551F3b954096", "baseFeeVaultRecipient": "0xBcd4042DE499D14e55001CcbB24a551F3b954096",
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
"governanceTokenSymbol": "OP", "governanceTokenSymbol": "OP",
"governanceTokenOwner": "0x038a8825A3C3B0c08d52Cc76E5E361953Cf6Dc76", "governanceTokenOwner": "0x038a8825A3C3B0c08d52Cc76E5E361953Cf6Dc76",
"l2GenesisBlockGasLimit": "0x17D7840", "l2GenesisBlockGasLimit": "0x1c9c380",
"l2GenesisBlockCoinbase": "0x4200000000000000000000000000000000000011", "l2GenesisBlockCoinbase": "0x4200000000000000000000000000000000000011",
"l2GenesisBlockBaseFeePerGas": "0x3b9aca00", "l2GenesisBlockBaseFeePerGas": "0x3b9aca00",
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
"l2OutputOracleSubmissionInterval": 120, "l2OutputOracleSubmissionInterval": 120,
"l2OutputOracleStartingBlockNumber": 0, "l2OutputOracleStartingBlockNumber": 0,
"l2OutputOracleStartingTimestamp": "TIMESTAMP", "l2OutputOracleStartingTimestamp": TIMESTAMP,
"l2OutputOracleProposer": "PROPOSER", "l2OutputOracleProposer": "PROPOSER",
"l2OutputOracleChallenger": "ADMIN", "l2OutputOracleChallenger": "ADMIN",
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
"governanceTokenName": "Optimism", "governanceTokenName": "Optimism",
"governanceTokenOwner": "ADMIN", "governanceTokenOwner": "ADMIN",
"l2GenesisBlockGasLimit": "0x17D7840", "l2GenesisBlockGasLimit": "0x1c9c380",
"l2GenesisBlockBaseFeePerGas": "0x3b9aca00", "l2GenesisBlockBaseFeePerGas": "0x3b9aca00",
"l2GenesisRegolithTimeOffset": "0x0", "l2GenesisRegolithTimeOffset": "0x0",
......
...@@ -17,6 +17,10 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -17,6 +17,10 @@ const deployFn: DeployFunction = async (hre) => {
'L2OutputOracleProxy' 'L2OutputOracleProxy'
) )
const Artifact__SystemConfigProxy = await hre.deployments.get(
'SystemConfigProxy'
)
const portalGuardian = hre.deployConfig.portalGuardian const portalGuardian = hre.deployConfig.portalGuardian
const portalGuardianCode = await hre.ethers.provider.getCode(portalGuardian) const portalGuardianCode = await hre.ethers.provider.getCode(portalGuardian)
if (portalGuardianCode === '0x') { if (portalGuardianCode === '0x') {
...@@ -41,6 +45,7 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -41,6 +45,7 @@ const deployFn: DeployFunction = async (hre) => {
L2OutputOracleProxy.address, L2OutputOracleProxy.address,
portalGuardian, portalGuardian,
true, // paused true, // paused
Artifact__SystemConfigProxy.address,
], ],
postDeployAction: async (contract) => { postDeployAction: async (contract) => {
await assertContractVariable( await assertContractVariable(
...@@ -53,6 +58,11 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -53,6 +58,11 @@ const deployFn: DeployFunction = async (hre) => {
'GUARDIAN', 'GUARDIAN',
hre.deployConfig.portalGuardian hre.deployConfig.portalGuardian
) )
await assertContractVariable(
contract,
'SYSTEM_CONFIG',
Artifact__SystemConfigProxy.address
)
}, },
}) })
} }
......
import assert from 'assert'
import { DeployFunction } from 'hardhat-deploy/dist/types' import { DeployFunction } from 'hardhat-deploy/dist/types'
import '@eth-optimism/hardhat-deploy-config' import '@eth-optimism/hardhat-deploy-config'
import { ethers } from 'ethers'
import { assertContractVariable, deploy } from '../src/deploy-utils' import { assertContractVariable, deploy } from '../src/deploy-utils'
const uint128Max = ethers.BigNumber.from('0xffffffffffffffffffffffffffffffff')
const deployFn: DeployFunction = async (hre) => { const deployFn: DeployFunction = async (hre) => {
const batcherHash = hre.ethers.utils const batcherHash = hre.ethers.utils
.hexZeroPad(hre.deployConfig.batchSenderAddress, 32) .hexZeroPad(hre.deployConfig.batchSenderAddress, 32)
...@@ -18,6 +23,14 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -18,6 +23,14 @@ const deployFn: DeployFunction = async (hre) => {
batcherHash, batcherHash,
hre.deployConfig.l2GenesisBlockGasLimit, hre.deployConfig.l2GenesisBlockGasLimit,
hre.deployConfig.p2pSequencerAddress, hre.deployConfig.p2pSequencerAddress,
{
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
systemTxMaxGas: 1_000_000,
minimumBaseFee: ethers.utils.parseUnits('1', 'gwei'),
maximumBaseFee: uint128Max,
},
], ],
postDeployAction: async (contract) => { postDeployAction: async (contract) => {
await assertContractVariable( await assertContractVariable(
...@@ -41,6 +54,14 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -41,6 +54,14 @@ const deployFn: DeployFunction = async (hre) => {
'unsafeBlockSigner', 'unsafeBlockSigner',
hre.deployConfig.p2pSequencerAddress hre.deployConfig.p2pSequencerAddress
) )
const config = await contract.resourceConfig()
assert(config.maxResourceLimit === 20_000_000)
assert(config.elasticityMultiplier === 10)
assert(config.baseFeeMaxChangeDenominator === 8)
assert(config.systemTxMaxGas === 1_000_000)
assert(ethers.utils.parseUnits('1', 'gwei').eq(config.minimumBaseFee))
assert(config.maximumBaseFee.eq(uint128Max))
}, },
}) })
} }
......
import assert from 'assert' import assert from 'assert'
import { ethers } from 'ethers' import { ethers, BigNumber } from 'ethers'
import { DeployFunction } from 'hardhat-deploy/dist/types' import { DeployFunction } from 'hardhat-deploy/dist/types'
import { awaitCondition } from '@eth-optimism/core-utils' import { awaitCondition } from '@eth-optimism/core-utils'
import '@eth-optimism/hardhat-deploy-config' import '@eth-optimism/hardhat-deploy-config'
...@@ -100,6 +100,18 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -100,6 +100,18 @@ const deployFn: DeployFunction = async (hre) => {
), ),
gasLimit: hre.deployConfig.l2GenesisBlockGasLimit, gasLimit: hre.deployConfig.l2GenesisBlockGasLimit,
unsafeBlockSigner: hre.deployConfig.p2pSequencerAddress, unsafeBlockSigner: hre.deployConfig.p2pSequencerAddress,
// The resource config is not exposed to the end user
// to simplify deploy config. It may be introduced in the future.
resourceConfig: {
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
minimumBaseFee: ethers.utils.parseUnits('1', 'gwei'),
systemTxMaxGas: 1_000_000,
maximumBaseFee: BigNumber.from(
'0xffffffffffffffffffffffffffffffff'
).toString(),
},
}, },
} }
......
...@@ -17,6 +17,8 @@ import { ...@@ -17,6 +17,8 @@ import {
getCastCommand, getCastCommand,
} from '../src/deploy-utils' } from '../src/deploy-utils'
const uint128Max = ethers.BigNumber.from('0xffffffffffffffffffffffffffffffff')
const deployFn: DeployFunction = async (hre) => { const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts() const { deployer } = await hre.getNamedAccounts()
...@@ -254,6 +256,14 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -254,6 +256,14 @@ const deployFn: DeployFunction = async (hre) => {
'gasLimit', 'gasLimit',
hre.deployConfig.l2GenesisBlockGasLimit hre.deployConfig.l2GenesisBlockGasLimit
) )
const config = await SystemConfigProxy.resourceConfig()
assert(config.maxResourceLimit === 20_000_000)
assert(config.elasticityMultiplier === 10)
assert(config.baseFeeMaxChangeDenominator === 8)
assert(config.systemTxMaxGas === 1_000_000)
assert(ethers.utils.parseUnits('1', 'gwei').eq(config.minimumBaseFee))
assert(config.maximumBaseFee.eq(uint128Max))
}, },
}) })
......
...@@ -182,6 +182,8 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -182,6 +182,8 @@ const deployFn: DeployFunction = async (hre) => {
false // do not pause the the OptimismPortal when initializing false // do not pause the the OptimismPortal when initializing
) )
} else { } else {
// pause the OptimismPortal when initializing
const optimismPortalPaused = true
const tx = await SystemDictator.populateTransaction.updateDynamicConfig( const tx = await SystemDictator.populateTransaction.updateDynamicConfig(
{ {
l2OutputOracleStartingBlockNumber: l2OutputOracleStartingBlockNumber:
...@@ -189,9 +191,22 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -189,9 +191,22 @@ const deployFn: DeployFunction = async (hre) => {
l2OutputOracleStartingTimestamp: l2OutputOracleStartingTimestamp:
hre.deployConfig.l2OutputOracleStartingTimestamp, hre.deployConfig.l2OutputOracleStartingTimestamp,
}, },
true optimismPortalPaused
) )
console.log(`Please update dynamic oracle config...`) console.log(`Please update dynamic oracle config...`)
console.log(
JSON.stringify(
{
l2OutputOracleStartingBlockNumber:
hre.deployConfig.l2OutputOracleStartingBlockNumber,
l2OutputOracleStartingTimestamp:
hre.deployConfig.l2OutputOracleStartingTimestamp,
optimismPortalPaused,
},
null,
2
)
)
console.log(`MSD address: ${SystemDictator.address}`) console.log(`MSD address: ${SystemDictator.address}`)
console.log(`JSON:`) console.log(`JSON:`)
console.log(jsonifyTransaction(tx)) console.log(jsonifyTransaction(tx))
...@@ -234,7 +249,7 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -234,7 +249,7 @@ const deployFn: DeployFunction = async (hre) => {
) )
const resourceParams = await OptimismPortal.params() const resourceParams = await OptimismPortal.params()
assert( assert(
resourceParams.prevBaseFee.eq(await OptimismPortal.INITIAL_BASE_FEE()), resourceParams.prevBaseFee.eq(ethers.utils.parseUnits('1', 'gwei')),
`OptimismPortal was not initialized with the correct initial base fee` `OptimismPortal was not initialized with the correct initial base fee`
) )
assert( assert(
......
...@@ -19,6 +19,6 @@ This invariant asserts that there is no chain of calls that can be made that wil ...@@ -19,6 +19,6 @@ This invariant asserts that there is no chain of calls that can be made that wil
## Deposits of any value should always succeed unless `_to` = `address(0)` or `_isCreation` = `true`. ## Deposits of any value should always succeed unless `_to` = `address(0)` or `_isCreation` = `true`.
**Test:** [`FuzzOptimismPortal.sol#L41`](../contracts/echidna/FuzzOptimismPortal.sol#L41) **Test:** [`FuzzOptimismPortal.sol#L57`](../contracts/echidna/FuzzOptimismPortal.sol#L57)
All deposits, barring creation transactions and transactions sent to `address(0)`, should always succeed. All deposits, barring creation transactions and transactions sent to `address(0)`, should always succeed.
# `ResourceMetering` Invariants # `ResourceMetering` Invariants
## The base fee should increase if the last block used more than the target amount of gas ## The base fee should increase if the last block used more than the target amount of gas
**Test:** [`FuzzResourceMetering.sol#L139`](../contracts/echidna/FuzzResourceMetering.sol#L139) **Test:** [`FuzzResourceMetering.sol#L158`](../contracts/echidna/FuzzResourceMetering.sol#L158)
If the last block used more than the target amount of gas (and there were no empty blocks in between), ensure this block's baseFee increased, but not by more than the max amount per block. If the last block used more than the target amount of gas (and there were no empty blocks in between), ensure this block's baseFee increased, but not by more than the max amount per block.
## The base fee should decrease if the last block used less than the target amount of gas ## The base fee should decrease if the last block used less than the target amount of gas
**Test:** [`FuzzResourceMetering.sol#L150`](../contracts/echidna/FuzzResourceMetering.sol#L150) **Test:** [`FuzzResourceMetering.sol#L169`](../contracts/echidna/FuzzResourceMetering.sol#L169)
If the previous block used less than the target amount of gas, the base fee should decrease, but not more than the max amount. If the previous block used less than the target amount of gas, the base fee should decrease, but not more than the max amount.
## A block's base fee should never be below `MINIMUM_BASE_FEE` ## A block's base fee should never be below `MINIMUM_BASE_FEE`
**Test:** [`FuzzResourceMetering.sol#L160`](../contracts/echidna/FuzzResourceMetering.sol#L160) **Test:** [`FuzzResourceMetering.sol#L179`](../contracts/echidna/FuzzResourceMetering.sol#L179)
This test asserts that a block's base fee can never drop below the `MINIMUM_BASE_FEE` threshold. This test asserts that a block's base fee can never drop below the `MINIMUM_BASE_FEE` threshold.
## A block can never consume more than `MAX_RESOURCE_LIMIT` gas. ## A block can never consume more than `MAX_RESOURCE_LIMIT` gas.
**Test:** [`FuzzResourceMetering.sol#L170`](../contracts/echidna/FuzzResourceMetering.sol#L170) **Test:** [`FuzzResourceMetering.sol#L189`](../contracts/echidna/FuzzResourceMetering.sol#L189)
This test asserts that a block can never consume more than the `MAX_RESOURCE_LIMIT` gas threshold. This test asserts that a block can never consume more than the `MAX_RESOURCE_LIMIT` gas threshold.
## The base fee can never be raised more than the max base fee change. ## The base fee can never be raised more than the max base fee change.
**Test:** [`FuzzResourceMetering.sol#L181`](../contracts/echidna/FuzzResourceMetering.sol#L181) **Test:** [`FuzzResourceMetering.sol#L200`](../contracts/echidna/FuzzResourceMetering.sol#L200)
After a block consumes more gas than the target gas, the base fee cannot be raised more than the maximum amount allowed. The max base fee change (per-block) is derived as follows: `prevBaseFee / BASE_FEE_MAX_CHANGE_DENOMINATOR` After a block consumes more gas than the target gas, the base fee cannot be raised more than the maximum amount allowed. The max base fee change (per-block) is derived as follows: `prevBaseFee / BASE_FEE_MAX_CHANGE_DENOMINATOR`
## The base fee can never be lowered more than the max base fee change. ## The base fee can never be lowered more than the max base fee change.
**Test:** [`FuzzResourceMetering.sol#L192`](../contracts/echidna/FuzzResourceMetering.sol#L192) **Test:** [`FuzzResourceMetering.sol#L211`](../contracts/echidna/FuzzResourceMetering.sol#L211)
After a block consumes less than the target gas, the base fee cannot be lowered more than the maximum amount allowed. The max base fee change (per-block) is derived as follows: `prevBaseFee / BASE_FEE_MAX_CHANGE_DENOMINATOR` After a block consumes less than the target gas, the base fee cannot be lowered more than the maximum amount allowed. The max base fee change (per-block) is derived as follows: `prevBaseFee / BASE_FEE_MAX_CHANGE_DENOMINATOR`
## The `maxBaseFeeChange` calculation over multiple blocks can never underflow. ## The `maxBaseFeeChange` calculation over multiple blocks can never underflow.
**Test:** [`FuzzResourceMetering.sol#L203`](../contracts/echidna/FuzzResourceMetering.sol#L203) **Test:** [`FuzzResourceMetering.sol#L222`](../contracts/echidna/FuzzResourceMetering.sol#L222)
When calculating the `maxBaseFeeChange` after multiple empty blocks, the calculation should never be allowed to underflow. When calculating the `maxBaseFeeChange` after multiple empty blocks, the calculation should never be allowed to underflow.
# `SystemConfig` Invariants # `SystemConfig` Invariants
## The gas limit of the `SystemConfig` contract can never be lower than the hard-coded lower bound. ## The gas limit of the `SystemConfig` contract can never be lower than the hard-coded lower bound.
**Test:** [`SystemConfig.t.sol#L39`](../contracts/test/invariants/SystemConfig.t.sol#L39) **Test:** [`SystemConfig.t.sol#L44`](../contracts/test/invariants/SystemConfig.t.sol#L44)
...@@ -77,7 +77,7 @@ ...@@ -77,7 +77,7 @@
"dotenv": "^16.0.0", "dotenv": "^16.0.0",
"ds-test": "https://github.com/dapphub/ds-test.git#9310e879db8ba3ea6d5c6489a579118fd264a3f5", "ds-test": "https://github.com/dapphub/ds-test.git#9310e879db8ba3ea6d5c6489a579118fd264a3f5",
"ethereum-waffle": "^3.0.0", "ethereum-waffle": "^3.0.0",
"forge-std": "https://github.com/foundry-rs/forge-std.git#fd86115ed6aba8e234ee0fb86c12fe35eff0b2a0", "forge-std": "https://github.com/foundry-rs/forge-std.git#46264e9788017fc74f9f58b7efa0bc6e1df6d410",
"glob": "^7.1.6", "glob": "^7.1.6",
"hardhat-deploy": "^0.11.4", "hardhat-deploy": "^0.11.4",
"solhint": "^3.3.7", "solhint": "^3.3.7",
......
...@@ -5,8 +5,10 @@ ...@@ -5,8 +5,10 @@
**Table of Contents** **Table of Contents**
- [Gas Stipend](#gas-stipend) - [Gas Stipend](#gas-stipend)
- [Default Values](#default-values)
- [Limiting Guaranteed Gas](#limiting-guaranteed-gas) - [Limiting Guaranteed Gas](#limiting-guaranteed-gas)
- [Rationale for burning L1 Gas](#rationale-for-burning-l1-gas) - [Rationale for burning L1 Gas](#rationale-for-burning-l1-gas)
- [On Preventing Griefing Attacks](#on-preventing-griefing-attacks)
<!-- END doctoc generated TOC please keep comment here to allow auto update --> <!-- END doctoc generated TOC please keep comment here to allow auto update -->
...@@ -36,6 +38,17 @@ the L2 gas, where `gas spent` is the amount of L1 gas spent processing the depos ...@@ -36,6 +38,17 @@ the L2 gas, where `gas spent` is the amount of L1 gas spent processing the depos
of this credit is greater than the ETH value of the requested guaranteed gas of this credit is greater than the ETH value of the requested guaranteed gas
(`requested guaranteed gas * L2 gas price`), no L1 gas is burnt. (`requested guaranteed gas * L2 gas price`), no L1 gas is burnt.
## Default Values
| Variable | Value |
| ------------------------------- | ----------------- |
| Max Resource Limit | 20,000,000 |
| Elasticity Multiplier | 10 |
| Base Fee Max Change Denominator | 8 |
| Minimum Base Fee | 1 gwei |
| Maximum Base Fee | type(uint128).max |
| System Tx Max Gas | 1,000,000 |
## Limiting Guaranteed Gas ## Limiting Guaranteed Gas
The total amount of guaranteed gas that can be bought in a single L1 block must be limited to The total amount of guaranteed gas that can be bought in a single L1 block must be limited to
...@@ -123,3 +136,20 @@ The payable version (Option 2) will likely have discount applied to it (or conve ...@@ -123,3 +136,20 @@ The payable version (Option 2) will likely have discount applied to it (or conve
premium applied to it). premium applied to it).
For the initial release of bedrock, only #1 is supported. For the initial release of bedrock, only #1 is supported.
## On Preventing Griefing Attacks
The cost of purchasing all of the deposit gas in every block must be expensive
enough to prevent attackers from griefing all deposits to the network.
An attacker would observe a deposit in the mempool and frontrun it with a deposit
that purchases enough gas such that the other deposit reverts.
The smaller the max resource limit is, the easier this attack is to pull off.
This attack is mitigated by having a large resource limit as well as a large
elastcity multiplier. This means that the target resource usage is kept small,
giving a lot of room for the deposit base fee to rise when the max resource limit
is being purchased.
This attack should be too expensive to pull off in practice, but if an extremely
wealthy adversary does decide to grief network deposits for an extended period
of time, efforts will be placed to ensure that deposits are able to be processed
on the network.
...@@ -23,7 +23,7 @@ make devnet-down # stops the devnet ...@@ -23,7 +23,7 @@ make devnet-down # stops the devnet
make devnet-clean # removes the devnet by deleting images and persistent volumes make devnet-clean # removes the devnet by deleting images and persistent volumes
``` ```
L1 is accessible at `http://localhost:8545`, and L2 is accessible at `http://localhost:8546`. L1 is accessible at `http://localhost:8545`, and L2 is accessible at `http://localhost:9545`.
Any Ethereum tool - Metamask, `seth`, etc. - can use these endpoints. Any Ethereum tool - Metamask, `seth`, etc. - can use these endpoints.
Note that you will need to specify the L2 chain ID manually if you use Metamask. The devnet's L2 chain ID is 901. Note that you will need to specify the L2 chain ID manually if you use Metamask. The devnet's L2 chain ID is 901.
...@@ -43,7 +43,7 @@ You'll need a `.env` with the following contents: ...@@ -43,7 +43,7 @@ You'll need a `.env` with the following contents:
```bash ```bash
L1_PROVIDER_URL=http://localhost:8545 L1_PROVIDER_URL=http://localhost:8545
L2_PROVIDER_URL=http://localhost:8546 L2_PROVIDER_URL=http://localhost:9545
PRIVATE_KEY=bf7604d9d3a1c7748642b1b7b05c2bd219c9faa91458b370f85e5a40f3b03af7 PRIVATE_KEY=bf7604d9d3a1c7748642b1b7b05c2bd219c9faa91458b370f85e5a40f3b03af7
``` ```
......
...@@ -11471,14 +11471,14 @@ forever-agent@~0.6.1: ...@@ -11471,14 +11471,14 @@ forever-agent@~0.6.1:
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
"forge-std@https://github.com/foundry-rs/forge-std.git#46264e9788017fc74f9f58b7efa0bc6e1df6d410":
version "1.5.2"
resolved "https://github.com/foundry-rs/forge-std.git#46264e9788017fc74f9f58b7efa0bc6e1df6d410"
"forge-std@https://github.com/foundry-rs/forge-std.git#53331f4cb2e313466f72440f3e73af048c454d02": "forge-std@https://github.com/foundry-rs/forge-std.git#53331f4cb2e313466f72440f3e73af048c454d02":
version "1.2.0" version "1.2.0"
resolved "https://github.com/foundry-rs/forge-std.git#53331f4cb2e313466f72440f3e73af048c454d02" resolved "https://github.com/foundry-rs/forge-std.git#53331f4cb2e313466f72440f3e73af048c454d02"
"forge-std@https://github.com/foundry-rs/forge-std.git#fd86115ed6aba8e234ee0fb86c12fe35eff0b2a0":
version "1.4.0"
resolved "https://github.com/foundry-rs/forge-std.git#fd86115ed6aba8e234ee0fb86c12fe35eff0b2a0"
form-data@^2.2.0: form-data@^2.2.0:
version "2.5.1" version "2.5.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4"
......
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