Commit 848f0bcc authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into clabby/op-chain-ops/eof-crawler

parents cc7866f2 e92bc23c
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -13,7 +13,7 @@ import ( ...@@ -13,7 +13,7 @@ import (
hdwallet "github.com/ethereum-optimism/go-ethereum-hdwallet" hdwallet "github.com/ethereum-optimism/go-ethereum-hdwallet"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-proposer/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"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"
......
...@@ -6,7 +6,7 @@ import ( ...@@ -6,7 +6,7 @@ import (
"math/big" "math/big"
"time" "time"
"github.com/ethereum-optimism/optimism/op-proposer/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"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"
......
...@@ -13,7 +13,7 @@ import ( ...@@ -13,7 +13,7 @@ import (
) )
var ( var (
Version = "v0.10.10" Version = "v0.10.11"
GitCommit = "" GitCommit = ""
GitDate = "" GitDate = ""
) )
......
...@@ -4,9 +4,8 @@ go 1.18 ...@@ -4,9 +4,8 @@ go 1.18
require ( require (
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3
github.com/ethereum-optimism/optimism/op-node v0.10.10 github.com/ethereum-optimism/optimism/op-node v0.10.11
github.com/ethereum-optimism/optimism/op-proposer v0.10.10 github.com/ethereum-optimism/optimism/op-service v0.10.11
github.com/ethereum-optimism/optimism/op-service v0.10.10
github.com/ethereum-optimism/optimism/op-signer v0.1.0 github.com/ethereum-optimism/optimism/op-signer v0.1.0
github.com/ethereum/go-ethereum v1.10.26 github.com/ethereum/go-ethereum v1.10.26
github.com/urfave/cli v1.22.9 github.com/urfave/cli v1.22.9
...@@ -25,7 +24,7 @@ require ( ...@@ -25,7 +24,7 @@ require (
github.com/deckarep/golang-set v1.8.0 // indirect github.com/deckarep/golang-set v1.8.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/dyson/certman v0.3.0 // indirect github.com/dyson/certman v0.3.0 // indirect
github.com/ethereum-optimism/optimism/op-bindings v0.10.10 // indirect github.com/ethereum-optimism/optimism/op-bindings v0.10.11 // indirect
github.com/fjl/memsize v0.0.1 // indirect github.com/fjl/memsize v0.0.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
...@@ -93,6 +92,7 @@ require ( ...@@ -93,6 +92,7 @@ require (
golang.org/x/sync v0.1.0 // indirect golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 // indirect golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 // indirect
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // 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
lukechampine.com/blake3 v1.1.7 // indirect lukechampine.com/blake3 v1.1.7 // indirect
......
...@@ -109,14 +109,12 @@ github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z ...@@ -109,14 +109,12 @@ github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z
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 v0.0.0-20221216190603-60b51d600468 h1:7KgjBYDji5AKi42eRYI+n8Gs+ZJVilSASL3WBu82c3M= github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468 h1:7KgjBYDji5AKi42eRYI+n8Gs+ZJVilSASL3WBu82c3M=
github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468/go.mod h1:p0Yox74PhYlq1HvijrCBCD9A3cI7rXco7hT6KrQr+rY= github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468/go.mod h1:p0Yox74PhYlq1HvijrCBCD9A3cI7rXco7hT6KrQr+rY=
github.com/ethereum-optimism/optimism/op-bindings v0.10.10 h1:Khi7m2IyNdB2JeMdW3Y1YibePTiAzuLMHe8hBE2WQzs= github.com/ethereum-optimism/optimism/op-bindings v0.10.11 h1:RDiRyHo0G/UuxHZQdMJyqIuHtWvpionuFNfczNaWCcM=
github.com/ethereum-optimism/optimism/op-bindings v0.10.10/go.mod h1:9ZSUq/rjlzp3uYyBN4sZmhTc3oZgDVqJ4wrUja7vj6c= github.com/ethereum-optimism/optimism/op-bindings v0.10.11/go.mod h1:9ZSUq/rjlzp3uYyBN4sZmhTc3oZgDVqJ4wrUja7vj6c=
github.com/ethereum-optimism/optimism/op-node v0.10.10 h1:edmxboiYSIk9n4A3paEF7I0m5qi+v+6FBFkqAfpFU7Y= github.com/ethereum-optimism/optimism/op-node v0.10.11 h1:ED72b68ainzcXr5/cLOYRwv+LdE4hRDnkq3SmNRY1+Q=
github.com/ethereum-optimism/optimism/op-node v0.10.10/go.mod h1:EEcHgMdKiWasJGO5uzspRN2xM1/OB+ehgVgMj4RgPas= github.com/ethereum-optimism/optimism/op-node v0.10.11/go.mod h1:/CDpkMxc3mDklZ1nqz2lmxfeUyAUz7yC/OLmX8egAUw=
github.com/ethereum-optimism/optimism/op-proposer v0.10.10 h1:VOpHt1T/CnaYhjbhj/rG+yU+/Tay3fkHSrU/2s8e2po= github.com/ethereum-optimism/optimism/op-service v0.10.11 h1:o+SazhFXlE3EM9Re5KIPEQklZ9uTI8rNkjl0h5OwRtU=
github.com/ethereum-optimism/optimism/op-proposer v0.10.10/go.mod h1:oRPWIlr9DsVT4iNHmXs1AVhUYlU6I9GZ50YqnKcADWc= github.com/ethereum-optimism/optimism/op-service v0.10.11/go.mod h1:wbtHqi1fv00B3agj7a2zdP3OFanEfGZ23zPgGgFCF/c=
github.com/ethereum-optimism/optimism/op-service v0.10.10 h1:B5mGpATX6zPkDABoh6smCjh6Z5mA2KWh71MD1i6T5ww=
github.com/ethereum-optimism/optimism/op-service v0.10.10/go.mod h1:wbtHqi1fv00B3agj7a2zdP3OFanEfGZ23zPgGgFCF/c=
github.com/ethereum-optimism/optimism/op-signer v0.1.0 h1:wH44Deai43YQWO0pEd44pDm3BahdAtSmrOHKiPvTB8Y= github.com/ethereum-optimism/optimism/op-signer v0.1.0 h1:wH44Deai43YQWO0pEd44pDm3BahdAtSmrOHKiPvTB8Y=
github.com/ethereum-optimism/optimism/op-signer v0.1.0/go.mod h1:u8sN6X/c20pP9F1Ey7jH3fi19D08Y+T9ep3PGJfdyi8= github.com/ethereum-optimism/optimism/op-signer v0.1.0/go.mod h1:u8sN6X/c20pP9F1Ey7jH3fi19D08Y+T9ep3PGJfdyi8=
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=
...@@ -604,6 +602,7 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb ...@@ -604,6 +602,7 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ= golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ=
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
......
...@@ -3,7 +3,7 @@ module github.com/ethereum-optimism/optimism/op-chain-ops ...@@ -3,7 +3,7 @@ module github.com/ethereum-optimism/optimism/op-chain-ops
go 1.18 go 1.18
require ( require (
github.com/ethereum-optimism/optimism/op-bindings v0.10.10 github.com/ethereum-optimism/optimism/op-bindings v0.10.11
github.com/ethereum-optimism/optimism/op-node v0.10.1 github.com/ethereum-optimism/optimism/op-node v0.10.1
github.com/ethereum/go-ethereum v1.10.26 github.com/ethereum/go-ethereum v1.10.26
github.com/holiman/uint256 v1.2.0 github.com/holiman/uint256 v1.2.0
...@@ -23,7 +23,7 @@ require ( ...@@ -23,7 +23,7 @@ require (
github.com/deckarep/golang-set v1.8.0 // indirect github.com/deckarep/golang-set v1.8.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/edsrzf/mmap-go v1.1.0 // indirect github.com/edsrzf/mmap-go v1.1.0 // indirect
github.com/ethereum-optimism/optimism/op-service v0.10.10 // indirect github.com/ethereum-optimism/optimism/op-service v0.10.11 // indirect
github.com/go-kit/kit v0.10.0 // indirect github.com/go-kit/kit v0.10.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-stack/stack v1.8.1 // indirect github.com/go-stack/stack v1.8.1 // indirect
......
...@@ -79,12 +79,12 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m ...@@ -79,12 +79,12 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468 h1:7KgjBYDji5AKi42eRYI+n8Gs+ZJVilSASL3WBu82c3M= github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468 h1:7KgjBYDji5AKi42eRYI+n8Gs+ZJVilSASL3WBu82c3M=
github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468/go.mod h1:p0Yox74PhYlq1HvijrCBCD9A3cI7rXco7hT6KrQr+rY= github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468/go.mod h1:p0Yox74PhYlq1HvijrCBCD9A3cI7rXco7hT6KrQr+rY=
github.com/ethereum-optimism/optimism/op-bindings v0.10.10 h1:Khi7m2IyNdB2JeMdW3Y1YibePTiAzuLMHe8hBE2WQzs= github.com/ethereum-optimism/optimism/op-bindings v0.10.11 h1:RDiRyHo0G/UuxHZQdMJyqIuHtWvpionuFNfczNaWCcM=
github.com/ethereum-optimism/optimism/op-bindings v0.10.10/go.mod h1:9ZSUq/rjlzp3uYyBN4sZmhTc3oZgDVqJ4wrUja7vj6c= github.com/ethereum-optimism/optimism/op-bindings v0.10.11/go.mod h1:9ZSUq/rjlzp3uYyBN4sZmhTc3oZgDVqJ4wrUja7vj6c=
github.com/ethereum-optimism/optimism/op-node v0.10.1 h1:kVBaOEOYLV22XEHRhB7dfdmoXepO0kx/RsZQK+Bpk1Y= github.com/ethereum-optimism/optimism/op-node v0.10.1 h1:kVBaOEOYLV22XEHRhB7dfdmoXepO0kx/RsZQK+Bpk1Y=
github.com/ethereum-optimism/optimism/op-node v0.10.1/go.mod h1:pup7wiiUs9g8cZKwXeB5tEGCqwUUwFVmej9MmSIm6S8= github.com/ethereum-optimism/optimism/op-node v0.10.1/go.mod h1:pup7wiiUs9g8cZKwXeB5tEGCqwUUwFVmej9MmSIm6S8=
github.com/ethereum-optimism/optimism/op-service v0.10.10 h1:B5mGpATX6zPkDABoh6smCjh6Z5mA2KWh71MD1i6T5ww= github.com/ethereum-optimism/optimism/op-service v0.10.11 h1:o+SazhFXlE3EM9Re5KIPEQklZ9uTI8rNkjl0h5OwRtU=
github.com/ethereum-optimism/optimism/op-service v0.10.10/go.mod h1:wbtHqi1fv00B3agj7a2zdP3OFanEfGZ23zPgGgFCF/c= github.com/ethereum-optimism/optimism/op-service v0.10.11/go.mod h1:wbtHqi1fv00B3agj7a2zdP3OFanEfGZ23zPgGgFCF/c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
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=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
......
package actions
import (
"bytes"
"compress/gzip"
"compress/zlib"
"crypto/rand"
"errors"
"fmt"
"io"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
)
type GarbageKind int64
const (
STRIP_VERSION GarbageKind = iota
RANDOM
TRUNCATE_END
DIRTY_APPEND
INVALID_COMPRESSION
MALFORM_RLP
)
var GarbageKinds = []GarbageKind{
STRIP_VERSION,
RANDOM,
TRUNCATE_END,
DIRTY_APPEND,
INVALID_COMPRESSION,
MALFORM_RLP,
}
// GarbageChannelCfg is the configuration for a `GarbageChannelOut`
type GarbageChannelCfg struct {
useInvalidCompression bool
malformRLP bool
}
// Writer is the interface shared between `zlib.Writer` and `gzip.Writer`
type Writer interface {
Close() error
Flush() error
Reset(io.Writer)
Write([]byte) (int, error)
}
// ChannelOutIface is the interface implemented by ChannelOut & GarbageChannelOut
type ChannelOutIface interface {
ID() derive.ChannelID
Reset() error
AddBlock(block *types.Block) error
ReadyBytes() int
Flush() error
Close() error
OutputFrame(w *bytes.Buffer, maxSize uint64) error
}
// Compile-time check for ChannelOutIface interface implementation for the ChannelOut type.
var _ ChannelOutIface = (*derive.ChannelOut)(nil)
// Compile-time check for ChannelOutIface interface implementation for the GarbageChannelOut type.
var _ ChannelOutIface = (*GarbageChannelOut)(nil)
// GarbageChannelOut is a modified `derive.ChannelOut` that can be configured to behave differently
// than the original
type GarbageChannelOut struct {
id derive.ChannelID
// Frame ID of the next frame to emit. Increment after emitting
frame uint64
// rlpLength is the uncompressed size of the channel. Must be less than MAX_RLP_BYTES_PER_CHANNEL
rlpLength int
// Compressor stage. Write input data to it
compress Writer
// post compression buffer
buf bytes.Buffer
closed bool
// Garbage channel configuration
cfg *GarbageChannelCfg
}
func (co *GarbageChannelOut) ID() derive.ChannelID {
return co.id
}
// NewGarbageChannelOut creates a new `GarbageChannelOut` with the given configuration.
func NewGarbageChannelOut(cfg *GarbageChannelCfg) (*GarbageChannelOut, error) {
c := &GarbageChannelOut{
id: derive.ChannelID{}, // TODO: use GUID here instead of fully random data
frame: 0,
rlpLength: 0,
cfg: cfg,
}
_, err := rand.Read(c.id[:])
if err != nil {
return nil, err
}
// Optionally use zlib or gzip compression
var compress Writer
if cfg.useInvalidCompression {
compress, err = gzip.NewWriterLevel(&c.buf, gzip.BestCompression)
} else {
compress, err = zlib.NewWriterLevel(&c.buf, zlib.BestCompression)
}
if err != nil {
return nil, err
}
c.compress = compress
return c, nil
}
// TODO: reuse ChannelOut for performance
func (co *GarbageChannelOut) Reset() error {
co.frame = 0
co.rlpLength = 0
co.buf.Reset()
co.compress.Reset(&co.buf)
co.closed = false
_, err := rand.Read(co.id[:])
return err
}
// AddBlock adds a block to the channel. It returns an error
// if there is a problem adding the block. The only sentinel
// error that it returns is ErrTooManyRLPBytes. If this error
// is returned, the channel should be closed and a new one
// should be made.
func (co *GarbageChannelOut) AddBlock(block *types.Block) error {
if co.closed {
return errors.New("already closed")
}
batch, err := blockToBatch(block)
if err != nil {
return err
}
// We encode to a temporary buffer to determine the encoded length to
// ensure that the total size of all RLP elements is less than or equal to MAX_RLP_BYTES_PER_CHANNEL
var buf bytes.Buffer
if err := rlp.Encode(&buf, batch); err != nil {
return err
}
if co.cfg.malformRLP {
// Malform the RLP by incrementing the length prefix by 1.
bufBytes := buf.Bytes()
bufBytes[0] += 1
buf.Reset()
buf.Write(bufBytes)
}
if co.rlpLength+buf.Len() > derive.MaxRLPBytesPerChannel {
return fmt.Errorf("could not add %d bytes to channel of %d bytes, max is %d. err: %w",
buf.Len(), co.rlpLength, derive.MaxRLPBytesPerChannel, derive.ErrTooManyRLPBytes)
}
co.rlpLength += buf.Len()
_, err = io.Copy(co.compress, &buf)
return err
}
// ReadyBytes returns the number of bytes that the channel out can immediately output into a frame.
// Use `Flush` or `Close` to move data from the compression buffer into the ready buffer if more bytes
// are needed. Add blocks may add to the ready buffer, but it is not guaranteed due to the compression stage.
func (co *GarbageChannelOut) ReadyBytes() int {
return co.buf.Len()
}
// Flush flushes the internal compression stage to the ready buffer. It enables pulling a larger & more
// complete frame. It reduces the compression efficiency.
func (co *GarbageChannelOut) Flush() error {
return co.compress.Flush()
}
func (co *GarbageChannelOut) Close() error {
if co.closed {
return errors.New("already closed")
}
co.closed = true
return co.compress.Close()
}
// OutputFrame writes a frame to w with a given max size
// Use `ReadyBytes`, `Flush`, and `Close` to modify the ready buffer.
// Returns io.EOF when the channel is closed & there are no more frames
// Returns nil if there is still more buffered data.
// Returns and error if it ran into an error during processing.
func (co *GarbageChannelOut) OutputFrame(w *bytes.Buffer, maxSize uint64) error {
f := derive.Frame{
ID: co.id,
FrameNumber: uint16(co.frame),
}
// Copy data from the local buffer into the frame data buffer
// Don't go past the maxSize with the fixed frame overhead.
// Fixed overhead: 32 + 8 + 2 + 4 + 1 = 47 bytes.
// Add one extra byte for the version byte (for the entire L1 tx though)
maxDataSize := maxSize - 47 - 1
if maxDataSize > uint64(co.buf.Len()) {
maxDataSize = uint64(co.buf.Len())
// If we are closed & will not spill past the current frame
// mark it is the final frame of the channel.
if co.closed {
f.IsLast = true
}
}
f.Data = make([]byte, maxDataSize)
if _, err := io.ReadFull(&co.buf, f.Data); err != nil {
return err
}
if err := f.MarshalBinary(w); err != nil {
return err
}
co.frame += 1
if f.IsLast {
return io.EOF
} else {
return nil
}
}
// blockToBatch transforms a block into a batch object that can easily be RLP encoded.
func blockToBatch(block *types.Block) (*derive.BatchData, error) {
opaqueTxs := make([]hexutil.Bytes, 0, len(block.Transactions()))
for i, tx := range block.Transactions() {
if tx.Type() == types.DepositTxType {
continue
}
otx, err := tx.MarshalBinary()
if err != nil {
return nil, fmt.Errorf("could not encode tx %v in block %v: %w", i, tx.Hash(), err)
}
opaqueTxs = append(opaqueTxs, otx)
}
l1InfoTx := block.Transactions()[0]
if l1InfoTx.Type() != types.DepositTxType {
return nil, derive.ErrNotDepositTx
}
l1Info, err := derive.L1InfoDepositTxData(l1InfoTx.Data())
if err != nil {
return nil, fmt.Errorf("could not parse the L1 Info deposit: %w", err)
}
return &derive.BatchData{
BatchV1: derive.BatchV1{
ParentHash: block.ParentHash(),
EpochNum: rollup.Epoch(l1Info.Number),
EpochHash: l1Info.BlockHash,
Timestamp: block.Time(),
Transactions: opaqueTxs,
},
}, nil
}
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"context" "context"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/rand"
"io" "io"
"math/big" "math/big"
...@@ -40,6 +41,8 @@ type BatcherCfg struct { ...@@ -40,6 +41,8 @@ type BatcherCfg struct {
MaxL1TxSize uint64 MaxL1TxSize uint64
BatcherKey *ecdsa.PrivateKey BatcherKey *ecdsa.PrivateKey
GarbageCfg *GarbageChannelCfg
} }
// L2Batcher buffers and submits L2 batches to L1. // L2Batcher buffers and submits L2 batches to L1.
...@@ -58,7 +61,7 @@ type L2Batcher struct { ...@@ -58,7 +61,7 @@ type L2Batcher struct {
l1Signer types.Signer l1Signer types.Signer
l2ChannelOut *derive.ChannelOut l2ChannelOut ChannelOutIface
l2Submitting bool // when the channel out is being submitted, and not safe to write to without resetting l2Submitting bool // when the channel out is being submitted, and not safe to write to without resetting
l2BufferedBlock eth.BlockID l2BufferedBlock eth.BlockID
l2SubmittedBlock eth.BlockID l2SubmittedBlock eth.BlockID
...@@ -122,7 +125,12 @@ func (s *L2Batcher) ActL2BatchBuffer(t Testing) { ...@@ -122,7 +125,12 @@ func (s *L2Batcher) ActL2BatchBuffer(t Testing) {
} }
// Create channel if we don't have one yet // Create channel if we don't have one yet
if s.l2ChannelOut == nil { if s.l2ChannelOut == nil {
ch, err := derive.NewChannelOut() var ch ChannelOutIface
if s.l2BatcherCfg.GarbageCfg != nil {
ch, err = NewGarbageChannelOut(s.l2BatcherCfg.GarbageCfg)
} else {
ch, err = derive.NewChannelOut()
}
require.NoError(t, err, "failed to create channel") require.NoError(t, err, "failed to create channel")
s.l2ChannelOut = ch s.l2ChannelOut = ch
} }
...@@ -195,6 +203,89 @@ func (s *L2Batcher) ActL2BatchSubmit(t Testing) { ...@@ -195,6 +203,89 @@ func (s *L2Batcher) ActL2BatchSubmit(t Testing) {
require.NoError(t, err, "need to send tx") require.NoError(t, err, "need to send tx")
} }
// ActL2BatchSubmitGarbage constructs a malformed channel frame and submits it to the
// batch inbox. This *should* cause the batch inbox to reject the blocks
// encoded within the frame, even if the blocks themselves are valid.
func (s *L2Batcher) ActL2BatchSubmitGarbage(t Testing, kind GarbageKind) {
// Don't run this action if there's no data to submit
if s.l2ChannelOut == nil {
t.InvalidAction("need to buffer data first, cannot batch submit with empty buffer")
return
}
// Collect the output frame
data := new(bytes.Buffer)
data.WriteByte(derive.DerivationVersion0)
// subtract one, to account for the version byte
if err := s.l2ChannelOut.OutputFrame(data, s.l2BatcherCfg.MaxL1TxSize-1); err == io.EOF {
s.l2ChannelOut = nil
s.l2Submitting = false
} else if err != nil {
s.l2Submitting = false
t.Fatalf("failed to output channel data to frame: %v", err)
}
outputFrame := data.Bytes()
// Malform the output frame
switch kind {
// Strip the derivation version byte from the output frame
case STRIP_VERSION:
outputFrame = outputFrame[1:]
// Replace the output frame with random bytes of length [1, 512]
case RANDOM:
i, err := rand.Int(rand.Reader, big.NewInt(512))
require.NoError(t, err, "error generating random bytes length")
buf := make([]byte, i.Int64()+1)
_, err = rand.Read(buf)
require.NoError(t, err, "error generating random bytes")
outputFrame = buf
// Remove 4 bytes from the tail end of the output frame
case TRUNCATE_END:
outputFrame = outputFrame[:len(outputFrame)-4]
// Append 4 garbage bytes to the end of the output frame
case DIRTY_APPEND:
outputFrame = append(outputFrame, []byte{0xBA, 0xD0, 0xC0, 0xDE}...)
case INVALID_COMPRESSION:
// Do nothing post frame encoding- the `GarbageChannelOut` used for this case is modified to
// use gzip compression rather than zlib, which is invalid.
break
case MALFORM_RLP:
// Do nothing post frame encoding- the `GarbageChannelOut` used for this case is modified to
// write malformed RLP each time a block is added to the channel.
break
default:
t.Fatalf("Unexpected garbage kind: %v", kind)
}
nonce, err := s.l1.PendingNonceAt(t.Ctx(), s.batcherAddr)
require.NoError(t, err, "need batcher nonce")
gasTipCap := big.NewInt(2 * params.GWei)
pendingHeader, err := s.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)))
rawTx := &types.DynamicFeeTx{
ChainID: s.rollupCfg.L1ChainID,
Nonce: nonce,
To: &s.rollupCfg.BatchInboxAddress,
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Data: outputFrame,
}
gas, err := core.IntrinsicGas(rawTx.Data, nil, false, true, true)
require.NoError(t, err, "need to compute intrinsic gas")
rawTx.Gas = gas
tx, err := types.SignNewTx(s.l2BatcherCfg.BatcherKey, s.l1Signer, rawTx)
require.NoError(t, err, "need to sign tx")
err = s.l1.SendTransaction(t.Ctx(), tx)
require.NoError(t, err, "need to send tx")
}
func (s *L2Batcher) ActBufferAll(t Testing) { func (s *L2Batcher) ActBufferAll(t Testing) {
stat, err := s.syncStatusAPI.SyncStatus(t.Ctx()) stat, err := s.syncStatusAPI.SyncStatus(t.Ctx())
require.NoError(t, err) require.NoError(t, err)
......
...@@ -196,6 +196,88 @@ func TestL2Finalization(gt *testing.T) { ...@@ -196,6 +196,88 @@ func TestL2Finalization(gt *testing.T) {
require.Equal(t, heightToSubmit, sequencer.SyncStatus().FinalizedL2.Number, "unknown/bad finalized L1 blocks are ignored") require.Equal(t, heightToSubmit, sequencer.SyncStatus().FinalizedL2.Number, "unknown/bad finalized L1 blocks are ignored")
} }
// TestGarbageBatch tests the behavior of an invalid/malformed output channel frame containing
// valid batches being submitted to the batch inbox. These batches should always be rejected
// and the safe L2 head should remain unaltered.
func TestGarbageBatch(gt *testing.T) {
t := NewDefaultTesting(gt)
p := defaultRollupTestParams
dp := e2eutils.MakeDeployParams(t, p)
for _, garbageKind := range GarbageKinds {
sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlError)
miner, engine, sequencer := setupSequencerTest(t, sd, log)
_, verifier := setupVerifier(t, sd, log, miner.L1Client(t, sd.RollupCfg))
batcherCfg := &BatcherCfg{
MinL1TxSize: 0,
MaxL1TxSize: 128_000,
BatcherKey: dp.Secrets.Batcher,
}
if garbageKind == MALFORM_RLP || garbageKind == INVALID_COMPRESSION {
// If the garbage kind is `INVALID_COMPRESSION` or `MALFORM_RLP`, use the `actions` packages
// modified `ChannelOut`.
batcherCfg.GarbageCfg = &GarbageChannelCfg{
useInvalidCompression: garbageKind == INVALID_COMPRESSION,
malformRLP: garbageKind == MALFORM_RLP,
}
}
batcher := NewL2Batcher(log, sd.RollupCfg, batcherCfg, sequencer.RollupClient(), miner.EthClient(), engine.EthClient())
sequencer.ActL2PipelineFull(t)
verifier.ActL2PipelineFull(t)
syncAndBuildL2 := func() {
// Send a head signal to the sequencer and verifier
sequencer.ActL1HeadSignal(t)
verifier.ActL1HeadSignal(t)
// Run the derivation pipeline on the sequencer and verifier
sequencer.ActL2PipelineFull(t)
verifier.ActL2PipelineFull(t)
// Build the L2 chain to the L1 head
sequencer.ActBuildToL1Head(t)
}
// Build an empty block on L1 and run the derivation pipeline + build L2
// to the L1 head (block #1)
miner.ActEmptyBlock(t)
syncAndBuildL2()
// Ensure that the L2 safe head has an L1 Origin at genesis before any
// batches are submitted.
require.Equal(t, uint64(0), sequencer.L2Safe().L1Origin.Number)
require.Equal(t, uint64(1), sequencer.L2Unsafe().L1Origin.Number)
// Submit a batch containing all blocks built on L2 while catching up
// to the L1 head above. The output channel frame submitted to the batch
// inbox will be invalid- it will be malformed depending on the passed
// `garbageKind`.
batcher.ActBufferAll(t)
batcher.ActL2ChannelClose(t)
batcher.ActL2BatchSubmitGarbage(t, garbageKind)
// Include the batch on L1 in block #2
miner.ActL1StartBlock(12)(t)
miner.ActL1IncludeTx(dp.Addresses.Batcher)(t)
miner.ActL1EndBlock(t)
// Send a head signal + run the derivation pipeline on the sequencer
// and verifier.
syncAndBuildL2()
// Verify that the L2 blocks that were batch submitted were *not* marked
// as safe due to the malformed output channel frame. The safe head should
// still have an L1 Origin at genesis.
require.Equal(t, uint64(0), sequencer.L2Safe().L1Origin.Number)
require.Equal(t, uint64(2), sequencer.L2Unsafe().L1Origin.Number)
}
}
func TestExtendedTimeWithoutL1Batches(gt *testing.T) { func TestExtendedTimeWithoutL1Batches(gt *testing.T) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
p := &e2eutils.TestParams{ p := &e2eutils.TestParams{
......
...@@ -15,8 +15,8 @@ import ( ...@@ -15,8 +15,8 @@ import (
"github.com/ethereum-optimism/optimism/op-node/sources" "github.com/ethereum-optimism/optimism/op-node/sources"
"github.com/ethereum-optimism/optimism/op-proposer/proposer" "github.com/ethereum-optimism/optimism/op-proposer/proposer"
"github.com/ethereum-optimism/optimism/op-proposer/txmgr"
opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
) )
type ProposerCfg struct { type ProposerCfg struct {
...@@ -34,7 +34,7 @@ type L2Proposer struct { ...@@ -34,7 +34,7 @@ type L2Proposer struct {
} }
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) proposer.SignerFn { signer := func(chainID *big.Int) opcrypto.SignerFn {
s := opcrypto.PrivateKeySignerFn(cfg.ProposerKey, chainID) s := opcrypto.PrivateKeySignerFn(cfg.ProposerKey, chainID)
return func(_ context.Context, addr common.Address, tx *types.Transaction) (*types.Transaction, error) { return func(_ context.Context, addr common.Address, tx *types.Transaction) (*types.Transaction, error) {
return s(addr, tx) return s(addr, tx)
...@@ -60,7 +60,7 @@ func NewL2Proposer(t Testing, log log.Logger, cfg *ProposerCfg, l1 *ethclient.Cl ...@@ -60,7 +60,7 @@ func NewL2Proposer(t Testing, log log.Logger, cfg *ProposerCfg, l1 *ethclient.Cl
SignerFnFactory: signer, SignerFnFactory: signer,
} }
dr, err := proposer.NewL2OutputSubmitterWithSigner(proposerCfg, log) dr, err := proposer.NewL2OutputSubmitter(proposerCfg, log)
require.NoError(t, err) require.NoError(t, err)
return &L2Proposer{ return &L2Proposer{
......
...@@ -10,12 +10,12 @@ require ( ...@@ -10,12 +10,12 @@ 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-optimism/optimism/op-batcher v0.10.10 github.com/ethereum-optimism/optimism/op-batcher v0.10.11
github.com/ethereum-optimism/optimism/op-bindings v0.10.10 github.com/ethereum-optimism/optimism/op-bindings v0.10.11
github.com/ethereum-optimism/optimism/op-chain-ops v0.10.10 github.com/ethereum-optimism/optimism/op-chain-ops v0.10.11
github.com/ethereum-optimism/optimism/op-node v0.10.10 github.com/ethereum-optimism/optimism/op-node v0.10.11
github.com/ethereum-optimism/optimism/op-proposer v0.10.10 github.com/ethereum-optimism/optimism/op-proposer v0.10.11
github.com/ethereum-optimism/optimism/op-service v0.10.10 github.com/ethereum-optimism/optimism/op-service v0.10.11
github.com/ethereum/go-ethereum v1.10.26 github.com/ethereum/go-ethereum v1.10.26
github.com/google/gofuzz v1.2.1-0.20220503160820-4a35382e8fc8 github.com/google/gofuzz v1.2.1-0.20220503160820-4a35382e8fc8
github.com/libp2p/go-libp2p v0.23.3 github.com/libp2p/go-libp2p v0.23.3
...@@ -44,10 +44,13 @@ require ( ...@@ -44,10 +44,13 @@ require (
github.com/deepmap/oapi-codegen v1.8.2 // indirect github.com/deepmap/oapi-codegen v1.8.2 // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/go-units v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect
github.com/dyson/certman v0.3.0 // indirect
github.com/edsrzf/mmap-go v1.1.0 // indirect github.com/edsrzf/mmap-go v1.1.0 // indirect
github.com/elastic/gosigar v0.14.2 // indirect github.com/elastic/gosigar v0.14.2 // indirect
github.com/ethereum-optimism/optimism/op-signer v0.1.0 // indirect
github.com/fjl/memsize v0.0.1 // indirect github.com/fjl/memsize v0.0.1 // indirect
github.com/flynn/noise v1.0.0 // indirect github.com/flynn/noise v1.0.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-stack/stack v1.8.1 // indirect github.com/go-stack/stack v1.8.1 // indirect
...@@ -157,7 +160,7 @@ require ( ...@@ -157,7 +160,7 @@ require (
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/net v0.0.0-20220920183852-bf014ff85ad5 // indirect golang.org/x/net v0.0.0-20220920183852-bf014ff85ad5 // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 // indirect golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 // indirect
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.3.7 // indirect
......
...@@ -145,6 +145,8 @@ github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD ...@@ -145,6 +145,8 @@ github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dyson/certman v0.3.0 h1:S7WCUim5faT/OiBhiY3u5cMaiC9MNKiA+8PJDXLaIYQ=
github.com/dyson/certman v0.3.0/go.mod h1:RMWlyA9op6D9SxOBRRX3sxnParehv9gf52WWUJPd1JA=
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ=
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
...@@ -159,18 +161,20 @@ github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z ...@@ -159,18 +161,20 @@ github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z
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 v0.0.0-20221216190603-60b51d600468 h1:7KgjBYDji5AKi42eRYI+n8Gs+ZJVilSASL3WBu82c3M= github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468 h1:7KgjBYDji5AKi42eRYI+n8Gs+ZJVilSASL3WBu82c3M=
github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468/go.mod h1:p0Yox74PhYlq1HvijrCBCD9A3cI7rXco7hT6KrQr+rY= github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468/go.mod h1:p0Yox74PhYlq1HvijrCBCD9A3cI7rXco7hT6KrQr+rY=
github.com/ethereum-optimism/optimism/op-batcher v0.10.10 h1:uujkdVCjRv7WJA9205PMwqXpCAeG0IxYu4hREPkK1IY= github.com/ethereum-optimism/optimism/op-batcher v0.10.11 h1:aqTOE3UnTrX/rngXruT815CyRhcKi9kvZ4xJllW9l6I=
github.com/ethereum-optimism/optimism/op-batcher v0.10.10/go.mod h1:dhTcmYjZyHKKfLAP9qQgXRBbrDvckBmNo3/Yu4zqaqg= github.com/ethereum-optimism/optimism/op-batcher v0.10.11/go.mod h1:HIsxM0YihXGGImsUuPdI0T+L1LuS8UXgZnaZweXUGug=
github.com/ethereum-optimism/optimism/op-bindings v0.10.10 h1:Khi7m2IyNdB2JeMdW3Y1YibePTiAzuLMHe8hBE2WQzs= github.com/ethereum-optimism/optimism/op-bindings v0.10.11 h1:RDiRyHo0G/UuxHZQdMJyqIuHtWvpionuFNfczNaWCcM=
github.com/ethereum-optimism/optimism/op-bindings v0.10.10/go.mod h1:9ZSUq/rjlzp3uYyBN4sZmhTc3oZgDVqJ4wrUja7vj6c= github.com/ethereum-optimism/optimism/op-bindings v0.10.11/go.mod h1:9ZSUq/rjlzp3uYyBN4sZmhTc3oZgDVqJ4wrUja7vj6c=
github.com/ethereum-optimism/optimism/op-chain-ops v0.10.10 h1:99OW6aQMSsBLTSplvVfvSrZy8ZH3qs5dG5T0o1Ffwd0= github.com/ethereum-optimism/optimism/op-chain-ops v0.10.11 h1:6ihrVPJYN1HvD4KG0Fk1zIJCM4ZB109kCu9fq81jznQ=
github.com/ethereum-optimism/optimism/op-chain-ops v0.10.10/go.mod h1:dWZgOwVz2nK/pFY9fVFxStjYFVqnbz954pwIysStmEY= github.com/ethereum-optimism/optimism/op-chain-ops v0.10.11/go.mod h1:6mub7Tx1cC4gDrfX9o9n+kA4R2qLlYvfWkG8es21EQI=
github.com/ethereum-optimism/optimism/op-node v0.10.10 h1:edmxboiYSIk9n4A3paEF7I0m5qi+v+6FBFkqAfpFU7Y= github.com/ethereum-optimism/optimism/op-node v0.10.11 h1:ED72b68ainzcXr5/cLOYRwv+LdE4hRDnkq3SmNRY1+Q=
github.com/ethereum-optimism/optimism/op-node v0.10.10/go.mod h1:EEcHgMdKiWasJGO5uzspRN2xM1/OB+ehgVgMj4RgPas= github.com/ethereum-optimism/optimism/op-node v0.10.11/go.mod h1:/CDpkMxc3mDklZ1nqz2lmxfeUyAUz7yC/OLmX8egAUw=
github.com/ethereum-optimism/optimism/op-proposer v0.10.10 h1:VOpHt1T/CnaYhjbhj/rG+yU+/Tay3fkHSrU/2s8e2po= github.com/ethereum-optimism/optimism/op-proposer v0.10.11 h1:uG+CXcac1LVRAnivv+3q7qJZDTgdKLv0D3mbu2o7nKI=
github.com/ethereum-optimism/optimism/op-proposer v0.10.10/go.mod h1:oRPWIlr9DsVT4iNHmXs1AVhUYlU6I9GZ50YqnKcADWc= github.com/ethereum-optimism/optimism/op-proposer v0.10.11/go.mod h1:O/BolDMRNanlblBQKwq2UGwnl7hTdKrnVlmXfijG7vw=
github.com/ethereum-optimism/optimism/op-service v0.10.10 h1:B5mGpATX6zPkDABoh6smCjh6Z5mA2KWh71MD1i6T5ww= github.com/ethereum-optimism/optimism/op-service v0.10.11 h1:o+SazhFXlE3EM9Re5KIPEQklZ9uTI8rNkjl0h5OwRtU=
github.com/ethereum-optimism/optimism/op-service v0.10.10/go.mod h1:wbtHqi1fv00B3agj7a2zdP3OFanEfGZ23zPgGgFCF/c= github.com/ethereum-optimism/optimism/op-service v0.10.11/go.mod h1:wbtHqi1fv00B3agj7a2zdP3OFanEfGZ23zPgGgFCF/c=
github.com/ethereum-optimism/optimism/op-signer v0.1.0 h1:wH44Deai43YQWO0pEd44pDm3BahdAtSmrOHKiPvTB8Y=
github.com/ethereum-optimism/optimism/op-signer v0.1.0/go.mod h1:u8sN6X/c20pP9F1Ey7jH3fi19D08Y+T9ep3PGJfdyi8=
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=
github.com/fjl/memsize v0.0.1/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fjl/memsize v0.0.1/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ= github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ=
...@@ -179,8 +183,9 @@ github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/ ...@@ -179,8 +183,9 @@ github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays=
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
...@@ -823,8 +828,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ ...@@ -823,8 +828,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
...@@ -898,6 +903,7 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc ...@@ -898,6 +903,7 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 h1:OK7RB6t2WQX54srQQYSXMW8dF5C6/8+oA/s5QBmmto4= golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 h1:OK7RB6t2WQX54srQQYSXMW8dF5C6/8+oA/s5QBmmto4=
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
......
...@@ -342,7 +342,7 @@ func TestMigration(t *testing.T) { ...@@ -342,7 +342,7 @@ func TestMigration(t *testing.T) {
batcher.Stop() batcher.Stop()
}) })
proposer, err := l2os.NewL2OutputSubmitter(l2os.CLIConfig{ proposer, err := l2os.NewL2OutputSubmitterFromCLIConfig(l2os.CLIConfig{
L1EthRpc: forkedL1URL, L1EthRpc: forkedL1URL,
RollupRpc: rollupNode.HTTPEndpoint(), RollupRpc: rollupNode.HTTPEndpoint(),
L2OOAddress: l2OS.Address.String(), L2OOAddress: l2OS.Address.String(),
......
...@@ -498,7 +498,7 @@ func (cfg SystemConfig) Start() (*System, error) { ...@@ -498,7 +498,7 @@ func (cfg SystemConfig) Start() (*System, error) {
} }
// L2Output Submitter // L2Output Submitter
sys.L2OutputSubmitter, err = l2os.NewL2OutputSubmitter(l2os.CLIConfig{ sys.L2OutputSubmitter, err = l2os.NewL2OutputSubmitterFromCLIConfig(l2os.CLIConfig{
L1EthRpc: sys.Nodes["l1"].WSEndpoint(), L1EthRpc: sys.Nodes["l1"].WSEndpoint(),
RollupRpc: sys.RollupNodes["sequencer"].HTTPEndpoint(), RollupRpc: sys.RollupNodes["sequencer"].HTTPEndpoint(),
L2OOAddress: predeploys.DevL2OutputOracleAddr.String(), L2OOAddress: predeploys.DevL2OutputOracleAddr.String(),
......
...@@ -6,9 +6,9 @@ require ( ...@@ -6,9 +6,9 @@ require (
github.com/btcsuite/btcd v0.23.3 github.com/btcsuite/btcd v0.23.3
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0
github.com/ethereum-optimism/optimism/op-bindings v0.10.10 github.com/ethereum-optimism/optimism/op-bindings v0.10.11
github.com/ethereum-optimism/optimism/op-chain-ops v0.10.10 github.com/ethereum-optimism/optimism/op-chain-ops v0.10.11
github.com/ethereum-optimism/optimism/op-service v0.10.10 github.com/ethereum-optimism/optimism/op-service v0.10.11
github.com/ethereum/go-ethereum v1.10.26 github.com/ethereum/go-ethereum v1.10.26
github.com/golang/snappy v0.0.4 github.com/golang/snappy v0.0.4
github.com/google/go-cmp v0.5.8 github.com/google/go-cmp v0.5.8
......
...@@ -145,12 +145,12 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m ...@@ -145,12 +145,12 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468 h1:7KgjBYDji5AKi42eRYI+n8Gs+ZJVilSASL3WBu82c3M= github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468 h1:7KgjBYDji5AKi42eRYI+n8Gs+ZJVilSASL3WBu82c3M=
github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468/go.mod h1:p0Yox74PhYlq1HvijrCBCD9A3cI7rXco7hT6KrQr+rY= github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468/go.mod h1:p0Yox74PhYlq1HvijrCBCD9A3cI7rXco7hT6KrQr+rY=
github.com/ethereum-optimism/optimism/op-bindings v0.10.10 h1:Khi7m2IyNdB2JeMdW3Y1YibePTiAzuLMHe8hBE2WQzs= github.com/ethereum-optimism/optimism/op-bindings v0.10.11 h1:RDiRyHo0G/UuxHZQdMJyqIuHtWvpionuFNfczNaWCcM=
github.com/ethereum-optimism/optimism/op-bindings v0.10.10/go.mod h1:9ZSUq/rjlzp3uYyBN4sZmhTc3oZgDVqJ4wrUja7vj6c= github.com/ethereum-optimism/optimism/op-bindings v0.10.11/go.mod h1:9ZSUq/rjlzp3uYyBN4sZmhTc3oZgDVqJ4wrUja7vj6c=
github.com/ethereum-optimism/optimism/op-chain-ops v0.10.10 h1:99OW6aQMSsBLTSplvVfvSrZy8ZH3qs5dG5T0o1Ffwd0= github.com/ethereum-optimism/optimism/op-chain-ops v0.10.11 h1:6ihrVPJYN1HvD4KG0Fk1zIJCM4ZB109kCu9fq81jznQ=
github.com/ethereum-optimism/optimism/op-chain-ops v0.10.10/go.mod h1:dWZgOwVz2nK/pFY9fVFxStjYFVqnbz954pwIysStmEY= github.com/ethereum-optimism/optimism/op-chain-ops v0.10.11/go.mod h1:6mub7Tx1cC4gDrfX9o9n+kA4R2qLlYvfWkG8es21EQI=
github.com/ethereum-optimism/optimism/op-service v0.10.10 h1:B5mGpATX6zPkDABoh6smCjh6Z5mA2KWh71MD1i6T5ww= github.com/ethereum-optimism/optimism/op-service v0.10.11 h1:o+SazhFXlE3EM9Re5KIPEQklZ9uTI8rNkjl0h5OwRtU=
github.com/ethereum-optimism/optimism/op-service v0.10.10/go.mod h1:wbtHqi1fv00B3agj7a2zdP3OFanEfGZ23zPgGgFCF/c= github.com/ethereum-optimism/optimism/op-service v0.10.11/go.mod h1:wbtHqi1fv00B3agj7a2zdP3OFanEfGZ23zPgGgFCF/c=
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=
github.com/fjl/memsize v0.0.1/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fjl/memsize v0.0.1/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
......
...@@ -124,6 +124,10 @@ func (n *OpNode) initL1(ctx context.Context, cfg *Config) error { ...@@ -124,6 +124,10 @@ func (n *OpNode) initL1(ctx context.Context, cfg *Config) error {
return fmt.Errorf("failed to create L1 source: %w", err) return fmt.Errorf("failed to create L1 source: %w", err)
} }
if err := cfg.Rollup.ValidateL1Config(ctx, n.l1Source); err != nil {
return err
}
// Keep subscribed to the L1 heads, which keeps the L1 maintainer pointing to the best headers to sync // Keep subscribed to the L1 heads, which keeps the L1 maintainer pointing to the best headers to sync
n.l1HeadsSub = event.ResubscribeErr(time.Second*10, func(ctx context.Context, err error) (event.Subscription, error) { n.l1HeadsSub = event.ResubscribeErr(time.Second*10, func(ctx context.Context, err error) (event.Subscription, error) {
if err != nil { if err != nil {
...@@ -189,6 +193,10 @@ func (n *OpNode) initL2(ctx context.Context, cfg *Config, snapshotLog log.Logger ...@@ -189,6 +193,10 @@ func (n *OpNode) initL2(ctx context.Context, cfg *Config, snapshotLog log.Logger
return fmt.Errorf("failed to create Engine client: %w", err) return fmt.Errorf("failed to create Engine client: %w", err)
} }
if err := cfg.Rollup.ValidateL2Config(ctx, n.l2Source); err != nil {
return err
}
n.l2Driver = driver.NewDriver(&cfg.Driver, &cfg.Rollup, n.l2Source, n.l1Source, n, n.log, snapshotLog, n.metrics) n.l2Driver = driver.NewDriver(&cfg.Driver, &cfg.Rollup, n.l2Source, n.l1Source, n, n.log, snapshotLog, n.metrics)
return nil return nil
......
package rollup package rollup
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
...@@ -55,6 +56,94 @@ type Config struct { ...@@ -55,6 +56,94 @@ type Config struct {
L1SystemConfigAddress common.Address `json:"l1_system_config_address"` L1SystemConfigAddress common.Address `json:"l1_system_config_address"`
} }
// ValidateL1Config checks L1 config variables for errors.
func (cfg *Config) ValidateL1Config(ctx context.Context, client L1Client) error {
// Validate the L1 Client Chain ID
if err := cfg.CheckL1ChainID(ctx, client); err != nil {
return err
}
// Validate the Rollup L1 Genesis Blockhash
if err := cfg.CheckL1GenesisBlockHash(ctx, client); err != nil {
return err
}
return nil
}
// ValidateL2Config checks L2 config variables for errors.
func (cfg *Config) ValidateL2Config(ctx context.Context, client L2Client) error {
// Validate the L2 Client Chain ID
if err := cfg.CheckL2ChainID(ctx, client); err != nil {
return err
}
// Validate the Rollup L2 Genesis Blockhash
if err := cfg.CheckL2GenesisBlockHash(ctx, client); err != nil {
return err
}
return nil
}
type L1Client interface {
ChainID(context.Context) (*big.Int, error)
L1BlockRefByNumber(context.Context, uint64) (eth.L1BlockRef, error)
}
// CheckL1ChainID checks that the configured L1 chain ID matches the client's chain ID.
func (cfg *Config) CheckL1ChainID(ctx context.Context, client L1Client) error {
id, err := client.ChainID(ctx)
if err != nil {
return err
}
if cfg.L1ChainID.Cmp(id) != 0 {
return fmt.Errorf("incorrect L1 RPC chain id %d, expected %d", cfg.L1ChainID, id)
}
return nil
}
// CheckL1GenesisBlockHash checks that the configured L1 genesis block hash is valid for the given client.
func (cfg *Config) CheckL1GenesisBlockHash(ctx context.Context, client L1Client) error {
l1GenesisBlockRef, err := client.L1BlockRefByNumber(ctx, cfg.Genesis.L1.Number)
if err != nil {
return err
}
if l1GenesisBlockRef.Hash != cfg.Genesis.L1.Hash {
return fmt.Errorf("incorrect L1 genesis block hash %d, expected %d", cfg.Genesis.L1.Hash, l1GenesisBlockRef.Hash)
}
return nil
}
type L2Client interface {
ChainID(context.Context) (*big.Int, error)
L2BlockRefByNumber(context.Context, uint64) (eth.L2BlockRef, error)
}
// CheckL2ChainID checks that the configured L2 chain ID matches the client's chain ID.
func (cfg *Config) CheckL2ChainID(ctx context.Context, client L2Client) error {
id, err := client.ChainID(ctx)
if err != nil {
return err
}
if cfg.L2ChainID.Cmp(id) != 0 {
return fmt.Errorf("incorrect L2 RPC chain id %d, expected %d", cfg.L2ChainID, id)
}
return nil
}
// CheckL2GenesisBlockHash checks that the configured L2 genesis block hash is valid for the given client.
func (cfg *Config) CheckL2GenesisBlockHash(ctx context.Context, client L2Client) error {
l2GenesisBlockRef, err := client.L2BlockRefByNumber(ctx, cfg.Genesis.L2.Number)
if err != nil {
return err
}
if l2GenesisBlockRef.Hash != cfg.Genesis.L2.Hash {
return fmt.Errorf("incorrect L2 genesis block hash %d, expected %d", cfg.Genesis.L2.Hash, l2GenesisBlockRef.Hash)
}
return nil
}
// Check verifies that the given configuration makes sense // Check verifies that the given configuration makes sense
func (cfg *Config) Check() error { func (cfg *Config) Check() error {
if cfg.BlockTime == 0 { if cfg.BlockTime == 0 {
......
package rollup package rollup
import ( import (
"context"
"encoding/json" "encoding/json"
"math/big" "math/big"
"math/rand" "math/rand"
...@@ -55,3 +56,159 @@ func TestConfigJSON(t *testing.T) { ...@@ -55,3 +56,159 @@ func TestConfigJSON(t *testing.T) {
assert.NoError(t, json.Unmarshal(data, &roundTripped)) assert.NoError(t, json.Unmarshal(data, &roundTripped))
assert.Equal(t, &roundTripped, config) assert.Equal(t, &roundTripped, config)
} }
type mockL1Client struct {
chainID *big.Int
Hash common.Hash
}
func (m *mockL1Client) ChainID(context.Context) (*big.Int, error) {
return m.chainID, nil
}
func (m *mockL1Client) L1BlockRefByNumber(ctx context.Context, number uint64) (eth.L1BlockRef, error) {
return eth.L1BlockRef{
Hash: m.Hash,
Number: 100,
}, nil
}
func TestValidateL1Config(t *testing.T) {
config := randConfig()
config.L1ChainID = big.NewInt(100)
config.Genesis.L1.Number = 100
config.Genesis.L1.Hash = [32]byte{0x01}
mockClient := mockL1Client{chainID: big.NewInt(100), Hash: common.Hash{0x01}}
err := config.ValidateL1Config(context.TODO(), &mockClient)
assert.NoError(t, err)
}
func TestValidateL1ConfigInvalidChainIdFails(t *testing.T) {
config := randConfig()
config.L1ChainID = big.NewInt(101)
config.Genesis.L1.Number = 100
config.Genesis.L1.Hash = [32]byte{0x01}
mockClient := mockL1Client{chainID: big.NewInt(100), Hash: common.Hash{0x01}}
err := config.ValidateL1Config(context.TODO(), &mockClient)
assert.Error(t, err)
config.L1ChainID = big.NewInt(99)
err = config.ValidateL1Config(context.TODO(), &mockClient)
assert.Error(t, err)
}
func TestValidateL1ConfigInvalidGenesisHashFails(t *testing.T) {
config := randConfig()
config.L1ChainID = big.NewInt(100)
config.Genesis.L1.Number = 100
config.Genesis.L1.Hash = [32]byte{0x00}
mockClient := mockL1Client{chainID: big.NewInt(100), Hash: common.Hash{0x01}}
err := config.ValidateL1Config(context.TODO(), &mockClient)
assert.Error(t, err)
config.Genesis.L1.Hash = [32]byte{0x02}
err = config.ValidateL1Config(context.TODO(), &mockClient)
assert.Error(t, err)
}
func TestCheckL1ChainID(t *testing.T) {
config := randConfig()
config.L1ChainID = big.NewInt(100)
err := config.CheckL1ChainID(context.TODO(), &mockL1Client{chainID: big.NewInt(100)})
assert.NoError(t, err)
err = config.CheckL1ChainID(context.TODO(), &mockL1Client{chainID: big.NewInt(101)})
assert.Error(t, err)
err = config.CheckL1ChainID(context.TODO(), &mockL1Client{chainID: big.NewInt(99)})
assert.Error(t, err)
}
func TestCheckL1BlockRefByNumber(t *testing.T) {
config := randConfig()
config.Genesis.L1.Number = 100
config.Genesis.L1.Hash = [32]byte{0x01}
mockClient := mockL1Client{chainID: big.NewInt(100), Hash: common.Hash{0x01}}
err := config.CheckL1GenesisBlockHash(context.TODO(), &mockClient)
assert.NoError(t, err)
mockClient.Hash = common.Hash{0x02}
err = config.CheckL1GenesisBlockHash(context.TODO(), &mockClient)
assert.Error(t, err)
mockClient.Hash = common.Hash{0x00}
err = config.CheckL1GenesisBlockHash(context.TODO(), &mockClient)
assert.Error(t, err)
}
type mockL2Client struct {
chainID *big.Int
Hash common.Hash
}
func (m *mockL2Client) ChainID(context.Context) (*big.Int, error) {
return m.chainID, nil
}
func (m *mockL2Client) L2BlockRefByNumber(ctx context.Context, number uint64) (eth.L2BlockRef, error) {
return eth.L2BlockRef{
Hash: m.Hash,
Number: 100,
}, nil
}
func TestValidateL2Config(t *testing.T) {
config := randConfig()
config.L2ChainID = big.NewInt(100)
config.Genesis.L2.Number = 100
config.Genesis.L2.Hash = [32]byte{0x01}
mockClient := mockL2Client{chainID: big.NewInt(100), Hash: common.Hash{0x01}}
err := config.ValidateL2Config(context.TODO(), &mockClient)
assert.NoError(t, err)
}
func TestValidateL2ConfigInvalidChainIdFails(t *testing.T) {
config := randConfig()
config.L2ChainID = big.NewInt(101)
config.Genesis.L2.Number = 100
config.Genesis.L2.Hash = [32]byte{0x01}
mockClient := mockL2Client{chainID: big.NewInt(100), Hash: common.Hash{0x01}}
err := config.ValidateL2Config(context.TODO(), &mockClient)
assert.Error(t, err)
config.L2ChainID = big.NewInt(99)
err = config.ValidateL2Config(context.TODO(), &mockClient)
assert.Error(t, err)
}
func TestValidateL2ConfigInvalidGenesisHashFails(t *testing.T) {
config := randConfig()
config.L2ChainID = big.NewInt(100)
config.Genesis.L2.Number = 100
config.Genesis.L2.Hash = [32]byte{0x00}
mockClient := mockL2Client{chainID: big.NewInt(100), Hash: common.Hash{0x01}}
err := config.ValidateL2Config(context.TODO(), &mockClient)
assert.Error(t, err)
config.Genesis.L2.Hash = [32]byte{0x02}
err = config.ValidateL2Config(context.TODO(), &mockClient)
assert.Error(t, err)
}
func TestCheckL2ChainID(t *testing.T) {
config := randConfig()
config.L2ChainID = big.NewInt(100)
err := config.CheckL2ChainID(context.TODO(), &mockL2Client{chainID: big.NewInt(100)})
assert.NoError(t, err)
err = config.CheckL2ChainID(context.TODO(), &mockL2Client{chainID: big.NewInt(101)})
assert.Error(t, err)
err = config.CheckL2ChainID(context.TODO(), &mockL2Client{chainID: big.NewInt(99)})
assert.Error(t, err)
}
func TestCheckL2BlockRefByNumber(t *testing.T) {
config := randConfig()
config.Genesis.L2.Number = 100
config.Genesis.L2.Hash = [32]byte{0x01}
mockClient := mockL2Client{chainID: big.NewInt(100), Hash: common.Hash{0x01}}
err := config.CheckL2GenesisBlockHash(context.TODO(), &mockClient)
assert.NoError(t, err)
mockClient.Hash = common.Hash{0x02}
err = config.CheckL2GenesisBlockHash(context.TODO(), &mockClient)
assert.Error(t, err)
mockClient.Hash = common.Hash{0x00}
err = config.CheckL2GenesisBlockHash(context.TODO(), &mockClient)
assert.Error(t, err)
}
...@@ -12,6 +12,7 @@ package sources ...@@ -12,6 +12,7 @@ package sources
import ( import (
"context" "context"
"fmt" "fmt"
"math/big"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -216,6 +217,16 @@ func (s *EthClient) payloadCall(ctx context.Context, method string, id any) (*et ...@@ -216,6 +217,16 @@ func (s *EthClient) payloadCall(ctx context.Context, method string, id any) (*et
return payload, nil return payload, nil
} }
// ChainID fetches the chain id of the internal RPC.
func (s *EthClient) ChainID(ctx context.Context) (*big.Int, error) {
var id hexutil.Big
err := s.client.CallContext(ctx, &id, "eth_chainId")
if err != nil {
return nil, err
}
return (*big.Int)(&id), nil
}
func (s *EthClient) InfoByHash(ctx context.Context, hash common.Hash) (eth.BlockInfo, error) { func (s *EthClient) InfoByHash(ctx context.Context, hash common.Hash) (eth.BlockInfo, error) {
if header, ok := s.headersCache.Get(hash); ok { if header, ok := s.headersCache.Get(hash); ok {
return header.(*HeaderInfo), nil return header.(*HeaderInfo), nil
......
package version package version
var ( var (
Version = "v0.10.10" Version = "v0.10.11"
Meta = "dev" Meta = "dev"
) )
...@@ -13,7 +13,7 @@ import ( ...@@ -13,7 +13,7 @@ import (
) )
var ( var (
Version = "v0.10.10" Version = "v0.10.11"
GitCommit = "" GitCommit = ""
GitDate = "" GitDate = ""
) )
...@@ -28,9 +28,17 @@ func main() { ...@@ -28,9 +28,17 @@ func main() {
app.Usage = "L2Output Submitter" app.Usage = "L2Output Submitter"
app.Description = "Service for generating and submitting L2 Output checkpoints to the L2OutputOracle contract" app.Description = "Service for generating and submitting L2 Output checkpoints to the L2OutputOracle contract"
app.Action = proposer.Main(Version) app.Action = curryMain(Version)
err := app.Run(os.Args) err := app.Run(os.Args)
if err != nil { if err != nil {
log.Crit("Application failed", "message", err) log.Crit("Application failed", "message", err)
} }
} }
// curryMain transforms the proposer.Main function into an app.Action
// This is done to capture the Version of the proposer.
func curryMain(version string) func(ctx *cli.Context) error {
return func(ctx *cli.Context) error {
return proposer.Main(version, ctx)
}
}
...@@ -3,13 +3,11 @@ module github.com/ethereum-optimism/optimism/op-proposer ...@@ -3,13 +3,11 @@ module github.com/ethereum-optimism/optimism/op-proposer
go 1.18 go 1.18
require ( require (
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 github.com/ethereum-optimism/optimism/op-bindings v0.10.11
github.com/ethereum-optimism/optimism/op-bindings v0.10.10 github.com/ethereum-optimism/optimism/op-node v0.10.11
github.com/ethereum-optimism/optimism/op-node v0.10.10 github.com/ethereum-optimism/optimism/op-service v0.10.11
github.com/ethereum-optimism/optimism/op-service v0.10.10
github.com/ethereum-optimism/optimism/op-signer v0.1.0 github.com/ethereum-optimism/optimism/op-signer v0.1.0
github.com/ethereum/go-ethereum v1.10.26 github.com/ethereum/go-ethereum v1.10.26
github.com/stretchr/testify v1.8.1
github.com/urfave/cli v1.22.9 github.com/urfave/cli v1.22.9
) )
...@@ -17,13 +15,9 @@ require ( ...@@ -17,13 +15,9 @@ require (
github.com/VictoriaMetrics/fastcache v1.10.0 // indirect github.com/VictoriaMetrics/fastcache v1.10.0 // indirect
github.com/benbjohnson/clock v1.3.0 // indirect github.com/benbjohnson/clock v1.3.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/btcsuite/btcd v0.23.3 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
github.com/btcsuite/btcd/btcutil v1.1.0 // indirect
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deckarep/golang-set v1.8.0 // indirect github.com/deckarep/golang-set v1.8.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/dyson/certman v0.3.0 // indirect github.com/dyson/certman v0.3.0 // indirect
...@@ -71,7 +65,6 @@ require ( ...@@ -71,7 +65,6 @@ require (
github.com/multiformats/go-varint v0.0.6 // indirect github.com/multiformats/go-varint v0.0.6 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.13.0 // indirect github.com/prometheus/client_golang v1.13.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/common v0.37.0 // indirect
...@@ -87,7 +80,6 @@ require ( ...@@ -87,7 +80,6 @@ require (
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.5.0 // indirect github.com/tklauser/numcpus v0.5.0 // indirect
github.com/tyler-smith/go-bip39 v1.1.0 // indirect
github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa // indirect github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect
...@@ -98,7 +90,6 @@ require ( ...@@ -98,7 +90,6 @@ require (
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // 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
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.1.7 // indirect lukechampine.com/blake3 v1.1.7 // indirect
) )
......
This diff is collapsed.
...@@ -9,11 +9,13 @@ import ( ...@@ -9,11 +9,13 @@ 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"
"github.com/ethereum-optimism/optimism/op-proposer/txmgr"
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"
opsigner "github.com/ethereum-optimism/optimism/op-signer/client" opsigner "github.com/ethereum-optimism/optimism/op-signer/client"
) )
...@@ -27,7 +29,7 @@ type Config struct { ...@@ -27,7 +29,7 @@ type Config struct {
RollupClient *sources.RollupClient RollupClient *sources.RollupClient
AllowNonFinalized bool AllowNonFinalized bool
From common.Address From common.Address
SignerFnFactory SignerFactory 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.
......
...@@ -2,7 +2,6 @@ package proposer ...@@ -2,7 +2,6 @@ package proposer
import ( import (
"context" "context"
"crypto/ecdsa"
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
...@@ -14,28 +13,23 @@ import ( ...@@ -14,28 +13,23 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/ethereum/go-ethereum/accounts"
"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/common/hexutil"
"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/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"
hdwallet "github.com/ethereum-optimism/go-ethereum-hdwallet"
"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"
"github.com/ethereum-optimism/optimism/op-proposer/txmgr"
opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" 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"
opsigner "github.com/ethereum-optimism/optimism/op-signer/client" "github.com/ethereum-optimism/optimism/op-service/txmgr"
) )
const ( const (
...@@ -46,83 +40,75 @@ const ( ...@@ -46,83 +40,75 @@ const (
var supportedL2OutputVersion = eth.Bytes32{} var supportedL2OutputVersion = eth.Bytes32{}
type SignerFn func(context.Context, common.Address, *types.Transaction) (*types.Transaction, error) // Main is the entrypoint into the L2 Output Submitter. This method executes the
// service and blocks until the service exits.
type SignerFactory func(chainID *big.Int) SignerFn func Main(version string, cliCtx *cli.Context) error {
cfg := NewConfig(cliCtx)
// Main is the entrypoint into the L2 Output Submitter. This method returns a if err := cfg.Check(); err != nil {
// closure that executes the service and blocks until the service exits. The use return fmt.Errorf("invalid CLI flags: %w", err)
// of a closure allows the parameters bound to the top-level main package, e.g. }
// GitVersion, to be captured and used once the function is executed.
func Main(version string) func(ctx *cli.Context) error {
return func(cliCtx *cli.Context) error {
cfg := NewConfig(cliCtx)
if err := cfg.Check(); err != nil {
return fmt.Errorf("invalid CLI flags: %w", err)
}
l := oplog.NewLogger(cfg.LogConfig)
l.Info("Initializing L2 Output Submitter")
l2OutputSubmitter, err := NewL2OutputSubmitter(cfg, l) l := oplog.NewLogger(cfg.LogConfig)
if err != nil { l.Info("Initializing L2 Output Submitter")
l.Error("Unable to create L2 Output Submitter", "error", err)
return err
}
l.Info("Starting L2 Output Submitter") l2OutputSubmitter, err := NewL2OutputSubmitterFromCLIConfig(cfg, l)
ctx, cancel := context.WithCancel(context.Background()) if err != nil {
l.Error("Unable to create the L2 Output Submitter", "error", err)
return err
}
if err := l2OutputSubmitter.Start(); err != nil { l.Info("Starting L2 Output Submitter")
cancel() ctx, cancel := context.WithCancel(context.Background())
l.Error("Unable to start L2 Output Submitter", "error", err)
return err
}
defer l2OutputSubmitter.Stop()
l.Info("L2 Output Submitter started")
pprofConfig := cfg.PprofConfig
if pprofConfig.Enabled {
l.Info("starting pprof", "addr", pprofConfig.ListenAddr, "port", pprofConfig.ListenPort)
go func() {
if err := oppprof.ListenAndServe(ctx, pprofConfig.ListenAddr, pprofConfig.ListenPort); err != nil {
l.Error("error starting pprof", "err", err)
}
}()
}
registry := opmetrics.NewRegistry() if err := l2OutputSubmitter.Start(); err != nil {
metricsCfg := cfg.MetricsConfig cancel()
if metricsCfg.Enabled { l.Error("Unable to start L2 Output Submitter", "error", err)
l.Info("starting metrics server", "addr", metricsCfg.ListenAddr, "port", metricsCfg.ListenPort) return err
go func() { }
if err := opmetrics.ListenAndServe(ctx, registry, metricsCfg.ListenAddr, metricsCfg.ListenPort); err != nil { defer l2OutputSubmitter.Stop()
l.Error("error starting metrics server", err)
} l.Info("L2 Output Submitter started")
}() pprofConfig := cfg.PprofConfig
addr := l2OutputSubmitter.from if pprofConfig.Enabled {
opmetrics.LaunchBalanceMetrics(ctx, l, registry, "", l2OutputSubmitter.l1Client, addr) l.Info("starting pprof", "addr", pprofConfig.ListenAddr, "port", pprofConfig.ListenPort)
} go func() {
if err := oppprof.ListenAndServe(ctx, pprofConfig.ListenAddr, pprofConfig.ListenPort); err != nil {
l.Error("error starting pprof", "err", err)
}
}()
}
rpcCfg := cfg.RPCConfig registry := opmetrics.NewRegistry()
server := oprpc.NewServer(rpcCfg.ListenAddr, rpcCfg.ListenPort, version) metricsCfg := cfg.MetricsConfig
if err := server.Start(); err != nil { if metricsCfg.Enabled {
cancel() l.Info("starting metrics server", "addr", metricsCfg.ListenAddr, "port", metricsCfg.ListenPort)
return fmt.Errorf("error starting RPC server: %w", err) go func() {
} if err := opmetrics.ListenAndServe(ctx, registry, metricsCfg.ListenAddr, metricsCfg.ListenPort); err != nil {
l.Error("error starting metrics server", err)
}
}()
addr := l2OutputSubmitter.from
opmetrics.LaunchBalanceMetrics(ctx, l, registry, "", l2OutputSubmitter.l1Client, addr)
}
interruptChannel := make(chan os.Signal, 1) rpcCfg := cfg.RPCConfig
signal.Notify(interruptChannel, []os.Signal{ server := oprpc.NewServer(rpcCfg.ListenAddr, rpcCfg.ListenPort, version)
os.Interrupt, if err := server.Start(); err != nil {
os.Kill,
syscall.SIGTERM,
syscall.SIGQUIT,
}...)
<-interruptChannel
cancel() cancel()
return fmt.Errorf("error starting RPC server: %w", err)
return nil
} }
interruptChannel := make(chan os.Signal, 1)
signal.Notify(interruptChannel, []os.Signal{
os.Interrupt,
os.Kill,
syscall.SIGTERM,
syscall.SIGQUIT,
}...)
<-interruptChannel
cancel()
return nil
} }
// L2OutputSubmitter is responsible for proposing outputs // L2OutputSubmitter is responsible for proposing outputs
...@@ -151,66 +137,16 @@ type L2OutputSubmitter struct { ...@@ -151,66 +137,16 @@ type L2OutputSubmitter struct {
// From is the address to send transactions from // From is the address to send transactions from
from common.Address from common.Address
// SignerFn is the function used to sign transactions // SignerFn is the function used to sign transactions
signerFn SignerFn 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
} }
// NewL2OutputSubmitter initializes the L2OutputSubmitter, gathering any resources // NewL2OutputSubmitterFromCLIConfig creates a new L2 Output Submitter given the CLI Config
// that will be needed during operation. func NewL2OutputSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger) (*L2OutputSubmitter, error) {
func NewL2OutputSubmitter(cfg CLIConfig, l log.Logger) (*L2OutputSubmitter, error) { signer, fromAddress, err := opcrypto.SignerFactoryFromConfig(l, cfg.PrivateKey, cfg.Mnemonic, cfg.L2OutputHDPath, cfg.SignerConfig)
var l2OutputPrivKey *ecdsa.PrivateKey if err != nil {
var err error return nil, err
var signer SignerFactory
var fromAddress common.Address
if cfg.SignerConfig.Enabled() {
signerClient, err := opsigner.NewSignerClientFromConfig(l, cfg.SignerConfig)
if err != nil {
l.Error("Unable to create Signer Client", "error", err)
return nil, err
}
fromAddress = common.BytesToAddress(hexutil.MustDecode(cfg.SignerConfig.Address))
signer = func(chainID *big.Int) SignerFn {
return func(ctx context.Context, address common.Address, tx *types.Transaction) (*types.Transaction, error) {
if address.String() != cfg.SignerConfig.Address {
return nil, fmt.Errorf("attempting to sign for %s, expected %s: ", address, cfg.SignerConfig.Address)
}
return signerClient.SignTransaction(ctx, chainID, tx)
}
}
} else {
if cfg.PrivateKey != "" && cfg.Mnemonic != "" {
return nil, errors.New("cannot specify both a private key and a mnemonic")
}
if cfg.PrivateKey == "" {
// Parse l2output wallet private key and L2OO contract address.
wallet, err := hdwallet.NewFromMnemonic(cfg.Mnemonic)
if err != nil {
return nil, err
}
l2OutputPrivKey, err = wallet.PrivateKey(accounts.Account{
URL: accounts.URL{
Path: cfg.L2OutputHDPath,
},
})
if err != nil {
return nil, err
}
} else {
l2OutputPrivKey, err = crypto.HexToECDSA(strings.TrimPrefix(cfg.PrivateKey, "0x"))
if err != nil {
return nil, err
}
}
fromAddress = crypto.PubkeyToAddress(l2OutputPrivKey.PublicKey)
signer = func(chainID *big.Int) SignerFn {
s := opcrypto.PrivateKeySignerFn(l2OutputPrivKey, chainID)
return func(_ context.Context, addr common.Address, tx *types.Transaction) (*types.Transaction, error) {
return s(addr, tx)
}
}
} }
l2ooAddress, err := parseAddress(cfg.L2OOAddress) l2ooAddress, err := parseAddress(cfg.L2OOAddress)
...@@ -218,8 +154,7 @@ func NewL2OutputSubmitter(cfg CLIConfig, l log.Logger) (*L2OutputSubmitter, erro ...@@ -218,8 +154,7 @@ func NewL2OutputSubmitter(cfg CLIConfig, l log.Logger) (*L2OutputSubmitter, erro
return nil, err 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.
ctx := context.Background() ctx := context.Background()
l1Client, err := dialEthClientWithTimeout(ctx, cfg.L1EthRpc) l1Client, err := dialEthClientWithTimeout(ctx, cfg.L1EthRpc)
if err != nil { if err != nil {
...@@ -251,11 +186,11 @@ func NewL2OutputSubmitter(cfg CLIConfig, l log.Logger) (*L2OutputSubmitter, erro ...@@ -251,11 +186,11 @@ func NewL2OutputSubmitter(cfg CLIConfig, l log.Logger) (*L2OutputSubmitter, erro
SignerFnFactory: signer, SignerFnFactory: signer,
} }
return NewL2OutputSubmitterWithSigner(proposerCfg, l) return NewL2OutputSubmitter(proposerCfg, l)
} }
// NewL2OutputSubmitterWithSigner creates a new L2 Output Submitter // NewL2OutputSubmitter creates a new L2 Output Submitter
func NewL2OutputSubmitterWithSigner(cfg Config, l log.Logger) (*L2OutputSubmitter, error) { func NewL2OutputSubmitter(cfg Config, l log.Logger) (*L2OutputSubmitter, error) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
cCtx, cCancel := context.WithTimeout(ctx, defaultDialTimeout) cCtx, cCancel := context.WithTimeout(ctx, defaultDialTimeout)
......
package crypto package crypto
import ( import (
"context"
"crypto/ecdsa" "crypto/ecdsa"
"errors"
"fmt"
"math/big" "math/big"
"strings"
"github.com/ethereum/go-ethereum/accounts"
"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/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
hdwallet "github.com/ethereum-optimism/go-ethereum-hdwallet"
opsigner "github.com/ethereum-optimism/optimism/op-signer/client"
) )
func PrivateKeySignerFn(key *ecdsa.PrivateKey, chainID *big.Int) bind.SignerFn { func PrivateKeySignerFn(key *ecdsa.PrivateKey, chainID *big.Int) bind.SignerFn {
...@@ -24,3 +33,71 @@ func PrivateKeySignerFn(key *ecdsa.PrivateKey, chainID *big.Int) bind.SignerFn { ...@@ -24,3 +33,71 @@ func PrivateKeySignerFn(key *ecdsa.PrivateKey, chainID *big.Int) bind.SignerFn {
return tx.WithSignature(signer, signature) return tx.WithSignature(signer, signature)
} }
} }
// SignerFn is a generic transaction signing function. It may be a remote signer so it takes a context.
// It also takes the address that should be used to sign the transaction with.
type SignerFn func(context.Context, common.Address, *types.Transaction) (*types.Transaction, error)
// SignerFactory creates a SignerFn that is bound to a specific ChainID
type SignerFactory func(chainID *big.Int) SignerFn
// SignerFactoryFromConfig considers three ways that signers are created & then creates single factory from those config options.
// It can either take a remote signer (via opsigner.CLIConfig) or it can be provided either a mnemonic + derivation path or a private key.
// It prefers the remote signer, then the mnemonic or private key (only one of which can be provided).
func SignerFactoryFromConfig(l log.Logger, privateKey, mnemonic, hdPath string, signerConfig opsigner.CLIConfig) (SignerFactory, common.Address, error) {
var signer SignerFactory
var fromAddress common.Address
if signerConfig.Enabled() {
signerClient, err := opsigner.NewSignerClientFromConfig(l, signerConfig)
if err != nil {
l.Error("Unable to create Signer Client", "error", err)
return nil, common.Address{}, fmt.Errorf("failed to create the signer client: %w", err)
}
fromAddress = common.HexToAddress(signerConfig.Address)
signer = func(chainID *big.Int) SignerFn {
return func(ctx context.Context, address common.Address, tx *types.Transaction) (*types.Transaction, error) {
if address.String() != signerConfig.Address {
return nil, fmt.Errorf("attempting to sign for %s, expected %s: ", address, signerConfig.Address)
}
return signerClient.SignTransaction(ctx, chainID, tx)
}
}
} else {
var privKey *ecdsa.PrivateKey
var err error
if privateKey != "" && mnemonic != "" {
return nil, common.Address{}, errors.New("cannot specify both a private key and a mnemonic")
}
if privateKey == "" {
// Parse l2output wallet private key and L2OO contract address.
wallet, err := hdwallet.NewFromMnemonic(mnemonic)
if err != nil {
return nil, common.Address{}, fmt.Errorf("failed to parse mnemonic: %w", err)
}
privKey, err = wallet.PrivateKey(accounts.Account{
URL: accounts.URL{
Path: hdPath,
},
})
if err != nil {
return nil, common.Address{}, fmt.Errorf("failed to create a wallet: %w", err)
}
} else {
privKey, err = crypto.HexToECDSA(strings.TrimPrefix(privateKey, "0x"))
if err != nil {
return nil, common.Address{}, fmt.Errorf("failed to parse the private key: %w", err)
}
}
fromAddress = crypto.PubkeyToAddress(privKey.PublicKey)
signer = func(chainID *big.Int) SignerFn {
s := PrivateKeySignerFn(privKey, chainID)
return func(_ context.Context, addr common.Address, tx *types.Transaction) (*types.Transaction, error) {
return s(addr, tx)
}
}
}
return signer, fromAddress, nil
}
...@@ -3,6 +3,8 @@ module github.com/ethereum-optimism/optimism/op-service ...@@ -3,6 +3,8 @@ module github.com/ethereum-optimism/optimism/op-service
go 1.18 go 1.18
require ( require (
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3
github.com/ethereum-optimism/optimism/op-signer v0.1.0
github.com/ethereum/go-ethereum v1.10.26 github.com/ethereum/go-ethereum v1.10.26
github.com/prometheus/client_golang v1.13.0 github.com/prometheus/client_golang v1.13.0
github.com/stretchr/testify v1.8.1 github.com/stretchr/testify v1.8.1
...@@ -13,23 +15,33 @@ require ( ...@@ -13,23 +15,33 @@ require (
require ( require (
github.com/VictoriaMetrics/fastcache v1.9.0 // indirect github.com/VictoriaMetrics/fastcache v1.9.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/btcsuite/btcd v0.23.3 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
github.com/btcsuite/btcd/btcutil v1.1.0 // indirect
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deckarep/golang-set v1.8.0 // indirect github.com/deckarep/golang-set v1.8.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/dyson/certman v0.3.0 // indirect
github.com/edsrzf/mmap-go v1.1.0 // indirect
github.com/fjl/memsize v0.0.1 // indirect github.com/fjl/memsize v0.0.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect
github.com/go-kit/kit v0.10.0 // indirect github.com/go-kit/kit v0.10.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-stack/stack v1.8.1 // indirect github.com/go-stack/stack v1.8.1 // indirect
github.com/golang-jwt/jwt/v4 v4.3.0 // indirect github.com/golang-jwt/jwt/v4 v4.3.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.2.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect
github.com/hashicorp/go-bexpr v0.1.11 // indirect github.com/hashicorp/go-bexpr v0.1.11 // indirect
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect
github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e // indirect
github.com/holiman/bloomfilter/v2 v2.0.3 // indirect
github.com/holiman/uint256 v1.2.0 // indirect
github.com/huin/goupnp v1.0.3 // indirect github.com/huin/goupnp v1.0.3 // indirect
github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/kr/pretty v0.3.0 // indirect github.com/kr/pretty v0.3.0 // indirect
...@@ -46,21 +58,24 @@ require ( ...@@ -46,21 +58,24 @@ require (
github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect
github.com/prometheus/tsdb v0.10.0 // indirect github.com/prometheus/tsdb v0.10.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.2.1-0.20211004051800-57c86be7915a // indirect
github.com/rjeczalik/notify v0.9.1 // indirect github.com/rjeczalik/notify v0.9.2 // indirect
github.com/rogpeppe/go-internal v1.8.1 // indirect github.com/rogpeppe/go-internal v1.8.1 // indirect
github.com/rs/cors v1.8.2 // indirect github.com/rs/cors v1.8.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/status-im/keycard-go v0.0.0-20211109104530-b0e0482ba91d // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.4.0 // indirect github.com/tklauser/numcpus v0.4.0 // indirect
github.com/tyler-smith/go-bip39 v1.1.0 // indirect
github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa // indirect github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 // indirect golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 // indirect
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
google.golang.org/protobuf v1.28.1 // indirect google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
......
This diff is collapsed.
...@@ -6,7 +6,7 @@ import ( ...@@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-proposer/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
) )
......
...@@ -10,7 +10,7 @@ import ( ...@@ -10,7 +10,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-proposer/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"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"
......
...@@ -631,10 +631,10 @@ ...@@ -631,10 +631,10 @@
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-end; justify-content: unsafe flex-end; width: 96px; height: 1px; padding-top: 1341px; margin-left: 2191px;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-end; justify-content: unsafe flex-end; width: 96px; height: 1px; padding-top: 1341px; margin-left: 2191px;">
<div style="box-sizing: border-box; font-size: 0px; text-align: right;" data-drawio-colors="color: #000000; "> <div style="box-sizing: border-box; font-size: 0px; text-align: right;" data-drawio-colors="color: #000000; ">
<div style="display: inline-block; font-size: 30px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">100-2</div> <div style="display: inline-block; font-size: 30px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">100-4</div>
</div> </div>
</div> </div>
</foreignObject><text x="2287" y="1341" fill="#000000" font-family="Helvetica" font-size="30px" text-anchor="end">100-2</text> </foreignObject><text x="2287" y="1341" fill="#000000" font-family="Helvetica" font-size="30px" text-anchor="end">100-4</text>
</switch> </switch>
</g> </g>
<path d="M 9360 5420 L 9440 5180 L 9840 5180 L 9760 5420 Z" fill="#ffff66" stroke="rgb(0, 0, 0)" stroke-width="4" stroke-miterlimit="10" pointer-events="all" /> <path d="M 9360 5420 L 9440 5180 L 9840 5180 L 9760 5420 Z" fill="#ffff66" stroke="rgb(0, 0, 0)" stroke-width="4" stroke-miterlimit="10" pointer-events="all" />
...@@ -836,4 +836,4 @@ ...@@ -836,4 +836,4 @@
<switch> <switch>
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" /><a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank"><text text-anchor="middle" font-size="10px" x="50%" y="100%">Text is not SVG - cannot display</text></a> <g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" /><a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank"><text text-anchor="middle" font-size="10px" x="50%" y="100%">Text is not SVG - cannot display</text></a>
</switch> </switch>
</svg> </svg>
\ No newline at end of file
This diff is collapsed.
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