Commit dd9c9965 authored by joohhnnn's avatar joohhnnn

Merge branch 'develop' into proposer

parents b1391700 a6d211ab
...@@ -1418,6 +1418,13 @@ workflows: ...@@ -1418,6 +1418,13 @@ workflows:
- op-stack-go-lint - op-stack-go-lint
- devnet-allocs - devnet-allocs
- l1-geth-version-check - l1-geth-version-check
- go-e2e-test:
name: op-e2e-span-batch-tests
module: op-e2e
target: test-span-batch
requires:
- op-stack-go-lint
- devnet-allocs
- op-program-compat: - op-program-compat:
requires: requires:
- op-program-tests - op-program-tests
......
...@@ -202,13 +202,12 @@ def devnet_deploy(paths): ...@@ -202,13 +202,12 @@ def devnet_deploy(paths):
# If someone reads this comment and understands why this is being done, please # If someone reads this comment and understands why this is being done, please
# update this comment to explain. # update this comment to explain.
init_devnet_l1_deploy_config(paths, update_timestamp=True) init_devnet_l1_deploy_config(paths, update_timestamp=True)
outfile_l1 = pjoin(paths.devnet_dir, 'genesis-l1.json')
run_command([ run_command([
'go', 'run', 'cmd/main.go', 'genesis', 'l1', 'go', 'run', 'cmd/main.go', 'genesis', 'l1',
'--deploy-config', paths.devnet_config_path, '--deploy-config', paths.devnet_config_path,
'--l1-allocs', paths.allocs_path, '--l1-allocs', paths.allocs_path,
'--l1-deployments', paths.addresses_json_path, '--l1-deployments', paths.addresses_json_path,
'--outfile.l1', outfile_l1, '--outfile.l1', paths.genesis_l1_path,
], cwd=paths.op_node_dir) ], cwd=paths.op_node_dir)
log.info('Starting L1.') log.info('Starting L1.')
...@@ -227,8 +226,8 @@ def devnet_deploy(paths): ...@@ -227,8 +226,8 @@ def devnet_deploy(paths):
'--l1-rpc', 'http://localhost:8545', '--l1-rpc', 'http://localhost:8545',
'--deploy-config', paths.devnet_config_path, '--deploy-config', paths.devnet_config_path,
'--deployment-dir', paths.deployment_dir, '--deployment-dir', paths.deployment_dir,
'--outfile.l2', pjoin(paths.devnet_dir, 'genesis-l2.json'), '--outfile.l2', paths.genesis_l2_path,
'--outfile.rollup', pjoin(paths.devnet_dir, 'rollup.json') '--outfile.rollup', paths.rollup_config_path
], cwd=paths.op_node_dir) ], cwd=paths.op_node_dir)
rollup_config = read_json(paths.rollup_config_path) rollup_config = read_json(paths.rollup_config_path)
...@@ -288,21 +287,23 @@ def debug_dumpBlock(url): ...@@ -288,21 +287,23 @@ def debug_dumpBlock(url):
def wait_for_rpc_server(url): def wait_for_rpc_server(url):
log.info(f'Waiting for RPC server at {url}') log.info(f'Waiting for RPC server at {url}')
conn = http.client.HTTPConnection(url)
headers = {'Content-type': 'application/json'} headers = {'Content-type': 'application/json'}
body = '{"id":1, "jsonrpc":"2.0", "method": "eth_chainId", "params":[]}' body = '{"id":1, "jsonrpc":"2.0", "method": "eth_chainId", "params":[]}'
while True: while True:
try: try:
conn = http.client.HTTPConnection(url)
conn.request('POST', '/', body, headers) conn.request('POST', '/', body, headers)
response = conn.getresponse() response = conn.getresponse()
conn.close()
if response.status < 300: if response.status < 300:
log.info(f'RPC server at {url} ready') log.info(f'RPC server at {url} ready')
return return
except Exception as e: except Exception as e:
log.info(f'Waiting for RPC server at {url}') log.info(f'Waiting for RPC server at {url}')
time.sleep(1) time.sleep(1)
finally:
if conn:
conn.close()
CommandPreset = namedtuple('Command', ['name', 'args', 'cwd', 'timeout']) CommandPreset = namedtuple('Command', ['name', 'args', 'cwd', 'timeout'])
......
...@@ -12,6 +12,7 @@ ignore: ...@@ -12,6 +12,7 @@ ignore:
- "op-bindings/bindings/*.go" - "op-bindings/bindings/*.go"
- "**/*.t.sol" - "**/*.t.sol"
- "packages/contracts-bedrock/test/**/*.sol" - "packages/contracts-bedrock/test/**/*.sol"
- "packages/contracts-bedrock/scripts/**/*.sol"
- "packages/contracts-bedrock/contracts/vendor/WETH9.sol" - "packages/contracts-bedrock/contracts/vendor/WETH9.sol"
- 'packages/contracts-bedrock/contracts/EAS/**/*.sol' - 'packages/contracts-bedrock/contracts/EAS/**/*.sol'
coverage: coverage:
......
...@@ -6,33 +6,25 @@ import ( ...@@ -6,33 +6,25 @@ import (
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"runtime/debug"
"strconv" "strconv"
"sync" "sync/atomic"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/prometheus/client_golang/prometheus"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/indexer/api/routes" "github.com/ethereum-optimism/optimism/indexer/api/routes"
"github.com/ethereum-optimism/optimism/indexer/config" "github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database" "github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/op-service/httputil" "github.com/ethereum-optimism/optimism/op-service/httputil"
"github.com/ethereum-optimism/optimism/op-service/metrics" "github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/ethereum/go-ethereum/log"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/prometheus/client_golang/prometheus"
) )
const ethereumAddressRegex = `^0x[a-fA-F0-9]{40}$` const ethereumAddressRegex = `^0x[a-fA-F0-9]{40}$`
// Api ... Indexer API struct
// TODO : Structured error responses
type API struct {
log log.Logger
router *chi.Mux
serverConfig config.ServerConfig
metricsConfig config.ServerConfig
metricsRegistry *prometheus.Registry
}
const ( const (
MetricsNamespace = "op_indexer_api" MetricsNamespace = "op_indexer_api"
addressParam = "{address:%s}" addressParam = "{address:%s}"
...@@ -45,6 +37,23 @@ const ( ...@@ -45,6 +37,23 @@ const (
WithdrawalsPath = "/api/v0/withdrawals/" WithdrawalsPath = "/api/v0/withdrawals/"
) )
// Api ... Indexer API struct
// TODO : Structured error responses
type APIService struct {
log log.Logger
router *chi.Mux
bv database.BridgeTransfersView
dbClose func() error
metricsRegistry *prometheus.Registry
apiServer *httputil.HTTPServer
metricsServer *httputil.HTTPServer
stopped atomic.Bool
}
// chiMetricsMiddleware ... Injects a metrics recorder into request processing middleware // chiMetricsMiddleware ... Injects a metrics recorder into request processing middleware
func chiMetricsMiddleware(rec metrics.HTTPRecorder) func(http.Handler) http.Handler { func chiMetricsMiddleware(rec metrics.HTTPRecorder) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
...@@ -53,112 +62,117 @@ func chiMetricsMiddleware(rec metrics.HTTPRecorder) func(http.Handler) http.Hand ...@@ -53,112 +62,117 @@ func chiMetricsMiddleware(rec metrics.HTTPRecorder) func(http.Handler) http.Hand
} }
// NewApi ... Construct a new api instance // NewApi ... Construct a new api instance
func NewApi(logger log.Logger, bv database.BridgeTransfersView, serverConfig config.ServerConfig, metricsConfig config.ServerConfig) *API { func NewApi(ctx context.Context, log log.Logger, cfg *Config) (*APIService, error) {
// (1) Initialize dependencies out := &APIService{log: log, metricsRegistry: metrics.NewRegistry()}
apiRouter := chi.NewRouter() if err := out.initFromConfig(ctx, cfg); err != nil {
h := routes.NewRoutes(logger, bv, apiRouter) return nil, errors.Join(err, out.Stop(ctx)) // close any resources we may have opened already
}
mr := metrics.NewRegistry() return out, nil
promRecorder := metrics.NewPromHTTPRecorder(mr, MetricsNamespace)
// (2) Inject routing middleware
apiRouter.Use(chiMetricsMiddleware(promRecorder))
apiRouter.Use(middleware.Recoverer)
apiRouter.Use(middleware.Heartbeat(HealthPath))
// (3) Set GET routes
apiRouter.Get(fmt.Sprintf(DepositsPath+addressParam, ethereumAddressRegex), h.L1DepositsHandler)
apiRouter.Get(fmt.Sprintf(WithdrawalsPath+addressParam, ethereumAddressRegex), h.L2WithdrawalsHandler)
return &API{log: logger, router: apiRouter, metricsRegistry: mr, serverConfig: serverConfig, metricsConfig: metricsConfig}
} }
// Run ... Runs the API server routines func (a *APIService) initFromConfig(ctx context.Context, cfg *Config) error {
func (a *API) Run(ctx context.Context) error { if err := a.initDB(ctx, cfg.DB); err != nil {
var wg sync.WaitGroup return fmt.Errorf("failed to init DB: %w", err)
errCh := make(chan error, 2) }
if err := a.startMetricsServer(cfg.MetricsServer); err != nil {
// (1) Construct an inner function that will start a goroutine return fmt.Errorf("failed to start metrics server: %w", err)
// and handle any panics that occur on a shared error channel }
processCtx, processCancel := context.WithCancel(ctx) a.initRouter(cfg.HTTPServer)
runProcess := func(start func(ctx context.Context) error) { if err := a.startServer(cfg.HTTPServer); err != nil {
wg.Add(1) return fmt.Errorf("failed to start API server: %w", err)
go func() {
defer func() {
if err := recover(); err != nil {
a.log.Error("halting api on panic", "err", err)
debug.PrintStack()
errCh <- fmt.Errorf("panic: %v", err)
} }
return nil
}
processCancel() func (a *APIService) Start(ctx context.Context) error {
wg.Done() // Completed all setup-up jobs at init-time already,
}() // and the API service does not have any other special starting routines or background-jobs to start.
return nil
}
errCh <- start(processCtx) func (a *APIService) Stop(ctx context.Context) error {
}() var result error
if a.apiServer != nil {
if err := a.apiServer.Stop(ctx); err != nil {
result = errors.Join(result, fmt.Errorf("failed to stop API server: %w", err))
}
} }
if a.metricsServer != nil {
if err := a.metricsServer.Stop(ctx); err != nil {
result = errors.Join(result, fmt.Errorf("failed to stop metrics server: %w", err))
}
}
if a.dbClose != nil {
if err := a.dbClose(); err != nil {
result = errors.Join(result, fmt.Errorf("failed to close DB: %w", err))
}
}
a.stopped.Store(true)
a.log.Info("API service shutdown complete")
return result
}
// (2) Start the API and metrics servers func (a *APIService) Stopped() bool {
runProcess(a.startServer) return a.stopped.Load()
runProcess(a.startMetricsServer) }
// (3) Wait for all processes to complete // Addr ... returns the address that the HTTP server is listening on (excl. http:// prefix, just the host and port)
wg.Wait() func (a *APIService) Addr() string {
if a.apiServer == nil {
return ""
}
return a.apiServer.Addr().String()
}
err := <-errCh func (a *APIService) initDB(ctx context.Context, connector DBConnector) error {
db, err := connector.OpenDB(ctx, a.log)
if err != nil { if err != nil {
a.log.Error("api stopped", "err", err) return fmt.Errorf("failed to connect to databse: %w", err)
} else {
a.log.Info("api stopped")
} }
a.dbClose = db.Closer
return err a.bv = db.BridgeTransfers
return nil
} }
// Port ... Returns the the port that server is listening on func (a *APIService) initRouter(apiConfig config.ServerConfig) {
func (a *API) Port() int { apiRouter := chi.NewRouter()
return a.serverConfig.Port h := routes.NewRoutes(a.log, a.bv, apiRouter)
promRecorder := metrics.NewPromHTTPRecorder(a.metricsRegistry, MetricsNamespace)
// (2) Inject routing middleware
apiRouter.Use(chiMetricsMiddleware(promRecorder))
apiRouter.Use(middleware.Timeout(time.Duration(apiConfig.WriteTimeout) * time.Second))
apiRouter.Use(middleware.Recoverer)
apiRouter.Use(middleware.Heartbeat(HealthPath))
// (3) Set GET routes
apiRouter.Get(fmt.Sprintf(DepositsPath+addressParam, ethereumAddressRegex), h.L1DepositsHandler)
apiRouter.Get(fmt.Sprintf(WithdrawalsPath+addressParam, ethereumAddressRegex), h.L2WithdrawalsHandler)
a.router = apiRouter
} }
// startServer ... Starts the API server // startServer ... Starts the API server
func (a *API) startServer(ctx context.Context) error { func (a *APIService) startServer(serverConfig config.ServerConfig) error {
a.log.Debug("api server listening...", "port", a.serverConfig.Port) a.log.Debug("API server listening...", "port", serverConfig.Port)
addr := net.JoinHostPort(a.serverConfig.Host, strconv.Itoa(a.serverConfig.Port)) addr := net.JoinHostPort(serverConfig.Host, strconv.Itoa(serverConfig.Port))
srv, err := httputil.StartHTTPServer(addr, a.router) srv, err := httputil.StartHTTPServer(addr, a.router)
if err != nil { if err != nil {
return fmt.Errorf("failed to start API server: %w", err) return fmt.Errorf("failed to start API server: %w", err)
} }
a.log.Info("API server started", "addr", srv.Addr().String())
host, portStr, err := net.SplitHostPort(srv.Addr().String()) a.apiServer = srv
if err != nil {
return errors.Join(err, srv.Close())
}
port, err := strconv.Atoi(portStr)
if err != nil {
return errors.Join(err, srv.Close())
}
// Update the port in the config in case the OS chose a different port
// than the one we requested (e.g. using port 0 to fetch a random open port)
a.serverConfig.Host = host
a.serverConfig.Port = port
<-ctx.Done()
if err := srv.Stop(context.Background()); err != nil {
return fmt.Errorf("failed to shutdown api server: %w", err)
}
return nil return nil
} }
// startMetricsServer ... Starts the metrics server // startMetricsServer ... Starts the metrics server
func (a *API) startMetricsServer(ctx context.Context) error { func (a *APIService) startMetricsServer(metricsConfig config.ServerConfig) error {
a.log.Debug("starting metrics server...", "port", a.metricsConfig.Port) a.log.Debug("starting metrics server...", "port", metricsConfig.Port)
srv, err := metrics.StartServer(a.metricsRegistry, a.metricsConfig.Host, a.metricsConfig.Port) srv, err := metrics.StartServer(a.metricsRegistry, metricsConfig.Host, metricsConfig.Port)
if err != nil { if err != nil {
return fmt.Errorf("failed to start metrics server: %w", err) return fmt.Errorf("failed to start metrics server: %w", err)
} }
<-ctx.Done() a.log.Info("Metrics server started", "addr", srv.Addr().String())
defer a.log.Info("metrics server stopped") a.metricsServer = srv
return srv.Stop(context.Background()) return nil
} }
package api package api
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
...@@ -24,11 +25,12 @@ var mockAddress = "0x4204204204204204204204204204204204204204" ...@@ -24,11 +25,12 @@ var mockAddress = "0x4204204204204204204204204204204204204204"
var apiConfig = config.ServerConfig{ var apiConfig = config.ServerConfig{
Host: "localhost", Host: "localhost",
Port: 8080, Port: 0, // random port, to allow parallel tests
} }
var metricsConfig = config.ServerConfig{ var metricsConfig = config.ServerConfig{
Host: "localhost", Host: "localhost",
Port: 7300, Port: 0, // random port, to allow parallel tests
} }
var ( var (
...@@ -95,8 +97,14 @@ func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalsByAddress(address common. ...@@ -95,8 +97,14 @@ func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalsByAddress(address common.
} }
func TestHealthz(t *testing.T) { func TestHealthz(t *testing.T) {
logger := testlog.Logger(t, log.LvlInfo) logger := testlog.Logger(t, log.LvlInfo)
api := NewApi(logger, &MockBridgeTransfersView{}, apiConfig, metricsConfig) cfg := &Config{
request, err := http.NewRequest("GET", "/healthz", nil) DB: &TestDBConnector{BridgeTransfers: &MockBridgeTransfersView{}},
HTTPServer: apiConfig,
MetricsServer: metricsConfig,
}
api, err := NewApi(context.Background(), logger, cfg)
require.NoError(t, err)
request, err := http.NewRequest("GET", "http://"+api.Addr()+"/healthz", nil)
assert.Nil(t, err) assert.Nil(t, err)
responseRecorder := httptest.NewRecorder() responseRecorder := httptest.NewRecorder()
...@@ -107,8 +115,14 @@ func TestHealthz(t *testing.T) { ...@@ -107,8 +115,14 @@ func TestHealthz(t *testing.T) {
func TestL1BridgeDepositsHandler(t *testing.T) { func TestL1BridgeDepositsHandler(t *testing.T) {
logger := testlog.Logger(t, log.LvlInfo) logger := testlog.Logger(t, log.LvlInfo)
api := NewApi(logger, &MockBridgeTransfersView{}, apiConfig, metricsConfig) cfg := &Config{
request, err := http.NewRequest("GET", fmt.Sprintf("/api/v0/deposits/%s", mockAddress), nil) DB: &TestDBConnector{BridgeTransfers: &MockBridgeTransfersView{}},
HTTPServer: apiConfig,
MetricsServer: metricsConfig,
}
api, err := NewApi(context.Background(), logger, cfg)
require.NoError(t, err)
request, err := http.NewRequest("GET", fmt.Sprintf("http://"+api.Addr()+"/api/v0/deposits/%s", mockAddress), nil)
assert.Nil(t, err) assert.Nil(t, err)
responseRecorder := httptest.NewRecorder() responseRecorder := httptest.NewRecorder()
...@@ -130,8 +144,14 @@ func TestL1BridgeDepositsHandler(t *testing.T) { ...@@ -130,8 +144,14 @@ func TestL1BridgeDepositsHandler(t *testing.T) {
func TestL2BridgeWithdrawalsByAddressHandler(t *testing.T) { func TestL2BridgeWithdrawalsByAddressHandler(t *testing.T) {
logger := testlog.Logger(t, log.LvlInfo) logger := testlog.Logger(t, log.LvlInfo)
api := NewApi(logger, &MockBridgeTransfersView{}, apiConfig, metricsConfig) cfg := &Config{
request, err := http.NewRequest("GET", fmt.Sprintf("/api/v0/withdrawals/%s", mockAddress), nil) DB: &TestDBConnector{BridgeTransfers: &MockBridgeTransfersView{}},
HTTPServer: apiConfig,
MetricsServer: metricsConfig,
}
api, err := NewApi(context.Background(), logger, cfg)
require.NoError(t, err)
request, err := http.NewRequest("GET", fmt.Sprintf("http://"+api.Addr()+"/api/v0/withdrawals/%s", mockAddress), nil)
assert.Nil(t, err) assert.Nil(t, err)
responseRecorder := httptest.NewRecorder() responseRecorder := httptest.NewRecorder()
......
package api
import (
"context"
"fmt"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database"
)
// DB represents the abstract DB access the API has.
type DB struct {
BridgeTransfers database.BridgeTransfersView
Closer func() error
}
// DBConfigConnector implements a fully config based DBConnector
type DBConfigConnector struct {
config.DBConfig
}
func (cfg *DBConfigConnector) OpenDB(ctx context.Context, log log.Logger) (*DB, error) {
db, err := database.NewDB(ctx, log, cfg.DBConfig)
if err != nil {
return nil, fmt.Errorf("failed to connect to databse: %w", err)
}
return &DB{
BridgeTransfers: db.BridgeTransfers,
Closer: db.Close,
}, nil
}
type TestDBConnector struct {
BridgeTransfers database.BridgeTransfersView
}
func (tdb *TestDBConnector) OpenDB(ctx context.Context, log log.Logger) (*DB, error) {
return &DB{
BridgeTransfers: tdb.BridgeTransfers,
Closer: func() error {
log.Info("API service closed test DB view")
return nil
},
}, nil
}
// DBConnector is an interface: the config may provide different ways to access the DB.
// This is implemented in tests to provide custom DB views, or share the DB with other services.
type DBConnector interface {
OpenDB(ctx context.Context, log log.Logger) (*DB, error)
}
// Config for the API service
type Config struct {
DB DBConnector
HTTPServer config.ServerConfig
MetricsServer config.ServerConfig
}
package main package main
import ( import (
"context"
"github.com/urfave/cli/v2"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum-optimism/optimism/indexer" "github.com/ethereum-optimism/optimism/indexer"
"github.com/ethereum-optimism/optimism/indexer/api" "github.com/ethereum-optimism/optimism/indexer/api"
"github.com/ethereum-optimism/optimism/indexer/config" "github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database" "github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/op-service/cliapp"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum-optimism/optimism/op-service/opio"
"github.com/urfave/cli/v2"
) )
var ( var (
...@@ -27,7 +32,7 @@ var ( ...@@ -27,7 +32,7 @@ var (
} }
) )
func runIndexer(ctx *cli.Context) error { func runIndexer(ctx *cli.Context, shutdown context.CancelCauseFunc) (cliapp.Lifecycle, error) {
log := oplog.NewLogger(oplog.AppOut(ctx), oplog.ReadCLIConfig(ctx)).New("role", "indexer") log := oplog.NewLogger(oplog.AppOut(ctx), oplog.ReadCLIConfig(ctx)).New("role", "indexer")
oplog.SetGlobalLogHandler(log.GetHandler()) oplog.SetGlobalLogHandler(log.GetHandler())
log.Info("running indexer...") log.Info("running indexer...")
...@@ -35,26 +40,13 @@ func runIndexer(ctx *cli.Context) error { ...@@ -35,26 +40,13 @@ func runIndexer(ctx *cli.Context) error {
cfg, err := config.LoadConfig(log, ctx.String(ConfigFlag.Name)) cfg, err := config.LoadConfig(log, ctx.String(ConfigFlag.Name))
if err != nil { if err != nil {
log.Error("failed to load config", "err", err) log.Error("failed to load config", "err", err)
return err return nil, err
} }
db, err := database.NewDB(log, cfg.DB) return indexer.NewIndexer(ctx.Context, log, &cfg, shutdown)
if err != nil {
log.Error("failed to connect to database", "err", err)
return err
}
defer db.Close()
indexer, err := indexer.NewIndexer(log, db, cfg.Chain, cfg.RPCs, cfg.HTTPServer, cfg.MetricsServer)
if err != nil {
log.Error("failed to create indexer", "err", err)
return err
}
return indexer.Run(ctx.Context)
} }
func runApi(ctx *cli.Context) error { func runApi(ctx *cli.Context, _ context.CancelCauseFunc) (cliapp.Lifecycle, error) {
log := oplog.NewLogger(oplog.AppOut(ctx), oplog.ReadCLIConfig(ctx)).New("role", "api") log := oplog.NewLogger(oplog.AppOut(ctx), oplog.ReadCLIConfig(ctx)).New("role", "api")
oplog.SetGlobalLogHandler(log.GetHandler()) oplog.SetGlobalLogHandler(log.GetHandler())
log.Info("running api...") log.Info("running api...")
...@@ -62,21 +54,22 @@ func runApi(ctx *cli.Context) error { ...@@ -62,21 +54,22 @@ func runApi(ctx *cli.Context) error {
cfg, err := config.LoadConfig(log, ctx.String(ConfigFlag.Name)) cfg, err := config.LoadConfig(log, ctx.String(ConfigFlag.Name))
if err != nil { if err != nil {
log.Error("failed to load config", "err", err) log.Error("failed to load config", "err", err)
return err return nil, err
} }
db, err := database.NewDB(log, cfg.DB) apiCfg := &api.Config{
if err != nil { DB: &api.DBConfigConnector{DBConfig: cfg.DB},
log.Error("failed to connect to database", "err", err) HTTPServer: cfg.HTTPServer,
return err MetricsServer: cfg.MetricsServer,
} }
defer db.Close()
api := api.NewApi(log, db.BridgeTransfers, cfg.HTTPServer, cfg.MetricsServer) return api.NewApi(ctx.Context, log, apiCfg)
return api.Run(ctx.Context)
} }
func runMigrations(ctx *cli.Context) error { func runMigrations(ctx *cli.Context) error {
// We don't maintain a complicated lifecycle here, just interrupt to shut down.
ctx.Context = opio.CancelOnInterrupt(ctx.Context)
log := oplog.NewLogger(oplog.AppOut(ctx), oplog.ReadCLIConfig(ctx)).New("role", "migrations") log := oplog.NewLogger(oplog.AppOut(ctx), oplog.ReadCLIConfig(ctx)).New("role", "migrations")
oplog.SetGlobalLogHandler(log.GetHandler()) oplog.SetGlobalLogHandler(log.GetHandler())
log.Info("running migrations...") log.Info("running migrations...")
...@@ -87,7 +80,7 @@ func runMigrations(ctx *cli.Context) error { ...@@ -87,7 +80,7 @@ func runMigrations(ctx *cli.Context) error {
return err return err
} }
db, err := database.NewDB(log, cfg.DB) db, err := database.NewDB(ctx.Context, log, cfg.DB)
if err != nil { if err != nil {
log.Error("failed to connect to database", "err", err) log.Error("failed to connect to database", "err", err)
return err return err
...@@ -112,13 +105,13 @@ func newCli(GitCommit string, GitDate string) *cli.App { ...@@ -112,13 +105,13 @@ func newCli(GitCommit string, GitDate string) *cli.App {
Name: "api", Name: "api",
Flags: flags, Flags: flags,
Description: "Runs the api service", Description: "Runs the api service",
Action: runApi, Action: cliapp.LifecycleCmd(runApi),
}, },
{ {
Name: "index", Name: "index",
Flags: flags, Flags: flags,
Description: "Runs the indexing service", Description: "Runs the indexing service",
Action: runIndexer, Action: cliapp.LifecycleCmd(runIndexer),
}, },
{ {
Name: "migrate", Name: "migrate",
......
...@@ -4,9 +4,10 @@ import ( ...@@ -4,9 +4,10 @@ import (
"context" "context"
"os" "os"
"github.com/ethereum/go-ethereum/log"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/opio" "github.com/ethereum-optimism/optimism/op-service/opio"
"github.com/ethereum/go-ethereum/log"
) )
var ( var (
...@@ -15,16 +16,10 @@ var ( ...@@ -15,16 +16,10 @@ var (
) )
func main() { func main() {
// This is the most root context, used to propagate
// cancellations to all spawned application-level goroutines
ctx, cancel := context.WithCancel(context.Background())
go func() {
opio.BlockOnInterrupts()
cancel()
}()
oplog.SetupDefaults() oplog.SetupDefaults()
app := newCli(GitCommit, GitDate) app := newCli(GitCommit, GitDate)
// sub-commands set up their individual interrupt lifecycles, which can block on the given interrupt as needed.
ctx := opio.WithInterruptBlocker(context.Background())
if err := app.RunContext(ctx, os.Args); err != nil { if err := app.RunContext(ctx, os.Args); err != nil {
log.Error("application failed", "err", err) log.Error("application failed", "err", err)
os.Exit(1) os.Exit(1)
......
...@@ -134,10 +134,11 @@ type DBConfig struct { ...@@ -134,10 +134,11 @@ type DBConfig struct {
Password string `toml:"password"` Password string `toml:"password"`
} }
// Configures the a server // Configures the server
type ServerConfig struct { type ServerConfig struct {
Host string `toml:"host"` Host string `toml:"host"`
Port int `toml:"port"` Port int `toml:"port"`
WriteTimeout int `toml:"timeout"`
} }
// LoadConfig loads the `indexer.toml` config file from a given path // LoadConfig loads the `indexer.toml` config file from a given path
......
...@@ -30,7 +30,9 @@ type DB struct { ...@@ -30,7 +30,9 @@ type DB struct {
BridgeTransactions BridgeTransactionsDB BridgeTransactions BridgeTransactionsDB
} }
func NewDB(log log.Logger, dbConfig config.DBConfig) (*DB, error) { // NewDB connects to the configured DB, and provides client-bindings to it.
// The initial connection may fail, or the dial may be cancelled with the provided context.
func NewDB(ctx context.Context, log log.Logger, dbConfig config.DBConfig) (*DB, error) {
log = log.New("module", "db") log = log.New("module", "db")
dsn := fmt.Sprintf("host=%s dbname=%s sslmode=disable", dbConfig.Host, dbConfig.Name) dsn := fmt.Sprintf("host=%s dbname=%s sslmode=disable", dbConfig.Host, dbConfig.Name)
......
...@@ -34,7 +34,7 @@ type E2ETestSuite struct { ...@@ -34,7 +34,7 @@ type E2ETestSuite struct {
// API // API
Client *client.Client Client *client.Client
API *api.API API *api.APIService
// Indexer // Indexer
DB *database.DB DB *database.DB
...@@ -73,7 +73,7 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite { ...@@ -73,7 +73,7 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite {
t.Cleanup(func() { opSys.Close() }) t.Cleanup(func() { opSys.Close() })
// Indexer Configuration and Start // Indexer Configuration and Start
indexerCfg := config.Config{ indexerCfg := &config.Config{
DB: config.DBConfig{ DB: config.DBConfig{
Host: "127.0.0.1", Host: "127.0.0.1",
Port: 5432, Port: 5432,
...@@ -106,51 +106,40 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite { ...@@ -106,51 +106,40 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite {
// the system is running, mark this test for Parallel execution // the system is running, mark this test for Parallel execution
t.Parallel() t.Parallel()
// provide a DB for the unit test. disable logging
silentLog := testlog.Logger(t, log.LvlInfo)
silentLog.SetHandler(log.DiscardHandler())
db, err := database.NewDB(silentLog, indexerCfg.DB)
require.NoError(t, err)
t.Cleanup(func() { db.Close() })
indexerLog := testlog.Logger(t, log.LvlInfo).New("role", "indexer") indexerLog := testlog.Logger(t, log.LvlInfo).New("role", "indexer")
indexer, err := indexer.NewIndexer(indexerLog, db, indexerCfg.Chain, indexerCfg.RPCs, indexerCfg.HTTPServer, indexerCfg.MetricsServer) ix, err := indexer.NewIndexer(context.Background(), indexerLog, indexerCfg, func(cause error) {
if cause != nil {
t.Fatalf("indexer shut down with critical error: %v", cause)
}
})
require.NoError(t, err) require.NoError(t, err)
indexerCtx, indexerStop := context.WithCancel(context.Background()) require.NoError(t, ix.Start(context.Background()), "cleanly start indexer")
go func() {
err := indexer.Run(indexerCtx) t.Cleanup(func() {
if err != nil { // panicking here ensures that the test will exit require.NoError(t, ix.Stop(context.Background()), "cleanly shut down indexer")
// during service failure. Using t.Fail() wouldn't be caught })
// until all awaiting routines finish which would never happen.
panic(err)
}
}()
apiLog := testlog.Logger(t, log.LvlInfo).New("role", "indexer_api") apiLog := testlog.Logger(t, log.LvlInfo).New("role", "indexer_api")
apiCfg := config.ServerConfig{ apiCfg := &api.Config{
DB: &api.TestDBConnector{BridgeTransfers: ix.DB.BridgeTransfers}, // reuse the same DB
HTTPServer: config.ServerConfig{
Host: "127.0.0.1", Host: "127.0.0.1",
Port: 0, Port: 0,
} },
MetricsServer: config.ServerConfig{
mCfg := config.ServerConfig{
Host: "127.0.0.1", Host: "127.0.0.1",
Port: 0, Port: 0,
},
} }
api := api.NewApi(apiLog, db.BridgeTransfers, apiCfg, mCfg) apiService, err := api.NewApi(context.Background(), apiLog, apiCfg)
apiCtx, apiStop := context.WithCancel(context.Background()) require.NoError(t, err, "create indexer API service")
go func() {
err := api.Run(apiCtx)
if err != nil {
panic(err)
}
}()
require.NoError(t, apiService.Start(context.Background()), "start indexer API service")
t.Cleanup(func() { t.Cleanup(func() {
apiStop() require.NoError(t, apiService.Stop(context.Background()), "cleanly shut down indexer")
indexerStop()
}) })
// Wait for the API to start listening // Wait for the API to start listening
...@@ -158,16 +147,15 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite { ...@@ -158,16 +147,15 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite {
client, err := client.NewClient(&client.Config{ client, err := client.NewClient(&client.Config{
PaginationLimit: 100, PaginationLimit: 100,
BaseURL: fmt.Sprintf("http://%s:%d", apiCfg.Host, api.Port()), BaseURL: "http://" + apiService.Addr(),
}) })
require.NoError(t, err, "must open indexer API client")
require.NoError(t, err)
return E2ETestSuite{ return E2ETestSuite{
t: t, t: t,
Client: client, Client: client,
DB: db, DB: ix.DB,
Indexer: indexer, Indexer: ix,
OpCfg: &opCfg, OpCfg: &opCfg,
OpSys: opSys, OpSys: opSys,
L1Client: opSys.Clients["l1"], L1Client: opSys.Clients["l1"],
...@@ -203,7 +191,7 @@ func setupTestDatabase(t *testing.T) string { ...@@ -203,7 +191,7 @@ func setupTestDatabase(t *testing.T) string {
silentLog := log.New() silentLog := log.New()
silentLog.SetHandler(log.DiscardHandler()) silentLog.SetHandler(log.DiscardHandler())
db, err := database.NewDB(silentLog, dbConfig) db, err := database.NewDB(context.Background(), silentLog, dbConfig)
require.NoError(t, err) require.NoError(t, err)
defer db.Close() defer db.Close()
......
...@@ -7,11 +7,13 @@ import ( ...@@ -7,11 +7,13 @@ import (
"math/big" "math/big"
"time" "time"
"github.com/ethereum-optimism/optimism/indexer/node"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/indexer/node"
"github.com/ethereum-optimism/optimism/op-service/clock"
) )
type Config struct { type Config struct {
...@@ -31,9 +33,15 @@ type ETL struct { ...@@ -31,9 +33,15 @@ type ETL struct {
headerTraversal *node.HeaderTraversal headerTraversal *node.HeaderTraversal
contracts []common.Address contracts []common.Address
etlBatches chan ETLBatch etlBatches chan *ETLBatch
EthClient node.EthClient EthClient node.EthClient
// A reference that'll stay populated between intervals
// in the event of failures in order to retry.
headers []types.Header
worker *clock.LoopFn
} }
type ETLBatch struct { type ETLBatch struct {
...@@ -46,25 +54,30 @@ type ETLBatch struct { ...@@ -46,25 +54,30 @@ type ETLBatch struct {
HeadersWithLog map[common.Hash]bool HeadersWithLog map[common.Hash]bool
} }
func (etl *ETL) Start(ctx context.Context) error { // Start starts the ETL polling routine. The ETL work should be stopped with Close().
done := ctx.Done() func (etl *ETL) Start() error {
pollTicker := time.NewTicker(etl.loopInterval) if etl.worker != nil {
defer pollTicker.Stop() return errors.New("already started")
}
// A reference that'll stay populated between intervals
// in the event of failures in order to retry.
var headers []types.Header
etl.log.Info("starting etl...") etl.log.Info("starting etl...")
for { etl.worker = clock.NewLoopFn(clock.SystemClock, etl.tick, func() error {
select { close(etl.etlBatches) // can close the channel now, to signal to the consumer that we're done
case <-done: etl.log.Info("stopped etl worker loop")
etl.log.Info("stopping etl") return nil
}, etl.loopInterval)
return nil return nil
}
case <-pollTicker.C: func (etl *ETL) Close() error {
if etl.worker == nil {
return nil // worker was not running
}
return etl.worker.Close()
}
func (etl *ETL) tick(_ context.Context) {
done := etl.metrics.RecordInterval() done := etl.metrics.RecordInterval()
if len(headers) > 0 { if len(etl.headers) > 0 {
etl.log.Info("retrying previous batch") etl.log.Info("retrying previous batch")
} else { } else {
newHeaders, err := etl.headerTraversal.NextHeaders(etl.headerBufferSize) newHeaders, err := etl.headerTraversal.NextHeaders(etl.headerBufferSize)
...@@ -73,7 +86,7 @@ func (etl *ETL) Start(ctx context.Context) error { ...@@ -73,7 +86,7 @@ func (etl *ETL) Start(ctx context.Context) error {
} else if len(newHeaders) == 0 { } else if len(newHeaders) == 0 {
etl.log.Warn("no new headers. etl at head?") etl.log.Warn("no new headers. etl at head?")
} else { } else {
headers = newHeaders etl.headers = newHeaders
} }
latestHeader := etl.headerTraversal.LatestHeader() latestHeader := etl.headerTraversal.LatestHeader()
...@@ -83,14 +96,12 @@ func (etl *ETL) Start(ctx context.Context) error { ...@@ -83,14 +96,12 @@ func (etl *ETL) Start(ctx context.Context) error {
} }
// only clear the reference if we were able to process this batch // only clear the reference if we were able to process this batch
err := etl.processBatch(headers) err := etl.processBatch(etl.headers)
if err == nil { if err == nil {
headers = nil etl.headers = nil
} }
done(err) done(err)
}
}
} }
func (etl *ETL) processBatch(headers []types.Header) error { func (etl *ETL) processBatch(headers []types.Header) error {
...@@ -143,6 +154,6 @@ func (etl *ETL) processBatch(headers []types.Header) error { ...@@ -143,6 +154,6 @@ func (etl *ETL) processBatch(headers []types.Header) error {
// ensure we use unique downstream references for the etl batch // ensure we use unique downstream references for the etl batch
headersRef := headers headersRef := headers
etl.etlBatches <- ETLBatch{Logger: batchLog, Headers: headersRef, HeaderMap: headerMap, Logs: logs.Logs, HeadersWithLog: headersWithLog} etl.etlBatches <- &ETLBatch{Logger: batchLog, Headers: headersRef, HeaderMap: headerMap, Logs: logs.Logs, HeadersWithLog: headersWithLog}
return nil return nil
} }
...@@ -8,26 +8,37 @@ import ( ...@@ -8,26 +8,37 @@ import (
"sync" "sync"
"time" "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/indexer/config" "github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database" "github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/node" "github.com/ethereum-optimism/optimism/indexer/node"
"github.com/ethereum-optimism/optimism/op-service/retry" "github.com/ethereum-optimism/optimism/op-service/retry"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum-optimism/optimism/op-service/tasks"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
) )
type L1ETL struct { type L1ETL struct {
ETL ETL
// the batch handler may do work that we can interrupt on shutdown
resourceCtx context.Context
resourceCancel context.CancelFunc
tasks tasks.Group
db *database.DB db *database.DB
mu *sync.Mutex
mu sync.Mutex
listeners []chan interface{} listeners []chan interface{}
} }
// NewL1ETL creates a new L1ETL instance that will start indexing from different starting points // NewL1ETL creates a new L1ETL instance that will start indexing from different starting points
// depending on the state of the database and the supplied start height. // depending on the state of the database and the supplied start height.
func NewL1ETL(cfg Config, log log.Logger, db *database.DB, metrics Metricer, client node.EthClient, contracts config.L1Contracts) (*L1ETL, error) { func NewL1ETL(cfg Config, log log.Logger, db *database.DB, metrics Metricer, client node.EthClient,
contracts config.L1Contracts, shutdown context.CancelCauseFunc) (*L1ETL, error) {
log = log.New("etl", "l1") log = log.New("etl", "l1")
zeroAddr := common.Address{} zeroAddr := common.Address{}
...@@ -71,8 +82,10 @@ func NewL1ETL(cfg Config, log log.Logger, db *database.DB, metrics Metricer, cli ...@@ -71,8 +82,10 @@ func NewL1ETL(cfg Config, log log.Logger, db *database.DB, metrics Metricer, cli
} }
// NOTE - The use of un-buffered channel here assumes that downstream consumers // NOTE - The use of un-buffered channel here assumes that downstream consumers
// will be able to keep up with the rate of incoming batches // will be able to keep up with the rate of incoming batches.
etlBatches := make(chan ETLBatch) // When the producer closes the channel we stop consuming from it.
etlBatches := make(chan *ETLBatch)
etl := ETL{ etl := ETL{
loopInterval: time.Duration(cfg.LoopIntervalMsec) * time.Millisecond, loopInterval: time.Duration(cfg.LoopIntervalMsec) * time.Millisecond,
headerBufferSize: uint64(cfg.HeaderBufferSize), headerBufferSize: uint64(cfg.HeaderBufferSize),
...@@ -86,22 +99,56 @@ func NewL1ETL(cfg Config, log log.Logger, db *database.DB, metrics Metricer, cli ...@@ -86,22 +99,56 @@ func NewL1ETL(cfg Config, log log.Logger, db *database.DB, metrics Metricer, cli
EthClient: client, EthClient: client,
} }
return &L1ETL{ETL: etl, db: db, mu: new(sync.Mutex)}, nil resCtx, resCancel := context.WithCancel(context.Background())
return &L1ETL{
ETL: etl,
db: db,
resourceCtx: resCtx,
resourceCancel: resCancel,
tasks: tasks.Group{HandleCrit: func(err error) {
shutdown(fmt.Errorf("critical error in L1 ETL: %w", err))
}},
}, nil
} }
func (l1Etl *L1ETL) Start(ctx context.Context) error { func (l1Etl *L1ETL) Close() error {
errCh := make(chan error, 1) var result error
go func() { // close the producer
errCh <- l1Etl.ETL.Start(ctx) if err := l1Etl.ETL.Close(); err != nil {
}() result = errors.Join(result, fmt.Errorf("failed to close internal ETL: %w", err))
}
// tell the consumer it can stop what it's doing
l1Etl.resourceCancel()
// wait for consumer to pick up on closure of producer
if err := l1Etl.tasks.Wait(); err != nil {
result = errors.Join(result, fmt.Errorf("failed to await batch handler completion: %w", err))
}
return result
}
func (l1Etl *L1ETL) Start() error {
// start ETL batch producer
if err := l1Etl.ETL.Start(); err != nil {
return fmt.Errorf("failed to start internal ETL: %w", err)
}
// start ETL batch consumer
l1Etl.tasks.Go(func() error {
for { for {
select {
case err := <-errCh:
return err
// Index incoming batches (only L1 blocks that have an emitted log) // Index incoming batches (only L1 blocks that have an emitted log)
case batch := <-l1Etl.etlBatches: batch, ok := <-l1Etl.etlBatches
if !ok {
l1Etl.log.Info("No more batches, shutting down L1 batch handler")
return nil
}
if err := l1Etl.handleBatch(batch); err != nil {
return fmt.Errorf("failed to handle batch, stopping L2 ETL: %w", err)
}
}
})
return nil
}
func (l1Etl *L1ETL) handleBatch(batch *ETLBatch) error {
l1BlockHeaders := make([]database.L1BlockHeader, 0, len(batch.Headers)) l1BlockHeaders := make([]database.L1BlockHeader, 0, len(batch.Headers))
for i := range batch.Headers { for i := range batch.Headers {
if _, ok := batch.HeadersWithLog[batch.Headers[i].Hash()]; ok { if _, ok := batch.HeadersWithLog[batch.Headers[i].Hash()]; ok {
...@@ -111,7 +158,7 @@ func (l1Etl *L1ETL) Start(ctx context.Context) error { ...@@ -111,7 +158,7 @@ func (l1Etl *L1ETL) Start(ctx context.Context) error {
if len(l1BlockHeaders) == 0 { if len(l1BlockHeaders) == 0 {
batch.Logger.Info("no l1 blocks with logs in batch") batch.Logger.Info("no l1 blocks with logs in batch")
continue return nil
} }
l1ContractEvents := make([]database.L1ContractEvent, len(batch.Logs)) l1ContractEvents := make([]database.L1ContractEvent, len(batch.Logs))
...@@ -123,7 +170,7 @@ func (l1Etl *L1ETL) Start(ctx context.Context) error { ...@@ -123,7 +170,7 @@ func (l1Etl *L1ETL) Start(ctx context.Context) error {
// Continually try to persist this batch. If it fails after 10 attempts, we simply error out // Continually try to persist this batch. If it fails after 10 attempts, we simply error out
retryStrategy := &retry.ExponentialStrategy{Min: 1000, Max: 20_000, MaxJitter: 250} retryStrategy := &retry.ExponentialStrategy{Min: 1000, Max: 20_000, MaxJitter: 250}
if _, err := retry.Do[interface{}](ctx, 10, retryStrategy, func() (interface{}, error) { if _, err := retry.Do[interface{}](l1Etl.resourceCtx, 10, retryStrategy, func() (interface{}, error) {
if err := l1Etl.db.Transaction(func(tx *database.DB) error { if err := l1Etl.db.Transaction(func(tx *database.DB) error {
if err := tx.Blocks.StoreL1BlockHeaders(l1BlockHeaders); err != nil { if err := tx.Blocks.StoreL1BlockHeaders(l1BlockHeaders); err != nil {
return err return err
...@@ -135,7 +182,7 @@ func (l1Etl *L1ETL) Start(ctx context.Context) error { ...@@ -135,7 +182,7 @@ func (l1Etl *L1ETL) Start(ctx context.Context) error {
return nil return nil
}); err != nil { }); err != nil {
batch.Logger.Error("unable to persist batch", "err", err) batch.Logger.Error("unable to persist batch", "err", err)
return nil, err return nil, fmt.Errorf("unable to persist batch: %w", err)
} }
l1Etl.ETL.metrics.RecordIndexedHeaders(len(l1BlockHeaders)) l1Etl.ETL.metrics.RecordIndexedHeaders(len(l1BlockHeaders))
...@@ -160,8 +207,7 @@ func (l1Etl *L1ETL) Start(ctx context.Context) error { ...@@ -160,8 +207,7 @@ func (l1Etl *L1ETL) Start(ctx context.Context) error {
} }
} }
l1Etl.mu.Unlock() l1Etl.mu.Unlock()
} return nil
}
} }
// Notify returns a channel that'll receive a value every time new data has // Notify returns a channel that'll receive a value every time new data has
......
...@@ -108,7 +108,9 @@ func TestL1ETLConstruction(t *testing.T) { ...@@ -108,7 +108,9 @@ func TestL1ETLConstruction(t *testing.T) {
logger := testlog.Logger(t, log.LvlInfo) logger := testlog.Logger(t, log.LvlInfo)
cfg := Config{StartHeight: ts.start} cfg := Config{StartHeight: ts.start}
etl, err := NewL1ETL(cfg, logger, ts.db.DB, etlMetrics, ts.client, ts.contracts) etl, err := NewL1ETL(cfg, logger, ts.db.DB, etlMetrics, ts.client, ts.contracts, func(cause error) {
t.Fatalf("crit error: %v", cause)
})
test.assertion(etl, err) test.assertion(etl, err)
}) })
} }
......
...@@ -3,24 +3,34 @@ package etl ...@@ -3,24 +3,34 @@ package etl
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"time" "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/indexer/config" "github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database" "github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/node" "github.com/ethereum-optimism/optimism/indexer/node"
"github.com/ethereum-optimism/optimism/op-service/retry" "github.com/ethereum-optimism/optimism/op-service/retry"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum-optimism/optimism/op-service/tasks"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
) )
type L2ETL struct { type L2ETL struct {
ETL ETL
// the batch handler may do work that we can interrupt on shutdown
resourceCtx context.Context
resourceCancel context.CancelFunc
tasks tasks.Group
db *database.DB db *database.DB
} }
func NewL2ETL(cfg Config, log log.Logger, db *database.DB, metrics Metricer, client node.EthClient, contracts config.L2Contracts) (*L2ETL, error) { func NewL2ETL(cfg Config, log log.Logger, db *database.DB, metrics Metricer, client node.EthClient,
contracts config.L2Contracts, shutdown context.CancelCauseFunc) (*L2ETL, error) {
log = log.New("etl", "l2") log = log.New("etl", "l2")
zeroAddr := common.Address{} zeroAddr := common.Address{}
...@@ -54,7 +64,7 @@ func NewL2ETL(cfg Config, log log.Logger, db *database.DB, metrics Metricer, cli ...@@ -54,7 +64,7 @@ func NewL2ETL(cfg Config, log log.Logger, db *database.DB, metrics Metricer, cli
log.Info("no indexed state, starting from genesis") log.Info("no indexed state, starting from genesis")
} }
etlBatches := make(chan ETLBatch) etlBatches := make(chan *ETLBatch)
etl := ETL{ etl := ETL{
loopInterval: time.Duration(cfg.LoopIntervalMsec) * time.Millisecond, loopInterval: time.Duration(cfg.LoopIntervalMsec) * time.Millisecond,
headerBufferSize: uint64(cfg.HeaderBufferSize), headerBufferSize: uint64(cfg.HeaderBufferSize),
...@@ -68,22 +78,57 @@ func NewL2ETL(cfg Config, log log.Logger, db *database.DB, metrics Metricer, cli ...@@ -68,22 +78,57 @@ func NewL2ETL(cfg Config, log log.Logger, db *database.DB, metrics Metricer, cli
EthClient: client, EthClient: client,
} }
return &L2ETL{ETL: etl, db: db}, nil resCtx, resCancel := context.WithCancel(context.Background())
return &L2ETL{
ETL: etl,
resourceCtx: resCtx,
resourceCancel: resCancel,
db: db,
tasks: tasks.Group{HandleCrit: func(err error) {
shutdown(fmt.Errorf("critical error in L2 ETL: %w", err))
}},
}, nil
}
func (l2Etl *L2ETL) Close() error {
var result error
// close the producer
if err := l2Etl.ETL.Close(); err != nil {
result = errors.Join(result, fmt.Errorf("failed to close internal ETL: %w", err))
}
// tell the consumer it can stop what it's doing
l2Etl.resourceCancel()
// wait for consumer to pick up on closure of producer
if err := l2Etl.tasks.Wait(); err != nil {
result = errors.Join(result, fmt.Errorf("failed to await batch handler completion: %w", err))
}
return result
} }
func (l2Etl *L2ETL) Start(ctx context.Context) error { func (l2Etl *L2ETL) Start() error {
errCh := make(chan error, 1) // start ETL batch producer
go func() { if err := l2Etl.ETL.Start(); err != nil {
errCh <- l2Etl.ETL.Start(ctx) return fmt.Errorf("failed to start internal ETL: %w", err)
}() }
// start ETL batch consumer
l2Etl.tasks.Go(func() error {
for { for {
select { // Index incoming batches (all L2 blocks)
case err := <-errCh: batch, ok := <-l2Etl.etlBatches
return err if !ok {
l2Etl.log.Info("No more batches, shutting down L2 batch handler")
return nil
}
if err := l2Etl.handleBatch(batch); err != nil {
return fmt.Errorf("failed to handle batch, stopping L2 ETL: %w", err)
}
}
})
return nil
}
// Index incoming batches (all L2 Blocks) func (l2Etl *L2ETL) handleBatch(batch *ETLBatch) error {
case batch := <-l2Etl.etlBatches:
l2BlockHeaders := make([]database.L2BlockHeader, len(batch.Headers)) l2BlockHeaders := make([]database.L2BlockHeader, len(batch.Headers))
for i := range batch.Headers { for i := range batch.Headers {
l2BlockHeaders[i] = database.L2BlockHeader{BlockHeader: database.BlockHeaderFromHeader(&batch.Headers[i])} l2BlockHeaders[i] = database.L2BlockHeader{BlockHeader: database.BlockHeaderFromHeader(&batch.Headers[i])}
...@@ -98,7 +143,7 @@ func (l2Etl *L2ETL) Start(ctx context.Context) error { ...@@ -98,7 +143,7 @@ func (l2Etl *L2ETL) Start(ctx context.Context) error {
// Continually try to persist this batch. If it fails after 10 attempts, we simply error out // Continually try to persist this batch. If it fails after 10 attempts, we simply error out
retryStrategy := &retry.ExponentialStrategy{Min: 1000, Max: 20_000, MaxJitter: 250} retryStrategy := &retry.ExponentialStrategy{Min: 1000, Max: 20_000, MaxJitter: 250}
if _, err := retry.Do[interface{}](ctx, 10, retryStrategy, func() (interface{}, error) { if _, err := retry.Do[interface{}](l2Etl.resourceCtx, 10, retryStrategy, func() (interface{}, error) {
if err := l2Etl.db.Transaction(func(tx *database.DB) error { if err := l2Etl.db.Transaction(func(tx *database.DB) error {
if err := tx.Blocks.StoreL2BlockHeaders(l2BlockHeaders); err != nil { if err := tx.Blocks.StoreL2BlockHeaders(l2BlockHeaders); err != nil {
return err return err
...@@ -124,6 +169,5 @@ func (l2Etl *L2ETL) Start(ctx context.Context) error { ...@@ -124,6 +169,5 @@ func (l2Etl *L2ETL) Start(ctx context.Context) error {
} }
batch.Logger.Info("indexed batch") batch.Logger.Info("indexed batch")
} return nil
}
} }
...@@ -2,12 +2,12 @@ package indexer ...@@ -2,12 +2,12 @@ package indexer
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"math/big" "math/big"
"net" "net"
"runtime/debug"
"strconv" "strconv"
"sync" "sync/atomic"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -30,149 +30,230 @@ import ( ...@@ -30,149 +30,230 @@ import (
// indexing the configured L1 and L2 chains // indexing the configured L1 and L2 chains
type Indexer struct { type Indexer struct {
log log.Logger log log.Logger
db *database.DB DB *database.DB
l1Client node.EthClient
l2Client node.EthClient
// api server only really serves a /health endpoint here, but this may change in the future
apiServer *httputil.HTTPServer
metricsServer *httputil.HTTPServer
httpConfig config.ServerConfig
metricsConfig config.ServerConfig
metricsRegistry *prometheus.Registry metricsRegistry *prometheus.Registry
L1ETL *etl.L1ETL L1ETL *etl.L1ETL
L2ETL *etl.L2ETL L2ETL *etl.L2ETL
BridgeProcessor *processors.BridgeProcessor BridgeProcessor *processors.BridgeProcessor
// shutdown requests the service that maintains the indexer to shut down,
// and provides the error-cause of the critical failure (if any).
shutdown context.CancelCauseFunc
stopped atomic.Bool
} }
// NewIndexer initializes an instance of the Indexer // NewIndexer initializes an instance of the Indexer
func NewIndexer( func NewIndexer(ctx context.Context, log log.Logger, cfg *config.Config, shutdown context.CancelCauseFunc) (*Indexer, error) {
log log.Logger, out := &Indexer{
db *database.DB, log: log,
chainConfig config.ChainConfig, metricsRegistry: metrics.NewRegistry(),
rpcsConfig config.RPCsConfig, shutdown: shutdown,
httpConfig config.ServerConfig, }
metricsConfig config.ServerConfig, if err := out.initFromConfig(ctx, cfg); err != nil {
) (*Indexer, error) { return nil, errors.Join(err, out.Stop(ctx))
metricsRegistry := metrics.NewRegistry() }
return out, nil
// L1 }
l1EthClient, err := node.DialEthClient(rpcsConfig.L1RPC, node.NewMetrics(metricsRegistry, "l1"))
func (ix *Indexer) Start(ctx context.Context) error {
// If any of these services has a critical failure,
// the service can request a shutdown, while providing the error cause.
if err := ix.L1ETL.Start(); err != nil {
return fmt.Errorf("failed to start L1 ETL: %w", err)
}
if err := ix.L2ETL.Start(); err != nil {
return fmt.Errorf("failed to start L2 ETL: %w", err)
}
if err := ix.BridgeProcessor.Start(); err != nil {
return fmt.Errorf("failed to start bridge processor: %w", err)
}
return nil
}
func (ix *Indexer) Stop(ctx context.Context) error {
var result error
if ix.L1ETL != nil {
if err := ix.L1ETL.Close(); err != nil {
result = errors.Join(result, fmt.Errorf("failed to close L1 ETL: %w", err))
}
}
if ix.L2ETL != nil {
if err := ix.L2ETL.Close(); err != nil {
result = errors.Join(result, fmt.Errorf("failed to close L2 ETL: %w", err))
}
}
if ix.BridgeProcessor != nil {
if err := ix.BridgeProcessor.Close(); err != nil {
result = errors.Join(result, fmt.Errorf("failed to close bridge processor: %w", err))
}
}
// Now that the ETLs are closed, we can stop the RPC clients
if ix.l1Client != nil {
ix.l1Client.Close()
}
if ix.l2Client != nil {
ix.l2Client.Close()
}
if ix.apiServer != nil {
if err := ix.apiServer.Close(); err != nil {
result = errors.Join(result, fmt.Errorf("failed to close indexer API server: %w", err))
}
}
// DB connection can be closed last, after all its potential users have shut down
if ix.DB != nil {
if err := ix.DB.Close(); err != nil {
result = errors.Join(result, fmt.Errorf("failed to close DB: %w", err))
}
}
if ix.metricsServer != nil {
if err := ix.metricsServer.Close(); err != nil {
result = errors.Join(result, fmt.Errorf("failed to close metrics server: %w", err))
}
}
ix.stopped.Store(true)
ix.log.Info("indexer stopped")
return result
}
func (ix *Indexer) Stopped() bool {
return ix.stopped.Load()
}
func (ix *Indexer) initFromConfig(ctx context.Context, cfg *config.Config) error {
if err := ix.initRPCClients(ctx, cfg.RPCs); err != nil {
return fmt.Errorf("failed to start RPC clients: %w", err)
}
if err := ix.initDB(ctx, cfg.DB); err != nil {
return fmt.Errorf("failed to init DB: %w", err)
}
if err := ix.initL1ETL(cfg.Chain); err != nil {
return fmt.Errorf("failed to init L1 ETL: %w", err)
}
if err := ix.initL2ETL(cfg.Chain); err != nil {
return fmt.Errorf("failed to init L2 ETL: %w", err)
}
if err := ix.initBridgeProcessor(cfg.Chain); err != nil {
return fmt.Errorf("failed to init Bridge-Processor: %w", err)
}
if err := ix.startHttpServer(ctx, cfg.HTTPServer); err != nil {
return fmt.Errorf("failed to start HTTP server: %w", err)
}
if err := ix.startMetricsServer(ctx, cfg.MetricsServer); err != nil {
return fmt.Errorf("failed to start Metrics server: %w", err)
}
return nil
}
func (ix *Indexer) initRPCClients(ctx context.Context, rpcsConfig config.RPCsConfig) error {
l1EthClient, err := node.DialEthClient(ctx, rpcsConfig.L1RPC, node.NewMetrics(ix.metricsRegistry, "l1"))
if err != nil {
return fmt.Errorf("failed to dial L1 client: %w", err)
}
ix.l1Client = l1EthClient
l2EthClient, err := node.DialEthClient(ctx, rpcsConfig.L2RPC, node.NewMetrics(ix.metricsRegistry, "l2"))
if err != nil { if err != nil {
return nil, err return fmt.Errorf("failed to dial L2 client: %w", err)
} }
ix.l2Client = l2EthClient
return nil
}
func (ix *Indexer) initDB(ctx context.Context, cfg config.DBConfig) error {
db, err := database.NewDB(ctx, ix.log, cfg)
if err != nil {
return fmt.Errorf("failed to connect to database: %w", err)
}
ix.DB = db
return nil
}
func (ix *Indexer) initL1ETL(chainConfig config.ChainConfig) error {
l1Cfg := etl.Config{ l1Cfg := etl.Config{
LoopIntervalMsec: chainConfig.L1PollingInterval, LoopIntervalMsec: chainConfig.L1PollingInterval,
HeaderBufferSize: chainConfig.L1HeaderBufferSize, HeaderBufferSize: chainConfig.L1HeaderBufferSize,
ConfirmationDepth: big.NewInt(int64(chainConfig.L1ConfirmationDepth)), ConfirmationDepth: big.NewInt(int64(chainConfig.L1ConfirmationDepth)),
StartHeight: big.NewInt(int64(chainConfig.L1StartingHeight)), StartHeight: big.NewInt(int64(chainConfig.L1StartingHeight)),
} }
l1Etl, err := etl.NewL1ETL(l1Cfg, log, db, etl.NewMetrics(metricsRegistry, "l1"), l1EthClient, chainConfig.L1Contracts) l1Etl, err := etl.NewL1ETL(l1Cfg, ix.log, ix.DB, etl.NewMetrics(ix.metricsRegistry, "l1"),
ix.l1Client, chainConfig.L1Contracts, ix.shutdown)
if err != nil { if err != nil {
return nil, err return err
} }
ix.L1ETL = l1Etl
return nil
}
func (ix *Indexer) initL2ETL(chainConfig config.ChainConfig) error {
// L2 (defaults to predeploy contracts) // L2 (defaults to predeploy contracts)
l2EthClient, err := node.DialEthClient(rpcsConfig.L2RPC, node.NewMetrics(metricsRegistry, "l2"))
if err != nil {
return nil, err
}
l2Cfg := etl.Config{ l2Cfg := etl.Config{
LoopIntervalMsec: chainConfig.L2PollingInterval, LoopIntervalMsec: chainConfig.L2PollingInterval,
HeaderBufferSize: chainConfig.L2HeaderBufferSize, HeaderBufferSize: chainConfig.L2HeaderBufferSize,
ConfirmationDepth: big.NewInt(int64(chainConfig.L2ConfirmationDepth)), ConfirmationDepth: big.NewInt(int64(chainConfig.L2ConfirmationDepth)),
} }
l2Etl, err := etl.NewL2ETL(l2Cfg, log, db, etl.NewMetrics(metricsRegistry, "l2"), l2EthClient, chainConfig.L2Contracts) l2Etl, err := etl.NewL2ETL(l2Cfg, ix.log, ix.DB, etl.NewMetrics(ix.metricsRegistry, "l2"),
ix.l2Client, chainConfig.L2Contracts, ix.shutdown)
if err != nil { if err != nil {
return nil, err return err
} }
ix.L2ETL = l2Etl
return nil
}
// Bridge func (ix *Indexer) initBridgeProcessor(chainConfig config.ChainConfig) error {
bridgeProcessor, err := processors.NewBridgeProcessor(log, db, bridge.NewMetrics(metricsRegistry), l1Etl, chainConfig) bridgeProcessor, err := processors.NewBridgeProcessor(
ix.log, ix.DB, bridge.NewMetrics(ix.metricsRegistry), ix.L1ETL, chainConfig, ix.shutdown)
if err != nil { if err != nil {
return nil, err return err
}
indexer := &Indexer{
log: log,
db: db,
httpConfig: httpConfig,
metricsConfig: metricsConfig,
metricsRegistry: metricsRegistry,
L1ETL: l1Etl,
L2ETL: l2Etl,
BridgeProcessor: bridgeProcessor,
} }
ix.BridgeProcessor = bridgeProcessor
return indexer, nil return nil
} }
func (i *Indexer) startHttpServer(ctx context.Context) error { func (ix *Indexer) startHttpServer(ctx context.Context, cfg config.ServerConfig) error {
i.log.Debug("starting http server...", "port", i.httpConfig.Host) ix.log.Debug("starting http server...", "port", cfg.Port)
r := chi.NewRouter() r := chi.NewRouter()
r.Use(middleware.Heartbeat("/healthz")) r.Use(middleware.Heartbeat("/healthz"))
addr := net.JoinHostPort(i.httpConfig.Host, strconv.Itoa(i.httpConfig.Port)) addr := net.JoinHostPort(cfg.Host, strconv.Itoa(cfg.Port))
srv, err := httputil.StartHTTPServer(addr, r) srv, err := httputil.StartHTTPServer(addr, r)
if err != nil { if err != nil {
return fmt.Errorf("http server failed to start: %w", err) return fmt.Errorf("http server failed to start: %w", err)
} }
i.log.Info("http server started", "addr", srv.Addr()) ix.apiServer = srv
<-ctx.Done() ix.log.Info("http server started", "addr", srv.Addr())
defer i.log.Info("http server stopped") return nil
return srv.Stop(context.Background())
} }
func (i *Indexer) startMetricsServer(ctx context.Context) error { func (ix *Indexer) startMetricsServer(ctx context.Context, cfg config.ServerConfig) error {
i.log.Debug("starting metrics server...", "port", i.metricsConfig.Port) ix.log.Debug("starting metrics server...", "port", cfg.Port)
srv, err := metrics.StartServer(i.metricsRegistry, i.metricsConfig.Host, i.metricsConfig.Port) srv, err := metrics.StartServer(ix.metricsRegistry, cfg.Host, cfg.Port)
if err != nil { if err != nil {
return fmt.Errorf("metrics server failed to start: %w", err) return fmt.Errorf("metrics server failed to start: %w", err)
} }
i.log.Info("metrics server started", "addr", srv.Addr()) ix.metricsServer = srv
<-ctx.Done() ix.log.Info("metrics server started", "addr", srv.Addr())
defer i.log.Info("metrics server stopped") return nil
return srv.Stop(context.Background())
}
// Start starts the indexing service on L1 and L2 chains
func (i *Indexer) Run(ctx context.Context) error {
var wg sync.WaitGroup
errCh := make(chan error, 5)
// if any goroutine halts, we stop the entire indexer
processCtx, processCancel := context.WithCancel(ctx)
runProcess := func(start func(ctx context.Context) error) {
wg.Add(1)
go func() {
defer func() {
if err := recover(); err != nil {
i.log.Error("halting indexer on panic", "err", err)
debug.PrintStack()
errCh <- fmt.Errorf("panic: %v", err)
}
processCancel()
wg.Done()
}()
errCh <- start(processCtx)
}()
}
// Kick off all the dependent routines
runProcess(i.L1ETL.Start)
runProcess(i.L2ETL.Start)
runProcess(i.BridgeProcessor.Start)
runProcess(i.startMetricsServer)
runProcess(i.startHttpServer)
wg.Wait()
err := <-errCh
if err != nil {
i.log.Error("indexer stopped", "err", err)
} else {
i.log.Info("indexer stopped")
}
return err
} }
...@@ -29,6 +29,7 @@ name = "$INDEXER_DB_NAME" ...@@ -29,6 +29,7 @@ name = "$INDEXER_DB_NAME"
[http] [http]
host = "127.0.0.1" host = "127.0.0.1"
port = 8080 port = 8080
timeout = 10
[metrics] [metrics]
host = "127.0.0.1" host = "127.0.0.1"
......
...@@ -40,23 +40,27 @@ type EthClient interface { ...@@ -40,23 +40,27 @@ type EthClient interface {
StorageHash(common.Address, *big.Int) (common.Hash, error) StorageHash(common.Address, *big.Int) (common.Hash, error)
FilterLogs(ethereum.FilterQuery) (Logs, error) FilterLogs(ethereum.FilterQuery) (Logs, error)
// Close closes the underlying RPC connection.
// RPC close does not return any errors, but does shut down e.g. a websocket connection.
Close()
} }
type clnt struct { type clnt struct {
rpc RPC rpc RPC
} }
func DialEthClient(rpcUrl string, metrics Metricer) (EthClient, error) { func DialEthClient(ctx context.Context, rpcUrl string, metrics Metricer) (EthClient, error) {
ctxwt, cancel := context.WithTimeout(context.Background(), defaultDialTimeout) ctx, cancel := context.WithTimeout(ctx, defaultDialTimeout)
defer cancel() defer cancel()
bOff := retry.Exponential() bOff := retry.Exponential()
rpcClient, err := retry.Do(ctxwt, defaultDialAttempts, bOff, func() (*rpc.Client, error) { rpcClient, err := retry.Do(ctx, defaultDialAttempts, bOff, func() (*rpc.Client, error) {
if !client.IsURLAvailable(rpcUrl) { if !client.IsURLAvailable(rpcUrl) {
return nil, fmt.Errorf("address unavailable (%s)", rpcUrl) return nil, fmt.Errorf("address unavailable (%s)", rpcUrl)
} }
client, err := rpc.DialContext(ctxwt, rpcUrl) client, err := rpc.DialContext(ctx, rpcUrl)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to dial address (%s): %w", rpcUrl, err) return nil, fmt.Errorf("failed to dial address (%s): %w", rpcUrl, err)
} }
...@@ -192,6 +196,10 @@ func (c *clnt) StorageHash(address common.Address, blockNumber *big.Int) (common ...@@ -192,6 +196,10 @@ func (c *clnt) StorageHash(address common.Address, blockNumber *big.Int) (common
return proof.StorageHash, nil return proof.StorageHash, nil
} }
func (c *clnt) Close() {
c.rpc.Close()
}
type Logs struct { type Logs struct {
Logs []types.Log Logs []types.Log
ToBlockHeader *types.Header ToBlockHeader *types.Header
......
package node package node
import ( import (
"context"
"fmt" "fmt"
"net" "net"
"strings" "strings"
...@@ -21,14 +22,14 @@ func TestDialEthClientUnavailable(t *testing.T) { ...@@ -21,14 +22,14 @@ func TestDialEthClientUnavailable(t *testing.T) {
metrics := &clientMetrics{} metrics := &clientMetrics{}
// available // available
_, err = DialEthClient(addr, metrics) _, err = DialEthClient(context.Background(), addr, metrics)
require.NoError(t, err) require.NoError(t, err)
// :0 requests a new unbound port // :0 requests a new unbound port
_, err = DialEthClient("http://localhost:0", metrics) _, err = DialEthClient(context.Background(), "http://localhost:0", metrics)
require.Error(t, err) require.Error(t, err)
// Fail open if we don't recognize the scheme // Fail open if we don't recognize the scheme
_, err = DialEthClient("mailto://example.com", metrics) _, err = DialEthClient(context.Background(), "mailto://example.com", metrics)
require.Error(t, err) require.Error(t, err)
} }
...@@ -45,3 +45,6 @@ func (m *MockEthClient) FilterLogs(query ethereum.FilterQuery) (Logs, error) { ...@@ -45,3 +45,6 @@ func (m *MockEthClient) FilterLogs(query ethereum.FilterQuery) (Logs, error) {
args := m.Called(query) args := m.Called(query)
return args.Get(0).(Logs), args.Error(1) return args.Get(0).(Logs), args.Error(1)
} }
func (m *MockEthClient) Close() {
}
...@@ -3,16 +3,18 @@ package processors ...@@ -3,16 +3,18 @@ package processors
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/indexer/bigint" "github.com/ethereum-optimism/optimism/indexer/bigint"
"github.com/ethereum-optimism/optimism/indexer/config" "github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database" "github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/etl" "github.com/ethereum-optimism/optimism/indexer/etl"
"github.com/ethereum-optimism/optimism/indexer/processors/bridge" "github.com/ethereum-optimism/optimism/indexer/processors/bridge"
"github.com/ethereum-optimism/optimism/op-service/tasks"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
) )
type BridgeProcessor struct { type BridgeProcessor struct {
...@@ -20,6 +22,10 @@ type BridgeProcessor struct { ...@@ -20,6 +22,10 @@ type BridgeProcessor struct {
db *database.DB db *database.DB
metrics bridge.Metricer metrics bridge.Metricer
resourceCtx context.Context
resourceCancel context.CancelFunc
tasks tasks.Group
l1Etl *etl.L1ETL l1Etl *etl.L1ETL
chainConfig config.ChainConfig chainConfig config.ChainConfig
...@@ -27,7 +33,8 @@ type BridgeProcessor struct { ...@@ -27,7 +33,8 @@ type BridgeProcessor struct {
LatestL2Header *types.Header LatestL2Header *types.Header
} }
func NewBridgeProcessor(log log.Logger, db *database.DB, metrics bridge.Metricer, l1Etl *etl.L1ETL, chainConfig config.ChainConfig) (*BridgeProcessor, error) { func NewBridgeProcessor(log log.Logger, db *database.DB, metrics bridge.Metricer, l1Etl *etl.L1ETL,
chainConfig config.ChainConfig, shutdown context.CancelCauseFunc) (*BridgeProcessor, error) {
log = log.New("processor", "bridge") log = log.New("processor", "bridge")
latestL1Header, err := db.BridgeTransactions.L1LatestBlockHeader() latestL1Header, err := db.BridgeTransactions.L1LatestBlockHeader()
...@@ -57,11 +64,25 @@ func NewBridgeProcessor(log log.Logger, db *database.DB, metrics bridge.Metricer ...@@ -57,11 +64,25 @@ func NewBridgeProcessor(log log.Logger, db *database.DB, metrics bridge.Metricer
log.Info("detected latest indexed bridge state", "l1_block_number", l1Height, "l2_block_number", l2Height) log.Info("detected latest indexed bridge state", "l1_block_number", l1Height, "l2_block_number", l2Height)
} }
return &BridgeProcessor{log, db, metrics, l1Etl, chainConfig, l1Header, l2Header}, nil resCtx, resCancel := context.WithCancel(context.Background())
return &BridgeProcessor{
log: log,
db: db,
metrics: metrics,
l1Etl: l1Etl,
resourceCtx: resCtx,
resourceCancel: resCancel,
chainConfig: chainConfig,
LatestL1Header: l1Header,
LatestL2Header: l2Header,
tasks: tasks.Group{HandleCrit: func(err error) {
shutdown(fmt.Errorf("critical error in bridge processor: %w", err))
}},
}, nil
} }
func (b *BridgeProcessor) Start(ctx context.Context) error { func (b *BridgeProcessor) Start() error {
done := ctx.Done() b.log.Info("starting bridge processor...")
// Fire off independently on startup to check for // Fire off independently on startup to check for
// new data or if we've indexed new L1 data. // new data or if we've indexed new L1 data.
...@@ -69,10 +90,10 @@ func (b *BridgeProcessor) Start(ctx context.Context) error { ...@@ -69,10 +90,10 @@ func (b *BridgeProcessor) Start(ctx context.Context) error {
startup := make(chan interface{}, 1) startup := make(chan interface{}, 1)
startup <- nil startup <- nil
b.log.Info("starting bridge processor...") b.tasks.Go(func() error {
for { for {
select { select {
case <-done: case <-b.resourceCtx.Done():
b.log.Info("stopping bridge processor") b.log.Info("stopping bridge processor")
return nil return nil
...@@ -82,8 +103,22 @@ func (b *BridgeProcessor) Start(ctx context.Context) error { ...@@ -82,8 +103,22 @@ func (b *BridgeProcessor) Start(ctx context.Context) error {
} }
done := b.metrics.RecordInterval() done := b.metrics.RecordInterval()
done(b.run()) // TODO(8013): why log all the errors and return the same thing, if we just return the error, and log here?
err := b.run()
if err != nil {
b.log.Error("bridge processor error", "err", err)
}
done(err)
} }
})
return nil
}
func (b *BridgeProcessor) Close() error {
// signal that we can stop any ongoing work
b.resourceCancel()
// await the work to stop
return b.tasks.Wait()
} }
// Runs the processing loop. In order to ensure all seen bridge finalization events // Runs the processing loop. In order to ensure all seen bridge finalization events
......
package main package main
import ( import (
"context"
"os" "os"
opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-batcher/batcher" "github.com/ethereum-optimism/optimism/op-batcher/batcher"
"github.com/ethereum-optimism/optimism/op-batcher/flags" "github.com/ethereum-optimism/optimism/op-batcher/flags"
"github.com/ethereum-optimism/optimism/op-batcher/metrics" "github.com/ethereum-optimism/optimism/op-batcher/metrics"
opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/ethereum-optimism/optimism/op-service/cliapp" "github.com/ethereum-optimism/optimism/op-service/cliapp"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/metrics/doc" "github.com/ethereum-optimism/optimism/op-service/metrics/doc"
"github.com/ethereum-optimism/optimism/op-service/opio"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
...@@ -38,7 +40,8 @@ func main() { ...@@ -38,7 +40,8 @@ func main() {
}, },
} }
err := app.Run(os.Args) ctx := opio.WithInterruptBlocker(context.Background())
err := app.RunContext(ctx, os.Args)
if err != nil { if err != nil {
log.Crit("Application failed", "message", err) log.Crit("Application failed", "message", err)
} }
......
...@@ -15,7 +15,7 @@ var MIPSStorageLayout = new(solc.StorageLayout) ...@@ -15,7 +15,7 @@ var MIPSStorageLayout = new(solc.StorageLayout)
var MIPSDeployedBin = "0x608060405234801561001057600080fd5b50600436106100415760003560e01c8063155633fe146100465780637dc0d1d01461006b578063836e7b32146100af575b600080fd5b610051634000000081565b60405163ffffffff90911681526020015b60405180910390f35b60405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152602001610062565b6100c26100bd366004611d2e565b6100d0565b604051908152602001610062565b60006100da611c5b565b608081146100e757600080fd5b604051610600146100f757600080fd5b6084871461010457600080fd5b6101a4851461011257600080fd5b8635608052602087013560a052604087013560e090811c60c09081526044890135821c82526048890135821c61010052604c890135821c610120526050890135821c61014052605489013590911c61016052605888013560f890811c610180526059890135901c6101a052605a880135901c6101c0526102006101e0819052606288019060005b60208110156101bd57823560e01c8252600490920191602090910190600101610199565b505050806101200151156101db576101d361061b565b915050610612565b6101408101805160010167ffffffffffffffff16905260608101516000906102039082610737565b9050603f601a82901c16600281148061022257508063ffffffff166003145b156102775760006002836303ffffff1663ffffffff16901b846080015163f00000001617905061026c8263ffffffff1660021461026057601f610263565b60005b60ff16826107f3565b945050505050610612565b6101608301516000908190601f601086901c81169190601587901c16602081106102a3576102a3611da2565b602002015192508063ffffffff851615806102c457508463ffffffff16601c145b156102fb578661016001518263ffffffff16602081106102e6576102e6611da2565b6020020151925050601f600b86901c166103b7565b60208563ffffffff16101561035d578463ffffffff16600c148061032557508463ffffffff16600d145b8061033657508463ffffffff16600e145b15610347578561ffff1692506103b7565b6103568661ffff1660106108e4565b92506103b7565b60288563ffffffff1610158061037957508463ffffffff166022145b8061038a57508463ffffffff166026145b156103b7578661016001518263ffffffff16602081106103ac576103ac611da2565b602002015192508190505b60048563ffffffff16101580156103d4575060088563ffffffff16105b806103e557508463ffffffff166001145b15610404576103f685878487610957565b975050505050505050610612565b63ffffffff6000602087831610610469576104248861ffff1660106108e4565b9095019463fffffffc861661043a816001610737565b915060288863ffffffff161015801561045a57508763ffffffff16603014155b1561046757809250600093505b505b600061047789888885610b67565b63ffffffff9081169150603f8a1690891615801561049c575060088163ffffffff1610155b80156104ae5750601c8163ffffffff16105b1561058b578063ffffffff16600814806104ce57508063ffffffff166009145b15610505576104f38163ffffffff166008146104ea57856104ed565b60005b896107f3565b9b505050505050505050505050610612565b8063ffffffff16600a03610525576104f3858963ffffffff8a16156112f7565b8063ffffffff16600b03610546576104f3858963ffffffff8a1615156112f7565b8063ffffffff16600c0361055d576104f38d6113dd565b60108163ffffffff161015801561057a5750601c8163ffffffff16105b1561058b576104f381898988611914565b8863ffffffff1660381480156105a6575063ffffffff861615155b156105db5760018b61016001518763ffffffff16602081106105ca576105ca611da2565b63ffffffff90921660209290920201525b8363ffffffff1663ffffffff146105f8576105f884600184611b0e565b610604858360016112f7565b9b5050505050505050505050505b95945050505050565b60408051608051815260a051602082015260dc519181019190915260fc51604482015261011c51604882015261013c51604c82015261015c51605082015261017c5160548201526101805161019f5160588301526101a0516101bf5160598401526101d851605a840152600092610200929091606283019190855b60208110156106ba57601c8601518452602090950194600490930192600101610696565b506000835283830384a06000945080600181146106da5760039550610702565b8280156106f257600181146106fb5760029650610700565b60009650610700565b600196505b505b50505081900390207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660f89190911b17919050565b60008061074383611bb2565b9050600384161561075357600080fd5b6020810190358460051c8160005b601b8110156107b95760208501943583821c6001168015610789576001811461079e576107af565b600084815260208390526040902093506107af565b600082815260208590526040902093505b5050600101610761565b5060805191508181146107d457630badf00d60005260206000fd5b5050601f94909416601c0360031b9390931c63ffffffff169392505050565b60006107fd611c5b565b60809050806060015160040163ffffffff16816080015163ffffffff1614610886576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f6a756d7020696e2064656c617920736c6f74000000000000000000000000000060448201526064015b60405180910390fd5b60608101805160808301805163ffffffff9081169093528583169052908516156108dc57806008018261016001518663ffffffff16602081106108cb576108cb611da2565b63ffffffff90921660209290920201525b61061261061b565b600063ffffffff8381167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80850183169190911c821615159160016020869003821681901b830191861691821b92911b0182610941576000610943565b815b90861663ffffffff16179250505092915050565b6000610961611c5b565b608090506000816060015160040163ffffffff16826080015163ffffffff16146109e7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f6272616e636820696e2064656c617920736c6f74000000000000000000000000604482015260640161087d565b8663ffffffff1660041480610a0257508663ffffffff166005145b15610a7e5760008261016001518663ffffffff1660208110610a2657610a26611da2565b602002015190508063ffffffff168563ffffffff16148015610a4e57508763ffffffff166004145b80610a7657508063ffffffff168563ffffffff1614158015610a7657508763ffffffff166005145b915050610afb565b8663ffffffff16600603610a9b5760008460030b13159050610afb565b8663ffffffff16600703610ab75760008460030b139050610afb565b8663ffffffff16600103610afb57601f601087901c166000819003610ae05760008560030b1291505b8063ffffffff16600103610af95760008560030b121591505b505b606082018051608084015163ffffffff169091528115610b41576002610b268861ffff1660106108e4565b63ffffffff90811690911b8201600401166080840152610b53565b60808301805160040163ffffffff1690525b610b5b61061b565b98975050505050505050565b6000603f601a86901c16801580610b96575060088163ffffffff1610158015610b965750600f8163ffffffff16105b15610fec57603f86168160088114610bdd5760098114610be657600a8114610bef57600b8114610bf857600c8114610c0157600d8114610c0a57600e8114610c1357610c18565b60209150610c18565b60219150610c18565b602a9150610c18565b602b9150610c18565b60249150610c18565b60259150610c18565b602691505b508063ffffffff16600003610c3f5750505063ffffffff8216601f600686901c161b6112ef565b8063ffffffff16600203610c655750505063ffffffff8216601f600686901c161c6112ef565b8063ffffffff16600303610c9b57601f600688901c16610c9163ffffffff8716821c60208390036108e4565b93505050506112ef565b8063ffffffff16600403610cbd5750505063ffffffff8216601f84161b6112ef565b8063ffffffff16600603610cdf5750505063ffffffff8216601f84161c6112ef565b8063ffffffff16600703610d1257610d098663ffffffff168663ffffffff16901c876020036108e4565b925050506112ef565b8063ffffffff16600803610d2a5785925050506112ef565b8063ffffffff16600903610d425785925050506112ef565b8063ffffffff16600a03610d5a5785925050506112ef565b8063ffffffff16600b03610d725785925050506112ef565b8063ffffffff16600c03610d8a5785925050506112ef565b8063ffffffff16600f03610da25785925050506112ef565b8063ffffffff16601003610dba5785925050506112ef565b8063ffffffff16601103610dd25785925050506112ef565b8063ffffffff16601203610dea5785925050506112ef565b8063ffffffff16601303610e025785925050506112ef565b8063ffffffff16601803610e1a5785925050506112ef565b8063ffffffff16601903610e325785925050506112ef565b8063ffffffff16601a03610e4a5785925050506112ef565b8063ffffffff16601b03610e625785925050506112ef565b8063ffffffff16602003610e7b575050508282016112ef565b8063ffffffff16602103610e94575050508282016112ef565b8063ffffffff16602203610ead575050508183036112ef565b8063ffffffff16602303610ec6575050508183036112ef565b8063ffffffff16602403610edf575050508282166112ef565b8063ffffffff16602503610ef8575050508282176112ef565b8063ffffffff16602603610f11575050508282186112ef565b8063ffffffff16602703610f2b57505050828217196112ef565b8063ffffffff16602a03610f5c578460030b8660030b12610f4d576000610f50565b60015b60ff16925050506112ef565b8063ffffffff16602b03610f84578463ffffffff168663ffffffff1610610f4d576000610f50565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f696e76616c696420696e737472756374696f6e00000000000000000000000000604482015260640161087d565b50610f84565b8063ffffffff16601c0361107057603f86166002819003611012575050508282026112ef565b8063ffffffff166020148061102d57508063ffffffff166021145b15610fe6578063ffffffff16602003611044579419945b60005b6380000000871615611066576401fffffffe600197881b169601611047565b92506112ef915050565b8063ffffffff16600f0361109257505065ffffffff0000601083901b166112ef565b8063ffffffff166020036110ce576110c68560031660080260180363ffffffff168463ffffffff16901c60ff1660086108e4565b9150506112ef565b8063ffffffff16602103611103576110c68560021660080260100363ffffffff168463ffffffff16901c61ffff1660106108e4565b8063ffffffff1660220361113257505063ffffffff60086003851602811681811b198416918316901b176112ef565b8063ffffffff1660230361114957829150506112ef565b8063ffffffff1660240361117b578460031660080260180363ffffffff168363ffffffff16901c60ff169150506112ef565b8063ffffffff166025036111ae578460021660080260100363ffffffff168363ffffffff16901c61ffff169150506112ef565b8063ffffffff166026036111e057505063ffffffff60086003851602601803811681811c198416918316901c176112ef565b8063ffffffff1660280361121657505060ff63ffffffff60086003861602601803811682811b9091188316918416901b176112ef565b8063ffffffff1660290361124d57505061ffff63ffffffff60086002861602601003811682811b9091188316918416901b176112ef565b8063ffffffff16602a0361127c57505063ffffffff60086003851602811681811c198316918416901c176112ef565b8063ffffffff16602b0361129357839150506112ef565b8063ffffffff16602e036112c557505063ffffffff60086003851602601803811681811b198316918416901b176112ef565b8063ffffffff166030036112dc57829150506112ef565b8063ffffffff16603803610f8457839150505b949350505050565b6000611301611c5b565b506080602063ffffffff861610611374576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f76616c6964207265676973746572000000000000000000000000000000000000604482015260640161087d565b63ffffffff8516158015906113865750825b156113ba57838161016001518663ffffffff16602081106113a9576113a9611da2565b63ffffffff90921660209290920201525b60808101805163ffffffff8082166060850152600490910116905261061261061b565b60006113e7611c5b565b506101e051604081015160808083015160a084015160c09094015191936000928392919063ffffffff8616610ffa036114615781610fff81161561143057610fff811661100003015b8363ffffffff166000036114575760e08801805163ffffffff83820116909152955061145b565b8395505b506118d3565b8563ffffffff16610fcd0361147c57634000000094506118d3565b8563ffffffff166110180361149457600194506118d3565b8563ffffffff16611096036114ca57600161012088015260ff83166101008801526114bd61061b565b9998505050505050505050565b8563ffffffff16610fa3036117365763ffffffff8316156118d3577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb63ffffffff8416016116f05760006115258363fffffffc166001610737565b60208901519091508060001a60010361159457604080516000838152336020528d83526060902091527effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01000000000000000000000000000000000000000000000000000000000000001790505b6040808a015190517fe03110e10000000000000000000000000000000000000000000000000000000081526004810183905263ffffffff9091166024820152600090819073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000169063e03110e1906044016040805180830381865afa158015611635573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116599190611dd1565b91509150600386168060040382811015611671578092505b508186101561167e578591505b8260088302610100031c9250826008828460040303021b9250600180600883600403021b036001806008858560040303021b039150811981169050838119871617955050506116d58663fffffffc16600186611b0e565b60408b018051820163ffffffff169052975061173192505050565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd63ffffffff841601611725578094506118d3565b63ffffffff9450600993505b6118d3565b8563ffffffff16610fa4036118275763ffffffff831660011480611760575063ffffffff83166002145b80611771575063ffffffff83166004145b1561177e578094506118d3565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa63ffffffff8416016117255760006117be8363fffffffc166001610737565b602089015190915060038416600403838110156117d9578093505b83900360089081029290921c7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600193850293841b0116911b176020880152600060408801529350836118d3565b8563ffffffff16610fd7036118d3578163ffffffff166003036118c75763ffffffff8316158061185d575063ffffffff83166005145b8061186e575063ffffffff83166003145b1561187c57600094506118d3565b63ffffffff831660011480611897575063ffffffff83166002145b806118a8575063ffffffff83166006145b806118b9575063ffffffff83166004145b1561172557600194506118d3565b63ffffffff9450601693505b6101608701805163ffffffff808816604090920191909152905185821660e09091015260808801805180831660608b015260040190911690526114bd61061b565b600061191e611c5b565b506080600063ffffffff871660100361193c575060c0810151611aa5565b8663ffffffff1660110361195b5763ffffffff861660c0830152611aa5565b8663ffffffff16601203611974575060a0810151611aa5565b8663ffffffff166013036119935763ffffffff861660a0830152611aa5565b8663ffffffff166018036119c75763ffffffff600387810b9087900b02602081901c821660c08501521660a0830152611aa5565b8663ffffffff166019036119f85763ffffffff86811681871602602081901c821660c08501521660a0830152611aa5565b8663ffffffff16601a03611a4e578460030b8660030b81611a1b57611a1b611df5565b0763ffffffff1660c0830152600385810b9087900b81611a3d57611a3d611df5565b0563ffffffff1660a0830152611aa5565b8663ffffffff16601b03611aa5578463ffffffff168663ffffffff1681611a7757611a77611df5565b0663ffffffff90811660c084015285811690871681611a9857611a98611df5565b0463ffffffff1660a08301525b63ffffffff841615611ae057808261016001518563ffffffff1660208110611acf57611acf611da2565b63ffffffff90921660209290920201525b60808201805163ffffffff80821660608601526004909101169052611b0361061b565b979650505050505050565b6000611b1983611bb2565b90506003841615611b2957600080fd5b6020810190601f8516601c0360031b83811b913563ffffffff90911b1916178460051c60005b601b811015611ba75760208401933582821c6001168015611b775760018114611b8c57611b9d565b60008581526020839052604090209450611b9d565b600082815260208690526040902094505b5050600101611b4f565b505060805250505050565b60ff8116610380026101a4810190369061052401811015611c55576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f636865636b207468617420746865726520697320656e6f7567682063616c6c6460448201527f6174610000000000000000000000000000000000000000000000000000000000606482015260840161087d565b50919050565b6040805161018081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e08101829052610100810182905261012081018290526101408101919091526101608101611cc1611cc6565b905290565b6040518061040001604052806020906020820280368337509192915050565b60008083601f840112611cf757600080fd5b50813567ffffffffffffffff811115611d0f57600080fd5b602083019150836020828501011115611d2757600080fd5b9250929050565b600080600080600060608688031215611d4657600080fd5b853567ffffffffffffffff80821115611d5e57600080fd5b611d6a89838a01611ce5565b90975095506020880135915080821115611d8357600080fd5b50611d9088828901611ce5565b96999598509660400135949350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60008060408385031215611de457600080fd5b505080516020909101519092909150565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fdfea164736f6c634300080f000a" var MIPSDeployedBin = "0x608060405234801561001057600080fd5b50600436106100415760003560e01c8063155633fe146100465780637dc0d1d01461006b578063836e7b32146100af575b600080fd5b610051634000000081565b60405163ffffffff90911681526020015b60405180910390f35b60405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152602001610062565b6100c26100bd366004611d2e565b6100d0565b604051908152602001610062565b60006100da611c5b565b608081146100e757600080fd5b604051610600146100f757600080fd5b6084871461010457600080fd5b6101a4851461011257600080fd5b8635608052602087013560a052604087013560e090811c60c09081526044890135821c82526048890135821c61010052604c890135821c610120526050890135821c61014052605489013590911c61016052605888013560f890811c610180526059890135901c6101a052605a880135901c6101c0526102006101e0819052606288019060005b60208110156101bd57823560e01c8252600490920191602090910190600101610199565b505050806101200151156101db576101d361061b565b915050610612565b6101408101805160010167ffffffffffffffff16905260608101516000906102039082610737565b9050603f601a82901c16600281148061022257508063ffffffff166003145b156102775760006002836303ffffff1663ffffffff16901b846080015163f00000001617905061026c8263ffffffff1660021461026057601f610263565b60005b60ff16826107f3565b945050505050610612565b6101608301516000908190601f601086901c81169190601587901c16602081106102a3576102a3611da2565b602002015192508063ffffffff851615806102c457508463ffffffff16601c145b156102fb578661016001518263ffffffff16602081106102e6576102e6611da2565b6020020151925050601f600b86901c166103b7565b60208563ffffffff16101561035d578463ffffffff16600c148061032557508463ffffffff16600d145b8061033657508463ffffffff16600e145b15610347578561ffff1692506103b7565b6103568661ffff1660106108e4565b92506103b7565b60288563ffffffff1610158061037957508463ffffffff166022145b8061038a57508463ffffffff166026145b156103b7578661016001518263ffffffff16602081106103ac576103ac611da2565b602002015192508190505b60048563ffffffff16101580156103d4575060088563ffffffff16105b806103e557508463ffffffff166001145b15610404576103f685878487610957565b975050505050505050610612565b63ffffffff6000602087831610610469576104248861ffff1660106108e4565b9095019463fffffffc861661043a816001610737565b915060288863ffffffff161015801561045a57508763ffffffff16603014155b1561046757809250600093505b505b600061047789888885610b67565b63ffffffff9081169150603f8a1690891615801561049c575060088163ffffffff1610155b80156104ae5750601c8163ffffffff16105b1561058b578063ffffffff16600814806104ce57508063ffffffff166009145b15610505576104f38163ffffffff166008146104ea57856104ed565b60005b896107f3565b9b505050505050505050505050610612565b8063ffffffff16600a03610525576104f3858963ffffffff8a16156112f7565b8063ffffffff16600b03610546576104f3858963ffffffff8a1615156112f7565b8063ffffffff16600c0361055d576104f38d6113dd565b60108163ffffffff161015801561057a5750601c8163ffffffff16105b1561058b576104f381898988611914565b8863ffffffff1660381480156105a6575063ffffffff861615155b156105db5760018b61016001518763ffffffff16602081106105ca576105ca611da2565b63ffffffff90921660209290920201525b8363ffffffff1663ffffffff146105f8576105f884600184611b0e565b610604858360016112f7565b9b5050505050505050505050505b95945050505050565b60408051608051815260a051602082015260dc519181019190915260fc51604482015261011c51604882015261013c51604c82015261015c51605082015261017c5160548201526101805161019f5160588301526101a0516101bf5160598401526101d851605a840152600092610200929091606283019190855b60208110156106ba57601c8601518452602090950194600490930192600101610696565b506000835283830384a06000945080600181146106da5760039550610702565b8280156106f257600181146106fb5760029650610700565b60009650610700565b600196505b505b50505081900390207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660f89190911b17919050565b60008061074383611bb2565b9050600384161561075357600080fd5b6020810190358460051c8160005b601b8110156107b95760208501943583821c6001168015610789576001811461079e576107af565b600084815260208390526040902093506107af565b600082815260208590526040902093505b5050600101610761565b5060805191508181146107d457630badf00d60005260206000fd5b5050601f94909416601c0360031b9390931c63ffffffff169392505050565b60006107fd611c5b565b60809050806060015160040163ffffffff16816080015163ffffffff1614610886576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f6a756d7020696e2064656c617920736c6f74000000000000000000000000000060448201526064015b60405180910390fd5b60608101805160808301805163ffffffff9081169093528583169052908516156108dc57806008018261016001518663ffffffff16602081106108cb576108cb611da2565b63ffffffff90921660209290920201525b61061261061b565b600063ffffffff8381167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80850183169190911c821615159160016020869003821681901b830191861691821b92911b0182610941576000610943565b815b90861663ffffffff16179250505092915050565b6000610961611c5b565b608090506000816060015160040163ffffffff16826080015163ffffffff16146109e7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f6272616e636820696e2064656c617920736c6f74000000000000000000000000604482015260640161087d565b8663ffffffff1660041480610a0257508663ffffffff166005145b15610a7e5760008261016001518663ffffffff1660208110610a2657610a26611da2565b602002015190508063ffffffff168563ffffffff16148015610a4e57508763ffffffff166004145b80610a7657508063ffffffff168563ffffffff1614158015610a7657508763ffffffff166005145b915050610afb565b8663ffffffff16600603610a9b5760008460030b13159050610afb565b8663ffffffff16600703610ab75760008460030b139050610afb565b8663ffffffff16600103610afb57601f601087901c166000819003610ae05760008560030b1291505b8063ffffffff16600103610af95760008560030b121591505b505b606082018051608084015163ffffffff169091528115610b41576002610b268861ffff1660106108e4565b63ffffffff90811690911b8201600401166080840152610b53565b60808301805160040163ffffffff1690525b610b5b61061b565b98975050505050505050565b6000603f601a86901c16801580610b96575060088163ffffffff1610158015610b965750600f8163ffffffff16105b15610fec57603f86168160088114610bdd5760098114610be657600a8114610bef57600b8114610bf857600c8114610c0157600d8114610c0a57600e8114610c1357610c18565b60209150610c18565b60219150610c18565b602a9150610c18565b602b9150610c18565b60249150610c18565b60259150610c18565b602691505b508063ffffffff16600003610c3f5750505063ffffffff8216601f600686901c161b6112ef565b8063ffffffff16600203610c655750505063ffffffff8216601f600686901c161c6112ef565b8063ffffffff16600303610c9b57601f600688901c16610c9163ffffffff8716821c60208390036108e4565b93505050506112ef565b8063ffffffff16600403610cbd5750505063ffffffff8216601f84161b6112ef565b8063ffffffff16600603610cdf5750505063ffffffff8216601f84161c6112ef565b8063ffffffff16600703610d1257610d098663ffffffff168663ffffffff16901c876020036108e4565b925050506112ef565b8063ffffffff16600803610d2a5785925050506112ef565b8063ffffffff16600903610d425785925050506112ef565b8063ffffffff16600a03610d5a5785925050506112ef565b8063ffffffff16600b03610d725785925050506112ef565b8063ffffffff16600c03610d8a5785925050506112ef565b8063ffffffff16600f03610da25785925050506112ef565b8063ffffffff16601003610dba5785925050506112ef565b8063ffffffff16601103610dd25785925050506112ef565b8063ffffffff16601203610dea5785925050506112ef565b8063ffffffff16601303610e025785925050506112ef565b8063ffffffff16601803610e1a5785925050506112ef565b8063ffffffff16601903610e325785925050506112ef565b8063ffffffff16601a03610e4a5785925050506112ef565b8063ffffffff16601b03610e625785925050506112ef565b8063ffffffff16602003610e7b575050508282016112ef565b8063ffffffff16602103610e94575050508282016112ef565b8063ffffffff16602203610ead575050508183036112ef565b8063ffffffff16602303610ec6575050508183036112ef565b8063ffffffff16602403610edf575050508282166112ef565b8063ffffffff16602503610ef8575050508282176112ef565b8063ffffffff16602603610f11575050508282186112ef565b8063ffffffff16602703610f2b57505050828217196112ef565b8063ffffffff16602a03610f5c578460030b8660030b12610f4d576000610f50565b60015b60ff16925050506112ef565b8063ffffffff16602b03610f84578463ffffffff168663ffffffff1610610f4d576000610f50565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f696e76616c696420696e737472756374696f6e00000000000000000000000000604482015260640161087d565b50610f84565b8063ffffffff16601c0361107057603f86166002819003611012575050508282026112ef565b8063ffffffff166020148061102d57508063ffffffff166021145b15610fe6578063ffffffff16602003611044579419945b60005b6380000000871615611066576401fffffffe600197881b169601611047565b92506112ef915050565b8063ffffffff16600f0361109257505065ffffffff0000601083901b166112ef565b8063ffffffff166020036110ce576110c68560031660080260180363ffffffff168463ffffffff16901c60ff1660086108e4565b9150506112ef565b8063ffffffff16602103611103576110c68560021660080260100363ffffffff168463ffffffff16901c61ffff1660106108e4565b8063ffffffff1660220361113257505063ffffffff60086003851602811681811b198416918316901b176112ef565b8063ffffffff1660230361114957829150506112ef565b8063ffffffff1660240361117b578460031660080260180363ffffffff168363ffffffff16901c60ff169150506112ef565b8063ffffffff166025036111ae578460021660080260100363ffffffff168363ffffffff16901c61ffff169150506112ef565b8063ffffffff166026036111e057505063ffffffff60086003851602601803811681811c198416918316901c176112ef565b8063ffffffff1660280361121657505060ff63ffffffff60086003861602601803811682811b9091188316918416901b176112ef565b8063ffffffff1660290361124d57505061ffff63ffffffff60086002861602601003811682811b9091188316918416901b176112ef565b8063ffffffff16602a0361127c57505063ffffffff60086003851602811681811c198316918416901c176112ef565b8063ffffffff16602b0361129357839150506112ef565b8063ffffffff16602e036112c557505063ffffffff60086003851602601803811681811b198316918416901b176112ef565b8063ffffffff166030036112dc57829150506112ef565b8063ffffffff16603803610f8457839150505b949350505050565b6000611301611c5b565b506080602063ffffffff861610611374576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f76616c6964207265676973746572000000000000000000000000000000000000604482015260640161087d565b63ffffffff8516158015906113865750825b156113ba57838161016001518663ffffffff16602081106113a9576113a9611da2565b63ffffffff90921660209290920201525b60808101805163ffffffff8082166060850152600490910116905261061261061b565b60006113e7611c5b565b506101e051604081015160808083015160a084015160c09094015191936000928392919063ffffffff8616610ffa036114615781610fff81161561143057610fff811661100003015b8363ffffffff166000036114575760e08801805163ffffffff83820116909152955061145b565b8395505b506118d3565b8563ffffffff16610fcd0361147c57634000000094506118d3565b8563ffffffff166110180361149457600194506118d3565b8563ffffffff16611096036114ca57600161012088015260ff83166101008801526114bd61061b565b9998505050505050505050565b8563ffffffff16610fa3036117365763ffffffff8316156118d3577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb63ffffffff8416016116f05760006115258363fffffffc166001610737565b60208901519091508060001a60010361159457604080516000838152336020528d83526060902091527effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01000000000000000000000000000000000000000000000000000000000000001790505b6040808a015190517fe03110e10000000000000000000000000000000000000000000000000000000081526004810183905263ffffffff9091166024820152600090819073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000169063e03110e1906044016040805180830381865afa158015611635573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116599190611dd1565b91509150600386168060040382811015611671578092505b508186101561167e578591505b8260088302610100031c9250826008828460040303021b9250600180600883600403021b036001806008858560040303021b039150811981169050838119871617955050506116d58663fffffffc16600186611b0e565b60408b018051820163ffffffff169052975061173192505050565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd63ffffffff841601611725578094506118d3565b63ffffffff9450600993505b6118d3565b8563ffffffff16610fa4036118275763ffffffff831660011480611760575063ffffffff83166002145b80611771575063ffffffff83166004145b1561177e578094506118d3565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa63ffffffff8416016117255760006117be8363fffffffc166001610737565b602089015190915060038416600403838110156117d9578093505b83900360089081029290921c7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600193850293841b0116911b176020880152600060408801529350836118d3565b8563ffffffff16610fd7036118d3578163ffffffff166003036118c75763ffffffff8316158061185d575063ffffffff83166005145b8061186e575063ffffffff83166003145b1561187c57600094506118d3565b63ffffffff831660011480611897575063ffffffff83166002145b806118a8575063ffffffff83166006145b806118b9575063ffffffff83166004145b1561172557600194506118d3565b63ffffffff9450601693505b6101608701805163ffffffff808816604090920191909152905185821660e09091015260808801805180831660608b015260040190911690526114bd61061b565b600061191e611c5b565b506080600063ffffffff871660100361193c575060c0810151611aa5565b8663ffffffff1660110361195b5763ffffffff861660c0830152611aa5565b8663ffffffff16601203611974575060a0810151611aa5565b8663ffffffff166013036119935763ffffffff861660a0830152611aa5565b8663ffffffff166018036119c75763ffffffff600387810b9087900b02602081901c821660c08501521660a0830152611aa5565b8663ffffffff166019036119f85763ffffffff86811681871602602081901c821660c08501521660a0830152611aa5565b8663ffffffff16601a03611a4e578460030b8660030b81611a1b57611a1b611df5565b0763ffffffff1660c0830152600385810b9087900b81611a3d57611a3d611df5565b0563ffffffff1660a0830152611aa5565b8663ffffffff16601b03611aa5578463ffffffff168663ffffffff1681611a7757611a77611df5565b0663ffffffff90811660c084015285811690871681611a9857611a98611df5565b0463ffffffff1660a08301525b63ffffffff841615611ae057808261016001518563ffffffff1660208110611acf57611acf611da2565b63ffffffff90921660209290920201525b60808201805163ffffffff80821660608601526004909101169052611b0361061b565b979650505050505050565b6000611b1983611bb2565b90506003841615611b2957600080fd5b6020810190601f8516601c0360031b83811b913563ffffffff90911b1916178460051c60005b601b811015611ba75760208401933582821c6001168015611b775760018114611b8c57611b9d565b60008581526020839052604090209450611b9d565b600082815260208690526040902094505b5050600101611b4f565b505060805250505050565b60ff8116610380026101a4810190369061052401811015611c55576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f636865636b207468617420746865726520697320656e6f7567682063616c6c6460448201527f6174610000000000000000000000000000000000000000000000000000000000606482015260840161087d565b50919050565b6040805161018081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e08101829052610100810182905261012081018290526101408101919091526101608101611cc1611cc6565b905290565b6040518061040001604052806020906020820280368337509192915050565b60008083601f840112611cf757600080fd5b50813567ffffffffffffffff811115611d0f57600080fd5b602083019150836020828501011115611d2757600080fd5b9250929050565b600080600080600060608688031215611d4657600080fd5b853567ffffffffffffffff80821115611d5e57600080fd5b611d6a89838a01611ce5565b90975095506020880135915080821115611d8357600080fd5b50611d9088828901611ce5565b96999598509660400135949350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60008060408385031215611de457600080fd5b505080516020909101519092909150565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fdfea164736f6c634300080f000a"
var MIPSDeployedSourceMap = "1131:40054:135:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1710:45;;1745:10;1710:45;;;;;188:10:304;176:23;;;158:42;;146:2;131:18;1710:45:135;;;;;;;;2448:99;;;412:42:304;2534:6:135;400:55:304;382:74;;370:2;355:18;2448:99:135;211:251:304;26025:6379:135;;;;;;:::i;:::-;;:::i;:::-;;;1755:25:304;;;1743:2;1728:18;26025:6379:135;1609:177:304;26025:6379:135;26128:7;26171:18;;:::i;:::-;26318:4;26311:5;26308:15;26298:134;;26412:1;26409;26402:12;26298:134;26468:4;26462:11;26475:10;26459:27;26449:136;;26565:1;26562;26555:12;26449:136;26634:3;26615:17;26612:26;26602:151;;26733:1;26730;26723:12;26602:151;26798:3;26783:13;26780:22;26770:146;;26896:1;26893;26886:12;26770:146;27176:24;;27521:4;27222:20;27579:2;27280:21;;27176:24;27338:18;27222:20;27280:21;;;27176:24;27153:21;27149:52;;;27338:18;27222:20;;;27280:21;;;27176:24;27149:52;;27222:20;;27280:21;;;27176:24;27149:52;;27338:18;27222:20;27280:21;;;27176:24;27149:52;;27338:18;27222:20;27280:21;;;27176:24;27149:52;;27338:18;27222:20;27280:21;;;27176:24;27149:52;;;27338:18;27222:20;27280:21;;;27176:24;27153:21;27149:52;;;27338:18;27222:20;27280:21;;;27176:24;27149:52;;27338:18;27222:20;27280:21;;;27176:24;27149:52;;27338:18;27222:20;28197:10;27338:18;28187:21;;;27280;;;;28295:1;28280:77;28305:2;28302:1;28299:9;28280:77;;;27176:24;;27153:21;27149:52;27222:20;;28353:1;27280:21;;;;27164:2;27338:18;;;;28323:1;28316:9;28280:77;;;28284:14;;;28435:5;:12;;;28431:71;;;28474:13;:11;:13::i;:::-;28467:20;;;;;28431:71;28516:10;;;:15;;28530:1;28516:15;;;;;28601:8;;;;-1:-1:-1;;28593:20:135;;-1:-1:-1;28593:7:135;:20::i;:::-;28579:34;-1:-1:-1;28643:10:135;28651:2;28643:10;;;;28720:1;28710:11;;;:26;;;28725:6;:11;;28735:1;28725:11;28710:26;28706:310;;;28866:13;28935:1;28913:4;28920:10;28913:17;28912:24;;;;28883:5;:12;;;28898:10;28883:25;28882:54;28866:70;;28961:40;28972:6;:11;;28982:1;28972:11;:20;;28990:2;28972:20;;;28986:1;28972:20;28961:40;;28994:6;28961:10;:40::i;:::-;28954:47;;;;;;;;28706:310;29265:15;;;;29060:9;;;;29197:4;29191:2;29183:10;;;29182:19;;;29265:15;29290:2;29282:10;;;29281:19;29265:36;;;;;;;:::i;:::-;;;;;;-1:-1:-1;29330:5:135;29354:11;;;;;:29;;;29369:6;:14;;29379:4;29369:14;29354:29;29350:832;;;29446:5;:15;;;29462:5;29446:22;;;;;;;;;:::i;:::-;;;;;;-1:-1:-1;;29509:4:135;29503:2;29495:10;;;29494:19;29350:832;;;29547:4;29538:6;:13;;;29534:648;;;29668:6;:13;;29678:3;29668:13;:30;;;;29685:6;:13;;29695:3;29685:13;29668:30;:47;;;;29702:6;:13;;29712:3;29702:13;29668:47;29664:253;;;29778:4;29785:6;29778:13;29773:18;;29534:648;;29664:253;29877:21;29880:4;29887:6;29880:13;29895:2;29877;:21::i;:::-;29872:26;;29534:648;;;29951:4;29941:6;:14;;;;:32;;;;29959:6;:14;;29969:4;29959:14;29941:32;:50;;;;29977:6;:14;;29987:4;29977:14;29941:50;29937:245;;;30061:5;:15;;;30077:5;30061:22;;;;;;;;;:::i;:::-;;;;;30056:27;;30162:5;30154:13;;29937:245;30211:1;30201:6;:11;;;;:25;;;;;30225:1;30216:6;:10;;;30201:25;30200:42;;;;30231:6;:11;;30241:1;30231:11;30200:42;30196:125;;;30269:37;30282:6;30290:4;30296:5;30303:2;30269:12;:37::i;:::-;30262:44;;;;;;;;;;;30196:125;30354:13;30335:16;30506:4;30496:14;;;;30492:446;;30575:21;30578:4;30585:6;30578:13;30593:2;30575;:21::i;:::-;30569:27;;;;30633:10;30628:15;;30667:16;30628:15;30681:1;30667:7;:16::i;:::-;30661:22;;30715:4;30705:6;:14;;;;:32;;;;;30723:6;:14;;30733:4;30723:14;;30705:32;30701:223;;;30802:4;30790:16;;30904:1;30896:9;;30701:223;30512:426;30492:446;30971:10;30984:26;30992:4;30998:2;31002;31006:3;30984:7;:26::i;:::-;31013:10;30984:39;;;;-1:-1:-1;31109:4:135;31102:11;;;31141;;;:24;;;;;31164:1;31156:4;:9;;;;31141:24;:39;;;;;31176:4;31169;:11;;;31141:39;31137:860;;;31204:4;:9;;31212:1;31204:9;:22;;;;31217:4;:9;;31225:1;31217:9;31204:22;31200:144;;;31288:37;31299:4;:9;;31307:1;31299:9;:21;;31315:5;31299:21;;;31311:1;31299:21;31322:2;31288:10;:37::i;:::-;31281:44;;;;;;;;;;;;;;;31200:144;31366:4;:11;;31374:3;31366:11;31362:121;;31436:28;31445:5;31452:2;31456:7;;;;31436:8;:28::i;31362:121::-;31504:4;:11;;31512:3;31504:11;31500:121;;31574:28;31583:5;31590:2;31594:7;;;;;31574:8;:28::i;31500:121::-;31691:4;:11;;31699:3;31691:11;31687:93;;31733:28;31747:13;31733;:28::i;31687:93::-;31883:4;31875;:12;;;;:27;;;;;31898:4;31891;:11;;;31875:27;31871:112;;;31933:31;31944:4;31950:2;31954;31958:5;31933:10;:31::i;31871:112::-;32057:6;:14;;32067:4;32057:14;:28;;;;-1:-1:-1;32075:10:135;;;;;32057:28;32053:93;;;32130:1;32105:5;:15;;;32121:5;32105:22;;;;;;;;;:::i;:::-;:26;;;;:22;;;;;;:26;32053:93;32192:9;:26;;32205:13;32192:26;32188:92;;32238:27;32247:9;32258:1;32261:3;32238:8;:27::i;:::-;32361:26;32370:5;32377:3;32382:4;32361:8;:26::i;:::-;32354:33;;;;;;;;;;;;;26025:6379;;;;;;;;:::o;3087:2334::-;3634:4;3628:11;;3550:4;3353:31;3342:43;;3413:13;3353:31;3752:2;3452:13;;3342:43;3359:24;3353:31;3452:13;;;3342:43;;;;3359:24;3353:31;3452:13;;;3342:43;3359:24;3353:31;3452:13;;;3342:43;3359:24;3353:31;3452:13;;;3342:43;3359:24;3353:31;3452:13;;;3342:43;3359:24;3353:31;3452:13;;;3342:43;3413:13;4180:11;3359:24;3353:31;3452:13;;;3342:43;3413:13;4275:11;3359:24;3353:31;3452:13;;;3342:43;3359:24;3353:31;3452:13;;;3342:43;3128:12;;4415:13;;3628:11;;3452:13;;;;4180:11;3128:12;4495:84;4520:2;4517:1;4514:9;4495:84;;;3369:13;3359:24;;3353:31;3342:43;;3373:2;3413:13;;;;4575:1;3452:13;;;;4538:1;4531:9;4495:84;;;4499:14;4642:1;4638:2;4631:13;4737:5;4733:2;4729:14;4722:5;4717:27;4811:1;4797:15;;4832:6;4856:1;4851:273;;;;5191:1;5181:11;;4825:369;;4851:273;4883:8;4941:22;;;;5020:1;5015:22;;;;5107:1;5097:11;;4876:234;;4941:22;4960:1;4950:11;;4941:22;;5015;5034:1;5024:11;;4876:234;;4825:369;-1:-1:-1;;;5317:14:135;;;5300:32;;5360:19;5356:30;5392:3;5388:16;;;;5353:52;;3087:2334;-1:-1:-1;3087:2334:135:o;21746:1831::-;21819:11;21930:14;21947:24;21959:11;21947;:24::i;:::-;21930:41;;22079:1;22072:5;22068:13;22065:33;;;22094:1;22091;22084:12;22065:33;22227:2;22215:15;;;22168:20;22657:5;22654:1;22650:13;22692:4;22728:1;22713:343;22738:2;22735:1;22732:9;22713:343;;;22861:2;22849:15;;;22798:20;22896:12;;;22910:1;22892:20;22933:42;;;;23001:1;22996:42;;;;22885:153;;22933:42;22391:1;22384:12;;;22424:2;22417:13;;;22469:2;22456:16;;22942:31;;22933:42;;22996;22391:1;22384:12;;;22424:2;22417:13;;;22469:2;22456:16;;23005:31;;22885:153;-1:-1:-1;;22756:1:135;22749:9;22713:343;;;22717:14;23166:4;23160:11;23145:26;;23252:7;23246:4;23243:17;23233:124;;23294:10;23291:1;23284:21;23336:2;23333:1;23326:13;23233:124;-1:-1:-1;;23484:2:135;23473:14;;;;23461:10;23457:31;23454:1;23450:39;23518:16;;;;23536:10;23514:33;;21746:1831;-1:-1:-1;;;21746:1831:135:o;18856:823::-;18925:12;19012:18;;:::i;:::-;19080:4;19071:13;;19132:5;:8;;;19143:1;19132:12;19116:28;;:5;:12;;;:28;;;19112:95;;19164:28;;;;;2182:2:304;19164:28:135;;;2164:21:304;2221:2;2201:18;;;2194:30;2260:20;2240:18;;;2233:48;2298:18;;19164:28:135;;;;;;;;19112:95;19296:8;;;;;19329:12;;;;;19318:23;;;;;;;19355:20;;;;;19296:8;19487:13;;;19483:90;;19548:6;19557:1;19548:10;19520:5;:15;;;19536:8;19520:25;;;;;;;;;:::i;:::-;:38;;;;:25;;;;;;:38;19483:90;19649:13;:11;:13::i;2645:339::-;2706:11;2770:18;;;;2779:8;;;;2770:18;;;;;;2769:25;;;;;2786:1;2833:2;:9;;;2827:16;;;;;2826:22;;2825:32;;;;;;;2887:9;;2886:15;2769:25;2944:21;;2964:1;2944:21;;;2955:6;2944:21;2929:11;;;;;:37;;-1:-1:-1;;;2645:339:135;;;;:::o;13732:2026::-;13829:12;13915:18;;:::i;:::-;13983:4;13974:13;;14015:17;14075:5;:8;;;14086:1;14075:12;14059:28;;:5;:12;;;:28;;;14055:97;;14107:30;;;;;2529:2:304;14107:30:135;;;2511:21:304;2568:2;2548:18;;;2541:30;2607:22;2587:18;;;2580:50;2647:18;;14107:30:135;2327:344:304;14055:97:135;14222:7;:12;;14233:1;14222:12;:28;;;;14238:7;:12;;14249:1;14238:12;14222:28;14218:947;;;14270:9;14282:5;:15;;;14298:6;14282:23;;;;;;;;;:::i;:::-;;;;;14270:35;;14346:2;14339:9;;:3;:9;;;:25;;;;;14352:7;:12;;14363:1;14352:12;14339:25;14338:58;;;;14377:2;14370:9;;:3;:9;;;;:25;;;;;14383:7;:12;;14394:1;14383:12;14370:25;14323:73;;14252:159;14218:947;;;14508:7;:12;;14519:1;14508:12;14504:661;;14569:1;14561:3;14555:15;;;;14540:30;;14504:661;;;14673:7;:12;;14684:1;14673:12;14669:496;;14733:1;14726:3;14720:14;;;14705:29;;14669:496;;;14854:7;:12;;14865:1;14854:12;14850:315;;14942:4;14936:2;14927:11;;;14926:20;14912:10;14969:8;;;14965:84;;15029:1;15022:3;15016:14;;;15001:29;;14965:84;15070:3;:8;;15077:1;15070:8;15066:85;;15131:1;15123:3;15117:15;;;;15102:30;;15066:85;14868:297;14850:315;15241:8;;;;;15319:12;;;;15308:23;;;;;15475:178;;;;15566:1;15540:22;15543:5;15551:6;15543:14;15559:2;15540;:22::i;:::-;:27;;;;;;;15526:42;;15535:1;15526:42;15511:57;:12;;;:57;15475:178;;;15622:12;;;;;15637:1;15622:16;15607:31;;;;15475:178;15728:13;:11;:13::i;:::-;15721:20;13732:2026;-1:-1:-1;;;;;;;;13732:2026:135:o;32450:8733::-;32537:10;32599;32607:2;32599:10;;;;32638:11;;;:44;;;32664:1;32654:6;:11;;;;:27;;;;;32678:3;32669:6;:12;;;32654:27;32634:8490;;;32723:4;32716:11;;32847:6;32907:3;32902:25;;;;32982:3;32977:25;;;;33056:3;33051:25;;;;33131:3;33126:25;;;;33205:3;33200:25;;;;33278:3;33273:25;;;;33352:3;33347:25;;;;32840:532;;32902:25;32921:4;32913:12;;32902:25;;32977;32996:4;32988:12;;32977:25;;33051;33070:4;33062:12;;33051:25;;33126;33145:4;33137:12;;33126:25;;33200;33219:4;33211:12;;33200:25;;33273;33292:4;33284:12;;33273:25;;33347;33366:4;33358:12;;32840:532;;33435:4;:12;;33443:4;33435:12;33431:4023;;-1:-1:-1;;;33486:9:135;33478:26;;33499:4;33494:1;33486:9;;;33485:18;33478:26;33471:33;;33431:4023;33572:4;:12;;33580:4;33572:12;33568:3886;;-1:-1:-1;;;33623:9:135;33615:26;;33636:4;33631:1;33623:9;;;33622:18;33615:26;33608:33;;33568:3886;33709:4;:12;;33717:4;33709:12;33705:3749;;33774:4;33769:1;33761:9;;;33760:18;33807:27;33761:9;33810:11;;;;33823:2;:10;;;33807:2;:27::i;:::-;33800:34;;;;;;;33705:3749;33903:4;:12;;33911:4;33903:12;33899:3555;;-1:-1:-1;;;33946:17:135;;;33958:4;33953:9;;33946:17;33939:24;;33899:3555;34032:4;:11;;34040:3;34032:11;34028:3426;;-1:-1:-1;;;34074:17:135;;;34086:4;34081:9;;34074:17;34067:24;;34028:3426;34160:4;:12;;34168:4;34160:12;34156:3298;;34203:21;34212:2;34206:8;;:2;:8;;;;34221:2;34216;:7;34203:2;:21::i;:::-;34196:28;;;;;;34156:3298;34473:4;:12;;34481:4;34473:12;34469:2985;;34516:2;34509:9;;;;;;34469:2985;34587:4;:12;;34595:4;34587:12;34583:2871;;34630:2;34623:9;;;;;;34583:2871;34701:4;:12;;34709:4;34701:12;34697:2757;;34744:2;34737:9;;;;;;34697:2757;34815:4;:12;;34823:4;34815:12;34811:2643;;34858:2;34851:9;;;;;;34811:2643;34932:4;:12;;34940:4;34932:12;34928:2526;;34975:2;34968:9;;;;;;34928:2526;35092:4;:12;;35100:4;35092:12;35088:2366;;35135:2;35128:9;;;;;;35088:2366;35206:4;:12;;35214:4;35206:12;35202:2252;;35249:2;35242:9;;;;;;35202:2252;35320:4;:12;;35328:4;35320:12;35316:2138;;35363:2;35356:9;;;;;;35316:2138;35434:4;:12;;35442:4;35434:12;35430:2024;;35477:2;35470:9;;;;;;35430:2024;35548:4;:12;;35556:4;35548:12;35544:1910;;35591:2;35584:9;;;;;;35544:1910;35662:4;:12;;35670:4;35662:12;35658:1796;;35705:2;35698:9;;;;;;35658:1796;35777:4;:12;;35785:4;35777:12;35773:1681;;35820:2;35813:9;;;;;;35773:1681;35890:4;:12;;35898:4;35890:12;35886:1568;;35933:2;35926:9;;;;;;35886:1568;36004:4;:12;;36012:4;36004:12;36000:1454;;36047:2;36040:9;;;;;;36000:1454;36196:4;:12;;36204:4;36196:12;36192:1262;;-1:-1:-1;;;36240:7:135;;;36232:16;;36192:1262;36317:4;:12;;36325:4;36317:12;36313:1141;;-1:-1:-1;;;36361:7:135;;;36353:16;;36313:1141;36437:4;:12;;36445:4;36437:12;36433:1021;;-1:-1:-1;;;36481:7:135;;;36473:16;;36433:1021;36558:4;:12;;36566:4;36558:12;36554:900;;-1:-1:-1;;;36602:7:135;;;36594:16;;36554:900;36678:4;:12;;36686:4;36678:12;36674:780;;-1:-1:-1;;;36722:7:135;;;36714:16;;36674:780;36797:4;:12;;36805:4;36797:12;36793:661;;-1:-1:-1;;;36841:7:135;;;36833:16;;36793:661;36917:4;:12;;36925:4;36917:12;36913:541;;-1:-1:-1;;;36961:7:135;;;36953:16;;36913:541;37037:4;:12;;37045:4;37037:12;37033:421;;-1:-1:-1;;;37082:7:135;;;37080:10;37073:17;;37033:421;37159:4;:12;;37167:4;37159:12;37155:299;;37220:2;37202:21;;37208:2;37202:21;;;:29;;37230:1;37202:29;;;37226:1;37202:29;37195:36;;;;;;;;37155:299;37301:4;:12;;37309:4;37301:12;37297:157;;37349:2;37344:7;;:2;:7;;;:15;;37358:1;37344:15;;37297:157;37406:29;;;;;2878:2:304;37406:29:135;;;2860:21:304;2917:2;2897:18;;;2890:30;2956:21;2936:18;;;2929:49;2995:18;;37406:29:135;2676:343:304;37297:157:135;32684:4784;32634:8490;;;37524:6;:14;;37534:4;37524:14;37520:3590;;37583:4;37576:11;;37658:3;37650:11;;;37646:549;;-1:-1:-1;;;37703:21:135;;;37689:36;;37646:549;37810:4;:12;;37818:4;37810:12;:28;;;;37826:4;:12;;37834:4;37826:12;37810:28;37806:389;;;37870:4;:12;;37878:4;37870:12;37866:83;;37919:3;;;37866:83;37974:8;38012:127;38024:10;38019:15;;:20;38012:127;;38104:8;38071:3;38104:8;;;;;38071:3;38012:127;;;38171:1;-1:-1:-1;38164:8:135;;-1:-1:-1;;38164:8:135;37520:3590;38262:6;:14;;38272:4;38262:14;38258:2852;;-1:-1:-1;;38307:8:135;38313:2;38307:8;;;;38300:15;;38258:2852;38382:6;:14;;38392:4;38382:14;38378:2732;;38427:42;38445:2;38450:1;38445:6;38455:1;38444:12;38439:2;:17;38431:26;;:3;:26;;;;38461:4;38430:35;38467:1;38427:2;:42::i;:::-;38420:49;;;;;38378:2732;38536:6;:14;;38546:4;38536:14;38532:2578;;38581:45;38599:2;38604:1;38599:6;38609:1;38598:12;38593:2;:17;38585:26;;:3;:26;;;;38615:6;38584:37;38623:2;38581;:45::i;38532:2578::-;38694:6;:14;;38704:4;38694:14;38690:2420;;-1:-1:-1;;38745:21:135;38764:1;38759;38754:6;;38753:12;38745:21;;38802:36;;;38873:5;38868:10;;38745:21;;;;;38867:18;38860:25;;38690:2420;38952:6;:14;;38962:4;38952:14;38948:2162;;38997:3;38990:10;;;;;38948:2162;39068:6;:14;;39078:4;39068:14;39064:2046;;39128:2;39133:1;39128:6;39138:1;39127:12;39122:2;:17;39114:26;;:3;:26;;;;39144:4;39113:35;39106:42;;;;;39064:2046;39217:6;:14;;39227:4;39217:14;39213:1897;;39277:2;39282:1;39277:6;39287:1;39276:12;39271:2;:17;39263:26;;:3;:26;;;;39293:6;39262:37;39255:44;;;;;39213:1897;39368:6;:14;;39378:4;39368:14;39364:1746;;-1:-1:-1;;39419:26:135;39443:1;39438;39433:6;;39432:12;39427:2;:17;39419:26;;39481:41;;;39557:5;39552:10;;39419:26;;;;;39551:18;39544:25;;39364:1746;39637:6;:14;;39647:4;39637:14;39633:1477;;-1:-1:-1;;39694:4:135;39688:34;39720:1;39715;39710:6;;39709:12;39704:2;:17;39688:34;;39778:27;;;39758:48;;;39836:10;;39689:9;;;39688:34;;39835:18;39828:25;;39633:1477;39921:6;:14;;39931:4;39921:14;39917:1193;;-1:-1:-1;;39978:6:135;39972:36;40006:1;40001;39996:6;;39995:12;39990:2;:17;39972:36;;40064:29;;;40044:50;;;40124:10;;39973:11;;;39972:36;;40123:18;40116:25;;39917:1193;40210:6;:14;;40220:4;40210:14;40206:904;;-1:-1:-1;;40261:20:135;40279:1;40274;40269:6;;40268:12;40261:20;;40317:36;;;40389:5;40383:11;;40261:20;;;;;40382:19;40375:26;;40206:904;40469:6;:14;;40479:4;40469:14;40465:645;;40514:2;40507:9;;;;;40465:645;40585:6;:14;;40595:4;40585:14;40581:529;;-1:-1:-1;;40636:25:135;40659:1;40654;40649:6;;40648:12;40643:2;:17;40636:25;;40697:41;;;40774:5;40768:11;;40636:25;;;;;40767:19;40760:26;;40581:529;40853:6;:14;;40863:4;40853:14;40849:261;;40898:3;40891:10;;;;;40849:261;40968:6;:14;;40978:4;40968:14;40964:146;;41013:2;41006:9;;;32450:8733;;;;;;;:::o;19960:782::-;20046:12;20133:18;;:::i;:::-;-1:-1:-1;20201:4:135;20308:2;20296:14;;;;20288:41;;;;;;;3226:2:304;20288:41:135;;;3208:21:304;3265:2;3245:18;;;3238:30;3304:16;3284:18;;;3277:44;3338:18;;20288:41:135;3024:338:304;20288:41:135;20425:14;;;;;;;:30;;;20443:12;20425:30;20421:102;;;20504:4;20475:5;:15;;;20491:9;20475:26;;;;;;;;;:::i;:::-;:33;;;;:26;;;;;;:33;20421:102;20578:12;;;;;20567:23;;;;:8;;;:23;20634:1;20619:16;;;20604:31;;;20712:13;:11;:13::i;5582:7764::-;5646:12;5732:18;;:::i;:::-;-1:-1:-1;5910:15:135;;:18;;;;5800:4;6070:18;;;;6114;;;;6158;;;;;5800:4;;5890:17;;;;6070:18;6114;6248;;;6262:4;6248:18;6244:6792;;6298:2;6327:4;6322:9;;:14;6318:144;;6438:4;6433:9;;6425:4;:18;6419:24;6318:144;6483:2;:7;;6489:1;6483:7;6479:161;;6519:10;;;;;6551:16;;;;;;;;6519:10;-1:-1:-1;6479:161:135;;;6619:2;6614:7;;6479:161;6268:386;6244:6792;;;6756:10;:18;;6770:4;6756:18;6752:6284;;1745:10;6794:14;;6752:6284;;;6892:10;:18;;6906:4;6892:18;6888:6148;;6935:1;6930:6;;6888:6148;;;7060:10;:18;;7074:4;7060:18;7056:5980;;7113:4;7098:12;;;:19;7135:26;;;:14;;;:26;7186:13;:11;:13::i;:::-;7179:20;5582:7764;-1:-1:-1;;;;;;;;;5582:7764:135:o;7056:5980::-;7325:10;:18;;7339:4;7325:18;7321:5715;;7476:14;;;7472:2723;7321:5715;7472:2723;7646:22;;;;;7642:2553;;7771:10;7784:27;7792:2;7797:10;7792:15;7809:1;7784:7;:27::i;:::-;7895:17;;;;7771:40;;-1:-1:-1;7895:17:135;7873:19;8045:14;8064:1;8039:26;8035:146;;1676:4:136;1670:11;;1533:21;1787:15;;;1828:8;1822:4;1815:22;1850:27;;;1996:4;1983:18;;2098:17;;2003:19;1979:44;2025:11;1976:61;8093:65:135;;8035:146;8267:20;;;;;8234:54;;;;;;;;3540:25:304;;;8234:54:135;3601:23:304;;;3581:18;;;3574:51;8203:11:135;;;;8234:19;:6;:19;;;;3513:18:304;;8234:54:135;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;8202:86;;;;8515:1;8511:2;8507:10;8612:9;8609:1;8605:17;8694:6;8687:5;8684:17;8681:40;;;8714:5;8704:15;;8681:40;;8797:6;8793:2;8790:14;8787:34;;;8817:2;8807:12;;8787:34;8923:3;8918:1;8910:6;8906:14;8901:3;8897:24;8893:34;8886:41;;9023:3;9019:1;9007:9;8998:6;8995:1;8991:14;8987:30;8983:38;8979:48;8972:55;;9178:1;9174;9170;9158:9;9155:1;9151:17;9147:25;9143:33;9139:41;9305:1;9301;9297;9288:6;9276:9;9273:1;9269:17;9265:30;9261:38;9257:46;9253:54;9235:72;;9436:10;9432:15;9426:4;9422:26;9414:34;;9552:3;9544:4;9540:9;9535:3;9531:19;9528:28;9521:35;;;;9698:33;9707:2;9712:10;9707:15;9724:1;9727:3;9698:8;:33::i;:::-;9753:20;;;:38;;;;;;;;;-1:-1:-1;7642:2553:135;;-1:-1:-1;;;7642:2553:135;;9910:18;;;;;9906:289;;10080:2;10075:7;;7321:5715;;9906:289;10134:10;10129:15;;2053:3;10166:10;;9906:289;7321:5715;;;10324:10;:18;;10338:4;10324:18;10320:2716;;10478:15;;;1824:1;10478:15;;:34;;-1:-1:-1;10497:15:135;;;1859:1;10497:15;10478:34;:57;;;-1:-1:-1;10516:19:135;;;1936:1;10516:19;10478:57;10474:1593;;;10564:2;10559:7;;10320:2716;;10474:1593;10690:23;;;;;10686:1381;;10737:10;10750:27;10758:2;10763:10;10758:15;10775:1;10750:7;:27::i;:::-;10853:17;;;;10737:40;;-1:-1:-1;11096:1:135;11088:10;;11190:1;11186:17;11265:13;;;11262:32;;;11287:5;11281:11;;11262:32;11573:14;;;11379:1;11569:22;;;11565:32;;;;11462:26;11486:1;11371:10;;;11466:18;;;11462:26;11561:43;11367:20;;11669:12;11797:17;;;:23;11865:1;11842:20;;;:24;11375:2;-1:-1:-1;11375:2:135;7321:5715;;10320:2716;12269:10;:18;;12283:4;12269:18;12265:771;;12379:2;:7;;12385:1;12379:7;12375:647;;12472:14;;;;;:40;;-1:-1:-1;12490:22:135;;;1978:1;12490:22;12472:40;:62;;;-1:-1:-1;12516:18:135;;;1897:1;12516:18;12472:62;12468:404;;;12567:1;12562:6;;12375:647;;12468:404;12613:15;;;1824:1;12613:15;;:34;;-1:-1:-1;12632:15:135;;;1859:1;12632:15;12613:34;:61;;;-1:-1:-1;12651:23:135;;;2021:1;12651:23;12613:61;:84;;;-1:-1:-1;12678:19:135;;;1936:1;12678:19;12613:84;12609:263;;;12730:1;12725:6;;7321:5715;;12375:647;12923:10;12918:15;;2087:4;12955:11;;12375:647;13111:15;;;;;:23;;;;:18;;;;:23;;;;13148:15;;:23;;;:18;;;;:23;-1:-1:-1;13237:12:135;;;;13226:23;;;:8;;;:23;13293:1;13278:16;13263:31;;;;;13316:13;:11;:13::i;16084:2480::-;16178:12;16264:18;;:::i;:::-;-1:-1:-1;16332:4:135;16364:10;16472:13;;;16481:4;16472:13;16468:1705;;-1:-1:-1;16511:8:135;;;;16468:1705;;;16630:5;:13;;16639:4;16630:13;16626:1547;;16663:14;;;:8;;;:14;16626:1547;;;16793:5;:13;;16802:4;16793:13;16789:1384;;-1:-1:-1;16832:8:135;;;;16789:1384;;;16951:5;:13;;16960:4;16951:13;16947:1226;;16984:14;;;:8;;;:14;16947:1226;;;17125:5;:13;;17134:4;17125:13;17121:1052;;17252:9;17198:17;17178;;;17198;;;;17178:37;17259:2;17252:9;;;;;17234:8;;;:28;17280:22;:8;;;:22;17121:1052;;;17439:5;:13;;17448:4;17439:13;17435:738;;17506:11;17492;;;17506;;;17492:25;17561:2;17554:9;;;;;17536:8;;;:28;17582:22;:8;;;:22;17435:738;;;17763:5;:13;;17772:4;17763:13;17759:414;;17833:3;17814:23;;17820:3;17814:23;;;;;;;:::i;:::-;;17796:42;;:8;;;:42;17874:23;;;;;;;;;;;;;:::i;:::-;;17856:42;;:8;;;:42;17759:414;;;18067:5;:13;;18076:4;18067:13;18063:110;;18117:3;18111:9;;:3;:9;;;;;;;:::i;:::-;;18100:20;;;;:8;;;:20;18149:9;;;;;;;;;;;:::i;:::-;;18138:20;;:8;;;:20;18063:110;18266:14;;;;18262:85;;18329:3;18300:5;:15;;;18316:9;18300:26;;;;;;;;;:::i;:::-;:32;;;;:26;;;;;;:32;18262:85;18401:12;;;;;18390:23;;;;:8;;;:23;18457:1;18442:16;;;18427:31;;;18534:13;:11;:13::i;:::-;18527:20;16084:2480;-1:-1:-1;;;;;;;16084:2480:135:o;23913:1654::-;24089:14;24106:24;24118:11;24106;:24::i;:::-;24089:41;;24238:1;24231:5;24227:13;24224:33;;;24253:1;24250;24243:12;24224:33;24392:2;24586:15;;;24411:2;24400:14;;24388:10;24384:31;24381:1;24377:39;24542:16;;;24327:20;;24527:10;24516:22;;;24512:27;24502:38;24499:60;25028:5;25025:1;25021:13;25099:1;25084:343;25109:2;25106:1;25103:9;25084:343;;;25232:2;25220:15;;;25169:20;25267:12;;;25281:1;25263:20;25304:42;;;;25372:1;25367:42;;;;25256:153;;25304:42;22391:1;22384:12;;;22424:2;22417:13;;;22469:2;22456:16;;25313:31;;25304:42;;25367;22391:1;22384:12;;;22424:2;22417:13;;;22469:2;22456:16;;25376:31;;25256:153;-1:-1:-1;;25127:1:135;25120:9;25084:343;;;-1:-1:-1;;25526:4:135;25519:18;-1:-1:-1;;;;23913:1654:135:o;20946:586::-;21268:20;;;21292:7;21268:32;21261:3;:40;;;21374:14;;21429:17;;21423:24;;;21415:72;;;;;;;4277:2:304;21415:72:135;;;4259:21:304;4316:2;4296:18;;;4289:30;4355:34;4335:18;;;4328:62;4426:5;4406:18;;;4399:33;4449:19;;21415:72:135;4075:399:304;21415:72:135;21501:14;20946:586;;;:::o;-1:-1:-1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;:::o;467:347:304:-;518:8;528:6;582:3;575:4;567:6;563:17;559:27;549:55;;600:1;597;590:12;549:55;-1:-1:-1;623:20:304;;666:18;655:30;;652:50;;;698:1;695;688:12;652:50;735:4;727:6;723:17;711:29;;787:3;780:4;771:6;763;759:19;755:30;752:39;749:59;;;804:1;801;794:12;749:59;467:347;;;;;:::o;819:785::-;918:6;926;934;942;950;1003:2;991:9;982:7;978:23;974:32;971:52;;;1019:1;1016;1009:12;971:52;1059:9;1046:23;1088:18;1129:2;1121:6;1118:14;1115:34;;;1145:1;1142;1135:12;1115:34;1184:58;1234:7;1225:6;1214:9;1210:22;1184:58;:::i;:::-;1261:8;;-1:-1:-1;1158:84:304;-1:-1:-1;1349:2:304;1334:18;;1321:32;;-1:-1:-1;1365:16:304;;;1362:36;;;1394:1;1391;1384:12;1362:36;;1433:60;1485:7;1474:8;1463:9;1459:24;1433:60;:::i;:::-;819:785;;;;-1:-1:-1;1512:8:304;1594:2;1579:18;1566:32;;819:785;-1:-1:-1;;;;819:785:304:o;1791:184::-;1843:77;1840:1;1833:88;1940:4;1937:1;1930:15;1964:4;1961:1;1954:15;3636:245;3715:6;3723;3776:2;3764:9;3755:7;3751:23;3747:32;3744:52;;;3792:1;3789;3782:12;3744:52;-1:-1:-1;;3815:16:304;;3871:2;3856:18;;;3850:25;3815:16;;3850:25;;-1:-1:-1;3636:245:304:o;3886:184::-;3938:77;3935:1;3928:88;4035:4;4032:1;4025:15;4059:4;4056:1;4049:15" var MIPSDeployedSourceMap = "1131:40054:135:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1710:45;;1745:10;1710:45;;;;;188:10:305;176:23;;;158:42;;146:2;131:18;1710:45:135;;;;;;;;2448:99;;;412:42:305;2534:6:135;400:55:305;382:74;;370:2;355:18;2448:99:135;211:251:305;26025:6379:135;;;;;;:::i;:::-;;:::i;:::-;;;1755:25:305;;;1743:2;1728:18;26025:6379:135;1609:177:305;26025:6379:135;26128:7;26171:18;;:::i;:::-;26318:4;26311:5;26308:15;26298:134;;26412:1;26409;26402:12;26298:134;26468:4;26462:11;26475:10;26459:27;26449:136;;26565:1;26562;26555:12;26449:136;26634:3;26615:17;26612:26;26602:151;;26733:1;26730;26723:12;26602:151;26798:3;26783:13;26780:22;26770:146;;26896:1;26893;26886:12;26770:146;27176:24;;27521:4;27222:20;27579:2;27280:21;;27176:24;27338:18;27222:20;27280:21;;;27176:24;27153:21;27149:52;;;27338:18;27222:20;;;27280:21;;;27176:24;27149:52;;27222:20;;27280:21;;;27176:24;27149:52;;27338:18;27222:20;27280:21;;;27176:24;27149:52;;27338:18;27222:20;27280:21;;;27176:24;27149:52;;27338:18;27222:20;27280:21;;;27176:24;27149:52;;;27338:18;27222:20;27280:21;;;27176:24;27153:21;27149:52;;;27338:18;27222:20;27280:21;;;27176:24;27149:52;;27338:18;27222:20;27280:21;;;27176:24;27149:52;;27338:18;27222:20;28197:10;27338:18;28187:21;;;27280;;;;28295:1;28280:77;28305:2;28302:1;28299:9;28280:77;;;27176:24;;27153:21;27149:52;27222:20;;28353:1;27280:21;;;;27164:2;27338:18;;;;28323:1;28316:9;28280:77;;;28284:14;;;28435:5;:12;;;28431:71;;;28474:13;:11;:13::i;:::-;28467:20;;;;;28431:71;28516:10;;;:15;;28530:1;28516:15;;;;;28601:8;;;;-1:-1:-1;;28593:20:135;;-1:-1:-1;28593:7:135;:20::i;:::-;28579:34;-1:-1:-1;28643:10:135;28651:2;28643:10;;;;28720:1;28710:11;;;:26;;;28725:6;:11;;28735:1;28725:11;28710:26;28706:310;;;28866:13;28935:1;28913:4;28920:10;28913:17;28912:24;;;;28883:5;:12;;;28898:10;28883:25;28882:54;28866:70;;28961:40;28972:6;:11;;28982:1;28972:11;:20;;28990:2;28972:20;;;28986:1;28972:20;28961:40;;28994:6;28961:10;:40::i;:::-;28954:47;;;;;;;;28706:310;29265:15;;;;29060:9;;;;29197:4;29191:2;29183:10;;;29182:19;;;29265:15;29290:2;29282:10;;;29281:19;29265:36;;;;;;;:::i;:::-;;;;;;-1:-1:-1;29330:5:135;29354:11;;;;;:29;;;29369:6;:14;;29379:4;29369:14;29354:29;29350:832;;;29446:5;:15;;;29462:5;29446:22;;;;;;;;;:::i;:::-;;;;;;-1:-1:-1;;29509:4:135;29503:2;29495:10;;;29494:19;29350:832;;;29547:4;29538:6;:13;;;29534:648;;;29668:6;:13;;29678:3;29668:13;:30;;;;29685:6;:13;;29695:3;29685:13;29668:30;:47;;;;29702:6;:13;;29712:3;29702:13;29668:47;29664:253;;;29778:4;29785:6;29778:13;29773:18;;29534:648;;29664:253;29877:21;29880:4;29887:6;29880:13;29895:2;29877;:21::i;:::-;29872:26;;29534:648;;;29951:4;29941:6;:14;;;;:32;;;;29959:6;:14;;29969:4;29959:14;29941:32;:50;;;;29977:6;:14;;29987:4;29977:14;29941:50;29937:245;;;30061:5;:15;;;30077:5;30061:22;;;;;;;;;:::i;:::-;;;;;30056:27;;30162:5;30154:13;;29937:245;30211:1;30201:6;:11;;;;:25;;;;;30225:1;30216:6;:10;;;30201:25;30200:42;;;;30231:6;:11;;30241:1;30231:11;30200:42;30196:125;;;30269:37;30282:6;30290:4;30296:5;30303:2;30269:12;:37::i;:::-;30262:44;;;;;;;;;;;30196:125;30354:13;30335:16;30506:4;30496:14;;;;30492:446;;30575:21;30578:4;30585:6;30578:13;30593:2;30575;:21::i;:::-;30569:27;;;;30633:10;30628:15;;30667:16;30628:15;30681:1;30667:7;:16::i;:::-;30661:22;;30715:4;30705:6;:14;;;;:32;;;;;30723:6;:14;;30733:4;30723:14;;30705:32;30701:223;;;30802:4;30790:16;;30904:1;30896:9;;30701:223;30512:426;30492:446;30971:10;30984:26;30992:4;30998:2;31002;31006:3;30984:7;:26::i;:::-;31013:10;30984:39;;;;-1:-1:-1;31109:4:135;31102:11;;;31141;;;:24;;;;;31164:1;31156:4;:9;;;;31141:24;:39;;;;;31176:4;31169;:11;;;31141:39;31137:860;;;31204:4;:9;;31212:1;31204:9;:22;;;;31217:4;:9;;31225:1;31217:9;31204:22;31200:144;;;31288:37;31299:4;:9;;31307:1;31299:9;:21;;31315:5;31299:21;;;31311:1;31299:21;31322:2;31288:10;:37::i;:::-;31281:44;;;;;;;;;;;;;;;31200:144;31366:4;:11;;31374:3;31366:11;31362:121;;31436:28;31445:5;31452:2;31456:7;;;;31436:8;:28::i;31362:121::-;31504:4;:11;;31512:3;31504:11;31500:121;;31574:28;31583:5;31590:2;31594:7;;;;;31574:8;:28::i;31500:121::-;31691:4;:11;;31699:3;31691:11;31687:93;;31733:28;31747:13;31733;:28::i;31687:93::-;31883:4;31875;:12;;;;:27;;;;;31898:4;31891;:11;;;31875:27;31871:112;;;31933:31;31944:4;31950:2;31954;31958:5;31933:10;:31::i;31871:112::-;32057:6;:14;;32067:4;32057:14;:28;;;;-1:-1:-1;32075:10:135;;;;;32057:28;32053:93;;;32130:1;32105:5;:15;;;32121:5;32105:22;;;;;;;;;:::i;:::-;:26;;;;:22;;;;;;:26;32053:93;32192:9;:26;;32205:13;32192:26;32188:92;;32238:27;32247:9;32258:1;32261:3;32238:8;:27::i;:::-;32361:26;32370:5;32377:3;32382:4;32361:8;:26::i;:::-;32354:33;;;;;;;;;;;;;26025:6379;;;;;;;;:::o;3087:2334::-;3634:4;3628:11;;3550:4;3353:31;3342:43;;3413:13;3353:31;3752:2;3452:13;;3342:43;3359:24;3353:31;3452:13;;;3342:43;;;;3359:24;3353:31;3452:13;;;3342:43;3359:24;3353:31;3452:13;;;3342:43;3359:24;3353:31;3452:13;;;3342:43;3359:24;3353:31;3452:13;;;3342:43;3359:24;3353:31;3452:13;;;3342:43;3413:13;4180:11;3359:24;3353:31;3452:13;;;3342:43;3413:13;4275:11;3359:24;3353:31;3452:13;;;3342:43;3359:24;3353:31;3452:13;;;3342:43;3128:12;;4415:13;;3628:11;;3452:13;;;;4180:11;3128:12;4495:84;4520:2;4517:1;4514:9;4495:84;;;3369:13;3359:24;;3353:31;3342:43;;3373:2;3413:13;;;;4575:1;3452:13;;;;4538:1;4531:9;4495:84;;;4499:14;4642:1;4638:2;4631:13;4737:5;4733:2;4729:14;4722:5;4717:27;4811:1;4797:15;;4832:6;4856:1;4851:273;;;;5191:1;5181:11;;4825:369;;4851:273;4883:8;4941:22;;;;5020:1;5015:22;;;;5107:1;5097:11;;4876:234;;4941:22;4960:1;4950:11;;4941:22;;5015;5034:1;5024:11;;4876:234;;4825:369;-1:-1:-1;;;5317:14:135;;;5300:32;;5360:19;5356:30;5392:3;5388:16;;;;5353:52;;3087:2334;-1:-1:-1;3087:2334:135:o;21746:1831::-;21819:11;21930:14;21947:24;21959:11;21947;:24::i;:::-;21930:41;;22079:1;22072:5;22068:13;22065:33;;;22094:1;22091;22084:12;22065:33;22227:2;22215:15;;;22168:20;22657:5;22654:1;22650:13;22692:4;22728:1;22713:343;22738:2;22735:1;22732:9;22713:343;;;22861:2;22849:15;;;22798:20;22896:12;;;22910:1;22892:20;22933:42;;;;23001:1;22996:42;;;;22885:153;;22933:42;22391:1;22384:12;;;22424:2;22417:13;;;22469:2;22456:16;;22942:31;;22933:42;;22996;22391:1;22384:12;;;22424:2;22417:13;;;22469:2;22456:16;;23005:31;;22885:153;-1:-1:-1;;22756:1:135;22749:9;22713:343;;;22717:14;23166:4;23160:11;23145:26;;23252:7;23246:4;23243:17;23233:124;;23294:10;23291:1;23284:21;23336:2;23333:1;23326:13;23233:124;-1:-1:-1;;23484:2:135;23473:14;;;;23461:10;23457:31;23454:1;23450:39;23518:16;;;;23536:10;23514:33;;21746:1831;-1:-1:-1;;;21746:1831:135:o;18856:823::-;18925:12;19012:18;;:::i;:::-;19080:4;19071:13;;19132:5;:8;;;19143:1;19132:12;19116:28;;:5;:12;;;:28;;;19112:95;;19164:28;;;;;2182:2:305;19164:28:135;;;2164:21:305;2221:2;2201:18;;;2194:30;2260:20;2240:18;;;2233:48;2298:18;;19164:28:135;;;;;;;;19112:95;19296:8;;;;;19329:12;;;;;19318:23;;;;;;;19355:20;;;;;19296:8;19487:13;;;19483:90;;19548:6;19557:1;19548:10;19520:5;:15;;;19536:8;19520:25;;;;;;;;;:::i;:::-;:38;;;;:25;;;;;;:38;19483:90;19649:13;:11;:13::i;2645:339::-;2706:11;2770:18;;;;2779:8;;;;2770:18;;;;;;2769:25;;;;;2786:1;2833:2;:9;;;2827:16;;;;;2826:22;;2825:32;;;;;;;2887:9;;2886:15;2769:25;2944:21;;2964:1;2944:21;;;2955:6;2944:21;2929:11;;;;;:37;;-1:-1:-1;;;2645:339:135;;;;:::o;13732:2026::-;13829:12;13915:18;;:::i;:::-;13983:4;13974:13;;14015:17;14075:5;:8;;;14086:1;14075:12;14059:28;;:5;:12;;;:28;;;14055:97;;14107:30;;;;;2529:2:305;14107:30:135;;;2511:21:305;2568:2;2548:18;;;2541:30;2607:22;2587:18;;;2580:50;2647:18;;14107:30:135;2327:344:305;14055:97:135;14222:7;:12;;14233:1;14222:12;:28;;;;14238:7;:12;;14249:1;14238:12;14222:28;14218:947;;;14270:9;14282:5;:15;;;14298:6;14282:23;;;;;;;;;:::i;:::-;;;;;14270:35;;14346:2;14339:9;;:3;:9;;;:25;;;;;14352:7;:12;;14363:1;14352:12;14339:25;14338:58;;;;14377:2;14370:9;;:3;:9;;;;:25;;;;;14383:7;:12;;14394:1;14383:12;14370:25;14323:73;;14252:159;14218:947;;;14508:7;:12;;14519:1;14508:12;14504:661;;14569:1;14561:3;14555:15;;;;14540:30;;14504:661;;;14673:7;:12;;14684:1;14673:12;14669:496;;14733:1;14726:3;14720:14;;;14705:29;;14669:496;;;14854:7;:12;;14865:1;14854:12;14850:315;;14942:4;14936:2;14927:11;;;14926:20;14912:10;14969:8;;;14965:84;;15029:1;15022:3;15016:14;;;15001:29;;14965:84;15070:3;:8;;15077:1;15070:8;15066:85;;15131:1;15123:3;15117:15;;;;15102:30;;15066:85;14868:297;14850:315;15241:8;;;;;15319:12;;;;15308:23;;;;;15475:178;;;;15566:1;15540:22;15543:5;15551:6;15543:14;15559:2;15540;:22::i;:::-;:27;;;;;;;15526:42;;15535:1;15526:42;15511:57;:12;;;:57;15475:178;;;15622:12;;;;;15637:1;15622:16;15607:31;;;;15475:178;15728:13;:11;:13::i;:::-;15721:20;13732:2026;-1:-1:-1;;;;;;;;13732:2026:135:o;32450:8733::-;32537:10;32599;32607:2;32599:10;;;;32638:11;;;:44;;;32664:1;32654:6;:11;;;;:27;;;;;32678:3;32669:6;:12;;;32654:27;32634:8490;;;32723:4;32716:11;;32847:6;32907:3;32902:25;;;;32982:3;32977:25;;;;33056:3;33051:25;;;;33131:3;33126:25;;;;33205:3;33200:25;;;;33278:3;33273:25;;;;33352:3;33347:25;;;;32840:532;;32902:25;32921:4;32913:12;;32902:25;;32977;32996:4;32988:12;;32977:25;;33051;33070:4;33062:12;;33051:25;;33126;33145:4;33137:12;;33126:25;;33200;33219:4;33211:12;;33200:25;;33273;33292:4;33284:12;;33273:25;;33347;33366:4;33358:12;;32840:532;;33435:4;:12;;33443:4;33435:12;33431:4023;;-1:-1:-1;;;33486:9:135;33478:26;;33499:4;33494:1;33486:9;;;33485:18;33478:26;33471:33;;33431:4023;33572:4;:12;;33580:4;33572:12;33568:3886;;-1:-1:-1;;;33623:9:135;33615:26;;33636:4;33631:1;33623:9;;;33622:18;33615:26;33608:33;;33568:3886;33709:4;:12;;33717:4;33709:12;33705:3749;;33774:4;33769:1;33761:9;;;33760:18;33807:27;33761:9;33810:11;;;;33823:2;:10;;;33807:2;:27::i;:::-;33800:34;;;;;;;33705:3749;33903:4;:12;;33911:4;33903:12;33899:3555;;-1:-1:-1;;;33946:17:135;;;33958:4;33953:9;;33946:17;33939:24;;33899:3555;34032:4;:11;;34040:3;34032:11;34028:3426;;-1:-1:-1;;;34074:17:135;;;34086:4;34081:9;;34074:17;34067:24;;34028:3426;34160:4;:12;;34168:4;34160:12;34156:3298;;34203:21;34212:2;34206:8;;:2;:8;;;;34221:2;34216;:7;34203:2;:21::i;:::-;34196:28;;;;;;34156:3298;34473:4;:12;;34481:4;34473:12;34469:2985;;34516:2;34509:9;;;;;;34469:2985;34587:4;:12;;34595:4;34587:12;34583:2871;;34630:2;34623:9;;;;;;34583:2871;34701:4;:12;;34709:4;34701:12;34697:2757;;34744:2;34737:9;;;;;;34697:2757;34815:4;:12;;34823:4;34815:12;34811:2643;;34858:2;34851:9;;;;;;34811:2643;34932:4;:12;;34940:4;34932:12;34928:2526;;34975:2;34968:9;;;;;;34928:2526;35092:4;:12;;35100:4;35092:12;35088:2366;;35135:2;35128:9;;;;;;35088:2366;35206:4;:12;;35214:4;35206:12;35202:2252;;35249:2;35242:9;;;;;;35202:2252;35320:4;:12;;35328:4;35320:12;35316:2138;;35363:2;35356:9;;;;;;35316:2138;35434:4;:12;;35442:4;35434:12;35430:2024;;35477:2;35470:9;;;;;;35430:2024;35548:4;:12;;35556:4;35548:12;35544:1910;;35591:2;35584:9;;;;;;35544:1910;35662:4;:12;;35670:4;35662:12;35658:1796;;35705:2;35698:9;;;;;;35658:1796;35777:4;:12;;35785:4;35777:12;35773:1681;;35820:2;35813:9;;;;;;35773:1681;35890:4;:12;;35898:4;35890:12;35886:1568;;35933:2;35926:9;;;;;;35886:1568;36004:4;:12;;36012:4;36004:12;36000:1454;;36047:2;36040:9;;;;;;36000:1454;36196:4;:12;;36204:4;36196:12;36192:1262;;-1:-1:-1;;;36240:7:135;;;36232:16;;36192:1262;36317:4;:12;;36325:4;36317:12;36313:1141;;-1:-1:-1;;;36361:7:135;;;36353:16;;36313:1141;36437:4;:12;;36445:4;36437:12;36433:1021;;-1:-1:-1;;;36481:7:135;;;36473:16;;36433:1021;36558:4;:12;;36566:4;36558:12;36554:900;;-1:-1:-1;;;36602:7:135;;;36594:16;;36554:900;36678:4;:12;;36686:4;36678:12;36674:780;;-1:-1:-1;;;36722:7:135;;;36714:16;;36674:780;36797:4;:12;;36805:4;36797:12;36793:661;;-1:-1:-1;;;36841:7:135;;;36833:16;;36793:661;36917:4;:12;;36925:4;36917:12;36913:541;;-1:-1:-1;;;36961:7:135;;;36953:16;;36913:541;37037:4;:12;;37045:4;37037:12;37033:421;;-1:-1:-1;;;37082:7:135;;;37080:10;37073:17;;37033:421;37159:4;:12;;37167:4;37159:12;37155:299;;37220:2;37202:21;;37208:2;37202:21;;;:29;;37230:1;37202:29;;;37226:1;37202:29;37195:36;;;;;;;;37155:299;37301:4;:12;;37309:4;37301:12;37297:157;;37349:2;37344:7;;:2;:7;;;:15;;37358:1;37344:15;;37297:157;37406:29;;;;;2878:2:305;37406:29:135;;;2860:21:305;2917:2;2897:18;;;2890:30;2956:21;2936:18;;;2929:49;2995:18;;37406:29:135;2676:343:305;37297:157:135;32684:4784;32634:8490;;;37524:6;:14;;37534:4;37524:14;37520:3590;;37583:4;37576:11;;37658:3;37650:11;;;37646:549;;-1:-1:-1;;;37703:21:135;;;37689:36;;37646:549;37810:4;:12;;37818:4;37810:12;:28;;;;37826:4;:12;;37834:4;37826:12;37810:28;37806:389;;;37870:4;:12;;37878:4;37870:12;37866:83;;37919:3;;;37866:83;37974:8;38012:127;38024:10;38019:15;;:20;38012:127;;38104:8;38071:3;38104:8;;;;;38071:3;38012:127;;;38171:1;-1:-1:-1;38164:8:135;;-1:-1:-1;;38164:8:135;37520:3590;38262:6;:14;;38272:4;38262:14;38258:2852;;-1:-1:-1;;38307:8:135;38313:2;38307:8;;;;38300:15;;38258:2852;38382:6;:14;;38392:4;38382:14;38378:2732;;38427:42;38445:2;38450:1;38445:6;38455:1;38444:12;38439:2;:17;38431:26;;:3;:26;;;;38461:4;38430:35;38467:1;38427:2;:42::i;:::-;38420:49;;;;;38378:2732;38536:6;:14;;38546:4;38536:14;38532:2578;;38581:45;38599:2;38604:1;38599:6;38609:1;38598:12;38593:2;:17;38585:26;;:3;:26;;;;38615:6;38584:37;38623:2;38581;:45::i;38532:2578::-;38694:6;:14;;38704:4;38694:14;38690:2420;;-1:-1:-1;;38745:21:135;38764:1;38759;38754:6;;38753:12;38745:21;;38802:36;;;38873:5;38868:10;;38745:21;;;;;38867:18;38860:25;;38690:2420;38952:6;:14;;38962:4;38952:14;38948:2162;;38997:3;38990:10;;;;;38948:2162;39068:6;:14;;39078:4;39068:14;39064:2046;;39128:2;39133:1;39128:6;39138:1;39127:12;39122:2;:17;39114:26;;:3;:26;;;;39144:4;39113:35;39106:42;;;;;39064:2046;39217:6;:14;;39227:4;39217:14;39213:1897;;39277:2;39282:1;39277:6;39287:1;39276:12;39271:2;:17;39263:26;;:3;:26;;;;39293:6;39262:37;39255:44;;;;;39213:1897;39368:6;:14;;39378:4;39368:14;39364:1746;;-1:-1:-1;;39419:26:135;39443:1;39438;39433:6;;39432:12;39427:2;:17;39419:26;;39481:41;;;39557:5;39552:10;;39419:26;;;;;39551:18;39544:25;;39364:1746;39637:6;:14;;39647:4;39637:14;39633:1477;;-1:-1:-1;;39694:4:135;39688:34;39720:1;39715;39710:6;;39709:12;39704:2;:17;39688:34;;39778:27;;;39758:48;;;39836:10;;39689:9;;;39688:34;;39835:18;39828:25;;39633:1477;39921:6;:14;;39931:4;39921:14;39917:1193;;-1:-1:-1;;39978:6:135;39972:36;40006:1;40001;39996:6;;39995:12;39990:2;:17;39972:36;;40064:29;;;40044:50;;;40124:10;;39973:11;;;39972:36;;40123:18;40116:25;;39917:1193;40210:6;:14;;40220:4;40210:14;40206:904;;-1:-1:-1;;40261:20:135;40279:1;40274;40269:6;;40268:12;40261:20;;40317:36;;;40389:5;40383:11;;40261:20;;;;;40382:19;40375:26;;40206:904;40469:6;:14;;40479:4;40469:14;40465:645;;40514:2;40507:9;;;;;40465:645;40585:6;:14;;40595:4;40585:14;40581:529;;-1:-1:-1;;40636:25:135;40659:1;40654;40649:6;;40648:12;40643:2;:17;40636:25;;40697:41;;;40774:5;40768:11;;40636:25;;;;;40767:19;40760:26;;40581:529;40853:6;:14;;40863:4;40853:14;40849:261;;40898:3;40891:10;;;;;40849:261;40968:6;:14;;40978:4;40968:14;40964:146;;41013:2;41006:9;;;32450:8733;;;;;;;:::o;19960:782::-;20046:12;20133:18;;:::i;:::-;-1:-1:-1;20201:4:135;20308:2;20296:14;;;;20288:41;;;;;;;3226:2:305;20288:41:135;;;3208:21:305;3265:2;3245:18;;;3238:30;3304:16;3284:18;;;3277:44;3338:18;;20288:41:135;3024:338:305;20288:41:135;20425:14;;;;;;;:30;;;20443:12;20425:30;20421:102;;;20504:4;20475:5;:15;;;20491:9;20475:26;;;;;;;;;:::i;:::-;:33;;;;:26;;;;;;:33;20421:102;20578:12;;;;;20567:23;;;;:8;;;:23;20634:1;20619:16;;;20604:31;;;20712:13;:11;:13::i;5582:7764::-;5646:12;5732:18;;:::i;:::-;-1:-1:-1;5910:15:135;;:18;;;;5800:4;6070:18;;;;6114;;;;6158;;;;;5800:4;;5890:17;;;;6070:18;6114;6248;;;6262:4;6248:18;6244:6792;;6298:2;6327:4;6322:9;;:14;6318:144;;6438:4;6433:9;;6425:4;:18;6419:24;6318:144;6483:2;:7;;6489:1;6483:7;6479:161;;6519:10;;;;;6551:16;;;;;;;;6519:10;-1:-1:-1;6479:161:135;;;6619:2;6614:7;;6479:161;6268:386;6244:6792;;;6756:10;:18;;6770:4;6756:18;6752:6284;;1745:10;6794:14;;6752:6284;;;6892:10;:18;;6906:4;6892:18;6888:6148;;6935:1;6930:6;;6888:6148;;;7060:10;:18;;7074:4;7060:18;7056:5980;;7113:4;7098:12;;;:19;7135:26;;;:14;;;:26;7186:13;:11;:13::i;:::-;7179:20;5582:7764;-1:-1:-1;;;;;;;;;5582:7764:135:o;7056:5980::-;7325:10;:18;;7339:4;7325:18;7321:5715;;7476:14;;;7472:2723;7321:5715;7472:2723;7646:22;;;;;7642:2553;;7771:10;7784:27;7792:2;7797:10;7792:15;7809:1;7784:7;:27::i;:::-;7895:17;;;;7771:40;;-1:-1:-1;7895:17:135;7873:19;8045:14;8064:1;8039:26;8035:146;;1676:4:136;1670:11;;1533:21;1787:15;;;1828:8;1822:4;1815:22;1850:27;;;1996:4;1983:18;;2098:17;;2003:19;1979:44;2025:11;1976:61;8093:65:135;;8035:146;8267:20;;;;;8234:54;;;;;;;;3540:25:305;;;8234:54:135;3601:23:305;;;3581:18;;;3574:51;8203:11:135;;;;8234:19;:6;:19;;;;3513:18:305;;8234:54:135;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;8202:86;;;;8515:1;8511:2;8507:10;8612:9;8609:1;8605:17;8694:6;8687:5;8684:17;8681:40;;;8714:5;8704:15;;8681:40;;8797:6;8793:2;8790:14;8787:34;;;8817:2;8807:12;;8787:34;8923:3;8918:1;8910:6;8906:14;8901:3;8897:24;8893:34;8886:41;;9023:3;9019:1;9007:9;8998:6;8995:1;8991:14;8987:30;8983:38;8979:48;8972:55;;9178:1;9174;9170;9158:9;9155:1;9151:17;9147:25;9143:33;9139:41;9305:1;9301;9297;9288:6;9276:9;9273:1;9269:17;9265:30;9261:38;9257:46;9253:54;9235:72;;9436:10;9432:15;9426:4;9422:26;9414:34;;9552:3;9544:4;9540:9;9535:3;9531:19;9528:28;9521:35;;;;9698:33;9707:2;9712:10;9707:15;9724:1;9727:3;9698:8;:33::i;:::-;9753:20;;;:38;;;;;;;;;-1:-1:-1;7642:2553:135;;-1:-1:-1;;;7642:2553:135;;9910:18;;;;;9906:289;;10080:2;10075:7;;7321:5715;;9906:289;10134:10;10129:15;;2053:3;10166:10;;9906:289;7321:5715;;;10324:10;:18;;10338:4;10324:18;10320:2716;;10478:15;;;1824:1;10478:15;;:34;;-1:-1:-1;10497:15:135;;;1859:1;10497:15;10478:34;:57;;;-1:-1:-1;10516:19:135;;;1936:1;10516:19;10478:57;10474:1593;;;10564:2;10559:7;;10320:2716;;10474:1593;10690:23;;;;;10686:1381;;10737:10;10750:27;10758:2;10763:10;10758:15;10775:1;10750:7;:27::i;:::-;10853:17;;;;10737:40;;-1:-1:-1;11096:1:135;11088:10;;11190:1;11186:17;11265:13;;;11262:32;;;11287:5;11281:11;;11262:32;11573:14;;;11379:1;11569:22;;;11565:32;;;;11462:26;11486:1;11371:10;;;11466:18;;;11462:26;11561:43;11367:20;;11669:12;11797:17;;;:23;11865:1;11842:20;;;:24;11375:2;-1:-1:-1;11375:2:135;7321:5715;;10320:2716;12269:10;:18;;12283:4;12269:18;12265:771;;12379:2;:7;;12385:1;12379:7;12375:647;;12472:14;;;;;:40;;-1:-1:-1;12490:22:135;;;1978:1;12490:22;12472:40;:62;;;-1:-1:-1;12516:18:135;;;1897:1;12516:18;12472:62;12468:404;;;12567:1;12562:6;;12375:647;;12468:404;12613:15;;;1824:1;12613:15;;:34;;-1:-1:-1;12632:15:135;;;1859:1;12632:15;12613:34;:61;;;-1:-1:-1;12651:23:135;;;2021:1;12651:23;12613:61;:84;;;-1:-1:-1;12678:19:135;;;1936:1;12678:19;12613:84;12609:263;;;12730:1;12725:6;;7321:5715;;12375:647;12923:10;12918:15;;2087:4;12955:11;;12375:647;13111:15;;;;;:23;;;;:18;;;;:23;;;;13148:15;;:23;;;:18;;;;:23;-1:-1:-1;13237:12:135;;;;13226:23;;;:8;;;:23;13293:1;13278:16;13263:31;;;;;13316:13;:11;:13::i;16084:2480::-;16178:12;16264:18;;:::i;:::-;-1:-1:-1;16332:4:135;16364:10;16472:13;;;16481:4;16472:13;16468:1705;;-1:-1:-1;16511:8:135;;;;16468:1705;;;16630:5;:13;;16639:4;16630:13;16626:1547;;16663:14;;;:8;;;:14;16626:1547;;;16793:5;:13;;16802:4;16793:13;16789:1384;;-1:-1:-1;16832:8:135;;;;16789:1384;;;16951:5;:13;;16960:4;16951:13;16947:1226;;16984:14;;;:8;;;:14;16947:1226;;;17125:5;:13;;17134:4;17125:13;17121:1052;;17252:9;17198:17;17178;;;17198;;;;17178:37;17259:2;17252:9;;;;;17234:8;;;:28;17280:22;:8;;;:22;17121:1052;;;17439:5;:13;;17448:4;17439:13;17435:738;;17506:11;17492;;;17506;;;17492:25;17561:2;17554:9;;;;;17536:8;;;:28;17582:22;:8;;;:22;17435:738;;;17763:5;:13;;17772:4;17763:13;17759:414;;17833:3;17814:23;;17820:3;17814:23;;;;;;;:::i;:::-;;17796:42;;:8;;;:42;17874:23;;;;;;;;;;;;;:::i;:::-;;17856:42;;:8;;;:42;17759:414;;;18067:5;:13;;18076:4;18067:13;18063:110;;18117:3;18111:9;;:3;:9;;;;;;;:::i;:::-;;18100:20;;;;:8;;;:20;18149:9;;;;;;;;;;;:::i;:::-;;18138:20;;:8;;;:20;18063:110;18266:14;;;;18262:85;;18329:3;18300:5;:15;;;18316:9;18300:26;;;;;;;;;:::i;:::-;:32;;;;:26;;;;;;:32;18262:85;18401:12;;;;;18390:23;;;;:8;;;:23;18457:1;18442:16;;;18427:31;;;18534:13;:11;:13::i;:::-;18527:20;16084:2480;-1:-1:-1;;;;;;;16084:2480:135:o;23913:1654::-;24089:14;24106:24;24118:11;24106;:24::i;:::-;24089:41;;24238:1;24231:5;24227:13;24224:33;;;24253:1;24250;24243:12;24224:33;24392:2;24586:15;;;24411:2;24400:14;;24388:10;24384:31;24381:1;24377:39;24542:16;;;24327:20;;24527:10;24516:22;;;24512:27;24502:38;24499:60;25028:5;25025:1;25021:13;25099:1;25084:343;25109:2;25106:1;25103:9;25084:343;;;25232:2;25220:15;;;25169:20;25267:12;;;25281:1;25263:20;25304:42;;;;25372:1;25367:42;;;;25256:153;;25304:42;22391:1;22384:12;;;22424:2;22417:13;;;22469:2;22456:16;;25313:31;;25304:42;;25367;22391:1;22384:12;;;22424:2;22417:13;;;22469:2;22456:16;;25376:31;;25256:153;-1:-1:-1;;25127:1:135;25120:9;25084:343;;;-1:-1:-1;;25526:4:135;25519:18;-1:-1:-1;;;;23913:1654:135:o;20946:586::-;21268:20;;;21292:7;21268:32;21261:3;:40;;;21374:14;;21429:17;;21423:24;;;21415:72;;;;;;;4277:2:305;21415:72:135;;;4259:21:305;4316:2;4296:18;;;4289:30;4355:34;4335:18;;;4328:62;4426:5;4406:18;;;4399:33;4449:19;;21415:72:135;4075:399:305;21415:72:135;21501:14;20946:586;;;:::o;-1:-1:-1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;:::o;467:347:305:-;518:8;528:6;582:3;575:4;567:6;563:17;559:27;549:55;;600:1;597;590:12;549:55;-1:-1:-1;623:20:305;;666:18;655:30;;652:50;;;698:1;695;688:12;652:50;735:4;727:6;723:17;711:29;;787:3;780:4;771:6;763;759:19;755:30;752:39;749:59;;;804:1;801;794:12;749:59;467:347;;;;;:::o;819:785::-;918:6;926;934;942;950;1003:2;991:9;982:7;978:23;974:32;971:52;;;1019:1;1016;1009:12;971:52;1059:9;1046:23;1088:18;1129:2;1121:6;1118:14;1115:34;;;1145:1;1142;1135:12;1115:34;1184:58;1234:7;1225:6;1214:9;1210:22;1184:58;:::i;:::-;1261:8;;-1:-1:-1;1158:84:305;-1:-1:-1;1349:2:305;1334:18;;1321:32;;-1:-1:-1;1365:16:305;;;1362:36;;;1394:1;1391;1384:12;1362:36;;1433:60;1485:7;1474:8;1463:9;1459:24;1433:60;:::i;:::-;819:785;;;;-1:-1:-1;1512:8:305;1594:2;1579:18;1566:32;;819:785;-1:-1:-1;;;;819:785:305:o;1791:184::-;1843:77;1840:1;1833:88;1940:4;1937:1;1930:15;1964:4;1961:1;1954:15;3636:245;3715:6;3723;3776:2;3764:9;3755:7;3751:23;3747:32;3744:52;;;3792:1;3789;3782:12;3744:52;-1:-1:-1;;3815:16:305;;3871:2;3856:18;;;3850:25;3815:16;;3850:25;;-1:-1:-1;3636:245:305:o;3886:184::-;3938:77;3935:1;3928:88;4035:4;4032:1;4025:15;4059:4;4056:1;4049:15"
func init() { func init() {
if err := json.Unmarshal([]byte(MIPSStorageLayoutJSON), MIPSStorageLayout); err != nil { if err := json.Unmarshal([]byte(MIPSStorageLayoutJSON), MIPSStorageLayout); err != nil {
......
...@@ -15,7 +15,7 @@ var PreimageOracleStorageLayout = new(solc.StorageLayout) ...@@ -15,7 +15,7 @@ var PreimageOracleStorageLayout = new(solc.StorageLayout)
var PreimageOracleDeployedBin = "0x608060405234801561001057600080fd5b50600436106100725760003560e01c8063e03110e111610050578063e03110e114610106578063e15926111461012e578063fef2b4ed1461014357600080fd5b806361238bde146100775780638542cf50146100b5578063c0c220c9146100f3575b600080fd5b6100a26100853660046104df565b600160209081526000928352604080842090915290825290205481565b6040519081526020015b60405180910390f35b6100e36100c33660046104df565b600260209081526000928352604080842090915290825290205460ff1681565b60405190151581526020016100ac565b6100a2610101366004610501565b610163565b6101196101143660046104df565b610238565b604080519283526020830191909152016100ac565b61014161013c36600461053c565b610329565b005b6100a26101513660046105b8565b60006020819052908152604090205481565b600061016f8686610432565b905061017c836008610600565b8211806101895750602083115b156101c0576040517ffe25498700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000602081815260c085901b82526008959095528251828252600286526040808320858452875280832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660019081179091558484528752808320948352938652838220558181529384905292205592915050565b6000828152600260209081526040808320848452909152812054819060ff166102c1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f7072652d696d616765206d757374206578697374000000000000000000000000604482015260640160405180910390fd5b50600083815260208181526040909120546102dd816008610600565b6102e8856020610600565b1061030657836102f9826008610600565b6103039190610618565b91505b506000938452600160209081526040808620948652939052919092205492909150565b604435600080600883018611156103485763fe2549876000526004601cfd5b60c083901b6080526088838682378087017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80151908490207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f02000000000000000000000000000000000000000000000000000000000000001760008181526002602090815260408083208b8452825280832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600190811790915584845282528083209a83529981528982209390935590815290819052959095209190915550505050565b7f01000000000000000000000000000000000000000000000000000000000000007effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8316176104d8818360408051600093845233602052918152606090922091527effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01000000000000000000000000000000000000000000000000000000000000001790565b9392505050565b600080604083850312156104f257600080fd5b50508035926020909101359150565b600080600080600060a0868803121561051957600080fd5b505083359560208501359550604085013594606081013594506080013592509050565b60008060006040848603121561055157600080fd5b83359250602084013567ffffffffffffffff8082111561057057600080fd5b818601915086601f83011261058457600080fd5b81358181111561059357600080fd5b8760208285010111156105a557600080fd5b6020830194508093505050509250925092565b6000602082840312156105ca57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60008219821115610613576106136105d1565b500190565b60008282101561062a5761062a6105d1565b50039056fea164736f6c634300080f000a" var PreimageOracleDeployedBin = "0x608060405234801561001057600080fd5b50600436106100725760003560e01c8063e03110e111610050578063e03110e114610106578063e15926111461012e578063fef2b4ed1461014357600080fd5b806361238bde146100775780638542cf50146100b5578063c0c220c9146100f3575b600080fd5b6100a26100853660046104df565b600160209081526000928352604080842090915290825290205481565b6040519081526020015b60405180910390f35b6100e36100c33660046104df565b600260209081526000928352604080842090915290825290205460ff1681565b60405190151581526020016100ac565b6100a2610101366004610501565b610163565b6101196101143660046104df565b610238565b604080519283526020830191909152016100ac565b61014161013c36600461053c565b610329565b005b6100a26101513660046105b8565b60006020819052908152604090205481565b600061016f8686610432565b905061017c836008610600565b8211806101895750602083115b156101c0576040517ffe25498700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000602081815260c085901b82526008959095528251828252600286526040808320858452875280832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660019081179091558484528752808320948352938652838220558181529384905292205592915050565b6000828152600260209081526040808320848452909152812054819060ff166102c1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f7072652d696d616765206d757374206578697374000000000000000000000000604482015260640160405180910390fd5b50600083815260208181526040909120546102dd816008610600565b6102e8856020610600565b1061030657836102f9826008610600565b6103039190610618565b91505b506000938452600160209081526040808620948652939052919092205492909150565b604435600080600883018611156103485763fe2549876000526004601cfd5b60c083901b6080526088838682378087017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80151908490207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f02000000000000000000000000000000000000000000000000000000000000001760008181526002602090815260408083208b8452825280832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600190811790915584845282528083209a83529981528982209390935590815290819052959095209190915550505050565b7f01000000000000000000000000000000000000000000000000000000000000007effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8316176104d8818360408051600093845233602052918152606090922091527effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01000000000000000000000000000000000000000000000000000000000000001790565b9392505050565b600080604083850312156104f257600080fd5b50508035926020909101359150565b600080600080600060a0868803121561051957600080fd5b505083359560208501359550604085013594606081013594506080013592509050565b60008060006040848603121561055157600080fd5b83359250602084013567ffffffffffffffff8082111561057057600080fd5b818601915086601f83011261058457600080fd5b81358181111561059357600080fd5b8760208285010111156105a557600080fd5b6020830194508093505050509250925092565b6000602082840312156105ca57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60008219821115610613576106136105d1565b500190565b60008282101561062a5761062a6105d1565b50039056fea164736f6c634300080f000a"
var PreimageOracleDeployedSourceMap = "306:3911:137:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;537:68;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;413:25:304;;;401:2;386:18;537:68:137;;;;;;;;680:66;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;614:14:304;;607:22;589:41;;577:2;562:18;680:66:137;449:187:304;1367:1211:137;;;;;;:::i;:::-;;:::i;789:536::-;;;;;;:::i;:::-;;:::i;:::-;;;;1274:25:304;;;1330:2;1315:18;;1308:34;;;;1247:18;789:536:137;1100:248:304;2620:1595:137;;;;;;:::i;:::-;;:::i;:::-;;419:50;;;;;;:::i;:::-;;;;;;;;;;;;;;;1367:1211;1560:12;1665:51;1694:6;1702:13;1665:28;:51::i;:::-;1658:58;-1:-1:-1;1810:9:137;:5;1818:1;1810:9;:::i;:::-;1796:11;:23;:37;;;;1831:2;1823:5;:10;1796:37;1792:90;;;1856:15;;;;;;;;;;;;;;1792:90;1951:12;2051:4;2044:18;;;2152:3;2148:15;;;2135:29;;2184:4;2177:19;;;;2286:18;;2376:20;;;:14;:20;;;;;;:33;;;;;;;;:40;;;;2412:4;2376:40;;;;;;2426:19;;;;;;;;:32;;;;;;;;;:39;2542:21;;;;;;;;;:29;2391:4;1367:1211;-1:-1:-1;;1367:1211:137:o;789:536::-;865:12;914:20;;;:14;:20;;;;;;;;:29;;;;;;;;;865:12;;914:29;;906:62;;;;;;;2908:2:304;906:62:137;;;2890:21:304;2947:2;2927:18;;;2920:30;2986:22;2966:18;;;2959:50;3026:18;;906:62:137;;;;;;;;-1:-1:-1;1099:14:137;1116:21;;;1087:2;1116:21;;;;;;;;1167:10;1116:21;1176:1;1167:10;:::i;:::-;1151:12;:7;1161:2;1151:12;:::i;:::-;:26;1147:87;;1216:7;1203:10;:6;1212:1;1203:10;:::i;:::-;:20;;;;:::i;:::-;1193:30;;1147:87;-1:-1:-1;1290:19:137;;;;:13;:19;;;;;;;;:28;;;;;;;;;;;;789:536;;-1:-1:-1;789:536:137:o;2620:1595::-;2916:4;2903:18;2721:12;;3045:1;3035:12;;3019:29;;3016:210;;;3120:10;3117:1;3110:21;3210:1;3204:4;3197:15;3016:210;3469:3;3465:14;;;3369:4;3453:27;3500:11;3474:4;3619:16;3500:11;3601:41;3832:29;;;3836:11;3832:29;3826:36;3884:20;;;;4031:19;4024:27;4053:11;4021:44;4084:19;;;;4062:1;4084:19;;;;;;;;:32;;;;;;;;:39;;;;4119:4;4084:39;;;;;;4133:18;;;;;;;;:31;;;;;;;;;:38;;;;4181:20;;;;;;;;;;;:27;;;;-1:-1:-1;;;;2620:1595:137:o;552:449:136:-;835:11;860:19;848:32;;832:49;965:29;832:49;980:13;1676:4;1670:11;;1533:21;1787:15;;;1828:8;1822:4;1815:22;1850:27;;;1996:4;1983:18;;;2098:17;;2003:19;1979:44;2025:11;1976:61;;1455:676;965:29;958:36;552:449;-1:-1:-1;;;552:449:136:o;14:248:304:-;82:6;90;143:2;131:9;122:7;118:23;114:32;111:52;;;159:1;156;149:12;111:52;-1:-1:-1;;182:23:304;;;252:2;237:18;;;224:32;;-1:-1:-1;14:248:304:o;641:454::-;736:6;744;752;760;768;821:3;809:9;800:7;796:23;792:33;789:53;;;838:1;835;828:12;789:53;-1:-1:-1;;861:23:304;;;931:2;916:18;;903:32;;-1:-1:-1;982:2:304;967:18;;954:32;;1033:2;1018:18;;1005:32;;-1:-1:-1;1084:3:304;1069:19;1056:33;;-1:-1:-1;641:454:304;-1:-1:-1;641:454:304:o;1353:659::-;1432:6;1440;1448;1501:2;1489:9;1480:7;1476:23;1472:32;1469:52;;;1517:1;1514;1507:12;1469:52;1553:9;1540:23;1530:33;;1614:2;1603:9;1599:18;1586:32;1637:18;1678:2;1670:6;1667:14;1664:34;;;1694:1;1691;1684:12;1664:34;1732:6;1721:9;1717:22;1707:32;;1777:7;1770:4;1766:2;1762:13;1758:27;1748:55;;1799:1;1796;1789:12;1748:55;1839:2;1826:16;1865:2;1857:6;1854:14;1851:34;;;1881:1;1878;1871:12;1851:34;1926:7;1921:2;1912:6;1908:2;1904:15;1900:24;1897:37;1894:57;;;1947:1;1944;1937:12;1894:57;1978:2;1974;1970:11;1960:21;;2000:6;1990:16;;;;;1353:659;;;;;:::o;2017:180::-;2076:6;2129:2;2117:9;2108:7;2104:23;2100:32;2097:52;;;2145:1;2142;2135:12;2097:52;-1:-1:-1;2168:23:304;;2017:180;-1:-1:-1;2017:180:304:o;2384:184::-;2436:77;2433:1;2426:88;2533:4;2530:1;2523:15;2557:4;2554:1;2547:15;2573:128;2613:3;2644:1;2640:6;2637:1;2634:13;2631:39;;;2650:18;;:::i;:::-;-1:-1:-1;2686:9:304;;2573:128::o;3055:125::-;3095:4;3123:1;3120;3117:8;3114:34;;;3128:18;;:::i;:::-;-1:-1:-1;3165:9:304;;3055:125::o" var PreimageOracleDeployedSourceMap = "306:3911:137:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;537:68;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;413:25:305;;;401:2;386:18;537:68:137;;;;;;;;680:66;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;614:14:305;;607:22;589:41;;577:2;562:18;680:66:137;449:187:305;1367:1211:137;;;;;;:::i;:::-;;:::i;789:536::-;;;;;;:::i;:::-;;:::i;:::-;;;;1274:25:305;;;1330:2;1315:18;;1308:34;;;;1247:18;789:536:137;1100:248:305;2620:1595:137;;;;;;:::i;:::-;;:::i;:::-;;419:50;;;;;;:::i;:::-;;;;;;;;;;;;;;;1367:1211;1560:12;1665:51;1694:6;1702:13;1665:28;:51::i;:::-;1658:58;-1:-1:-1;1810:9:137;:5;1818:1;1810:9;:::i;:::-;1796:11;:23;:37;;;;1831:2;1823:5;:10;1796:37;1792:90;;;1856:15;;;;;;;;;;;;;;1792:90;1951:12;2051:4;2044:18;;;2152:3;2148:15;;;2135:29;;2184:4;2177:19;;;;2286:18;;2376:20;;;:14;:20;;;;;;:33;;;;;;;;:40;;;;2412:4;2376:40;;;;;;2426:19;;;;;;;;:32;;;;;;;;;:39;2542:21;;;;;;;;;:29;2391:4;1367:1211;-1:-1:-1;;1367:1211:137:o;789:536::-;865:12;914:20;;;:14;:20;;;;;;;;:29;;;;;;;;;865:12;;914:29;;906:62;;;;;;;2908:2:305;906:62:137;;;2890:21:305;2947:2;2927:18;;;2920:30;2986:22;2966:18;;;2959:50;3026:18;;906:62:137;;;;;;;;-1:-1:-1;1099:14:137;1116:21;;;1087:2;1116:21;;;;;;;;1167:10;1116:21;1176:1;1167:10;:::i;:::-;1151:12;:7;1161:2;1151:12;:::i;:::-;:26;1147:87;;1216:7;1203:10;:6;1212:1;1203:10;:::i;:::-;:20;;;;:::i;:::-;1193:30;;1147:87;-1:-1:-1;1290:19:137;;;;:13;:19;;;;;;;;:28;;;;;;;;;;;;789:536;;-1:-1:-1;789:536:137:o;2620:1595::-;2916:4;2903:18;2721:12;;3045:1;3035:12;;3019:29;;3016:210;;;3120:10;3117:1;3110:21;3210:1;3204:4;3197:15;3016:210;3469:3;3465:14;;;3369:4;3453:27;3500:11;3474:4;3619:16;3500:11;3601:41;3832:29;;;3836:11;3832:29;3826:36;3884:20;;;;4031:19;4024:27;4053:11;4021:44;4084:19;;;;4062:1;4084:19;;;;;;;;:32;;;;;;;;:39;;;;4119:4;4084:39;;;;;;4133:18;;;;;;;;:31;;;;;;;;;:38;;;;4181:20;;;;;;;;;;;:27;;;;-1:-1:-1;;;;2620:1595:137:o;552:449:136:-;835:11;860:19;848:32;;832:49;965:29;832:49;980:13;1676:4;1670:11;;1533:21;1787:15;;;1828:8;1822:4;1815:22;1850:27;;;1996:4;1983:18;;;2098:17;;2003:19;1979:44;2025:11;1976:61;;1455:676;965:29;958:36;552:449;-1:-1:-1;;;552:449:136:o;14:248:305:-;82:6;90;143:2;131:9;122:7;118:23;114:32;111:52;;;159:1;156;149:12;111:52;-1:-1:-1;;182:23:305;;;252:2;237:18;;;224:32;;-1:-1:-1;14:248:305:o;641:454::-;736:6;744;752;760;768;821:3;809:9;800:7;796:23;792:33;789:53;;;838:1;835;828:12;789:53;-1:-1:-1;;861:23:305;;;931:2;916:18;;903:32;;-1:-1:-1;982:2:305;967:18;;954:32;;1033:2;1018:18;;1005:32;;-1:-1:-1;1084:3:305;1069:19;1056:33;;-1:-1:-1;641:454:305;-1:-1:-1;641:454:305:o;1353:659::-;1432:6;1440;1448;1501:2;1489:9;1480:7;1476:23;1472:32;1469:52;;;1517:1;1514;1507:12;1469:52;1553:9;1540:23;1530:33;;1614:2;1603:9;1599:18;1586:32;1637:18;1678:2;1670:6;1667:14;1664:34;;;1694:1;1691;1684:12;1664:34;1732:6;1721:9;1717:22;1707:32;;1777:7;1770:4;1766:2;1762:13;1758:27;1748:55;;1799:1;1796;1789:12;1748:55;1839:2;1826:16;1865:2;1857:6;1854:14;1851:34;;;1881:1;1878;1871:12;1851:34;1926:7;1921:2;1912:6;1908:2;1904:15;1900:24;1897:37;1894:57;;;1947:1;1944;1937:12;1894:57;1978:2;1974;1970:11;1960:21;;2000:6;1990:16;;;;;1353:659;;;;;:::o;2017:180::-;2076:6;2129:2;2117:9;2108:7;2104:23;2100:32;2097:52;;;2145:1;2142;2135:12;2097:52;-1:-1:-1;2168:23:305;;2017:180;-1:-1:-1;2017:180:305:o;2384:184::-;2436:77;2433:1;2426:88;2533:4;2530:1;2523:15;2557:4;2554:1;2547:15;2573:128;2613:3;2644:1;2640:6;2637:1;2634:13;2631:39;;;2650:18;;:::i;:::-;-1:-1:-1;2686:9:305;;2573:128::o;3055:125::-;3095:4;3123:1;3120;3117:8;3114:34;;;3128:18;;:::i;:::-;-1:-1:-1;3165:9:305;;3055:125::o"
func init() { func init() {
if err := json.Unmarshal([]byte(PreimageOracleStorageLayoutJSON), PreimageOracleStorageLayout); err != nil { if err := json.Unmarshal([]byte(PreimageOracleStorageLayoutJSON), PreimageOracleStorageLayout); err != nil {
......
...@@ -112,7 +112,7 @@ func entrypoint(ctx *cli.Context) error { ...@@ -112,7 +112,7 @@ func entrypoint(ctx *cli.Context) error {
name, _ := toDeployConfigName(chainConfig) name, _ := toDeployConfigName(chainConfig)
config, err := genesis.NewDeployConfigWithNetwork(name, deployConfig) config, err := genesis.NewDeployConfigWithNetwork(name, deployConfig)
if err != nil { if err != nil {
log.Warn("Cannot find deploy config for network", "name", chainConfig.Name, "deploy-config-name", name, "path", deployConfig) log.Warn("Cannot find deploy config for network", "name", chainConfig.Name, "deploy-config-name", name, "path", deployConfig, "err", err)
} }
if config != nil { if config != nil {
......
...@@ -39,7 +39,7 @@ func NewFaultDisputeGameContract(addr common.Address, caller *batching.MultiCall ...@@ -39,7 +39,7 @@ func NewFaultDisputeGameContract(addr common.Address, caller *batching.MultiCall
} }
func (f *FaultDisputeGameContract) GetGameDuration(ctx context.Context) (uint64, error) { func (f *FaultDisputeGameContract) GetGameDuration(ctx context.Context) (uint64, error) {
result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call(methodGameDuration)) result, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, f.contract.Call(methodGameDuration))
if err != nil { if err != nil {
return 0, fmt.Errorf("failed to fetch game duration: %w", err) return 0, fmt.Errorf("failed to fetch game duration: %w", err)
} }
...@@ -47,7 +47,7 @@ func (f *FaultDisputeGameContract) GetGameDuration(ctx context.Context) (uint64, ...@@ -47,7 +47,7 @@ func (f *FaultDisputeGameContract) GetGameDuration(ctx context.Context) (uint64,
} }
func (f *FaultDisputeGameContract) GetMaxGameDepth(ctx context.Context) (uint64, error) { func (f *FaultDisputeGameContract) GetMaxGameDepth(ctx context.Context) (uint64, error) {
result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call(methodMaxGameDepth)) result, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, f.contract.Call(methodMaxGameDepth))
if err != nil { if err != nil {
return 0, fmt.Errorf("failed to fetch max game depth: %w", err) return 0, fmt.Errorf("failed to fetch max game depth: %w", err)
} }
...@@ -55,7 +55,7 @@ func (f *FaultDisputeGameContract) GetMaxGameDepth(ctx context.Context) (uint64, ...@@ -55,7 +55,7 @@ func (f *FaultDisputeGameContract) GetMaxGameDepth(ctx context.Context) (uint64,
} }
func (f *FaultDisputeGameContract) GetAbsolutePrestateHash(ctx context.Context) (common.Hash, error) { func (f *FaultDisputeGameContract) GetAbsolutePrestateHash(ctx context.Context) (common.Hash, error) {
result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call(methodAbsolutePrestate)) result, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, f.contract.Call(methodAbsolutePrestate))
if err != nil { if err != nil {
return common.Hash{}, fmt.Errorf("failed to fetch absolute prestate hash: %w", err) return common.Hash{}, fmt.Errorf("failed to fetch absolute prestate hash: %w", err)
} }
...@@ -63,7 +63,7 @@ func (f *FaultDisputeGameContract) GetAbsolutePrestateHash(ctx context.Context) ...@@ -63,7 +63,7 @@ func (f *FaultDisputeGameContract) GetAbsolutePrestateHash(ctx context.Context)
} }
func (f *FaultDisputeGameContract) GetStatus(ctx context.Context) (gameTypes.GameStatus, error) { func (f *FaultDisputeGameContract) GetStatus(ctx context.Context) (gameTypes.GameStatus, error) {
result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call(methodStatus)) result, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, f.contract.Call(methodStatus))
if err != nil { if err != nil {
return 0, fmt.Errorf("failed to fetch status: %w", err) return 0, fmt.Errorf("failed to fetch status: %w", err)
} }
...@@ -71,7 +71,7 @@ func (f *FaultDisputeGameContract) GetStatus(ctx context.Context) (gameTypes.Gam ...@@ -71,7 +71,7 @@ func (f *FaultDisputeGameContract) GetStatus(ctx context.Context) (gameTypes.Gam
} }
func (f *FaultDisputeGameContract) GetClaimCount(ctx context.Context) (uint64, error) { func (f *FaultDisputeGameContract) GetClaimCount(ctx context.Context) (uint64, error) {
result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call(methodClaimCount)) result, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, f.contract.Call(methodClaimCount))
if err != nil { if err != nil {
return 0, fmt.Errorf("failed to fetch claim count: %w", err) return 0, fmt.Errorf("failed to fetch claim count: %w", err)
} }
...@@ -79,7 +79,7 @@ func (f *FaultDisputeGameContract) GetClaimCount(ctx context.Context) (uint64, e ...@@ -79,7 +79,7 @@ func (f *FaultDisputeGameContract) GetClaimCount(ctx context.Context) (uint64, e
} }
func (f *FaultDisputeGameContract) GetClaim(ctx context.Context, idx uint64) (types.Claim, error) { func (f *FaultDisputeGameContract) GetClaim(ctx context.Context, idx uint64) (types.Claim, error) {
result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call(methodClaim, new(big.Int).SetUint64(idx))) result, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, f.contract.Call(methodClaim, new(big.Int).SetUint64(idx)))
if err != nil { if err != nil {
return types.Claim{}, fmt.Errorf("failed to fetch claim %v: %w", idx, err) return types.Claim{}, fmt.Errorf("failed to fetch claim %v: %w", idx, err)
} }
...@@ -97,7 +97,7 @@ func (f *FaultDisputeGameContract) GetAllClaims(ctx context.Context) ([]types.Cl ...@@ -97,7 +97,7 @@ func (f *FaultDisputeGameContract) GetAllClaims(ctx context.Context) ([]types.Cl
calls[i] = f.contract.Call(methodClaim, new(big.Int).SetUint64(i)) calls[i] = f.contract.Call(methodClaim, new(big.Int).SetUint64(i))
} }
results, err := f.multiCaller.CallLatest(ctx, calls...) results, err := f.multiCaller.Call(ctx, batching.BlockLatest, calls...)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to fetch claim data: %w", err) return nil, fmt.Errorf("failed to fetch claim data: %w", err)
} }
......
...@@ -65,7 +65,7 @@ func TestSimpleGetters(t *testing.T) { ...@@ -65,7 +65,7 @@ func TestSimpleGetters(t *testing.T) {
test := test test := test
t.Run(test.method, func(t *testing.T) { t.Run(test.method, func(t *testing.T) {
stubRpc, game := setup(t) stubRpc, game := setup(t)
stubRpc.SetResponse(test.method, nil, []interface{}{test.result}) stubRpc.SetResponse(test.method, batching.BlockLatest, nil, []interface{}{test.result})
status, err := test.call(game) status, err := test.call(game)
require.NoError(t, err) require.NoError(t, err)
expected := test.expected expected := test.expected
...@@ -85,7 +85,7 @@ func TestGetClaim(t *testing.T) { ...@@ -85,7 +85,7 @@ func TestGetClaim(t *testing.T) {
value := common.Hash{0xab} value := common.Hash{0xab}
position := big.NewInt(2) position := big.NewInt(2)
clock := big.NewInt(1234) clock := big.NewInt(1234)
stubRpc.SetResponse(methodClaim, []interface{}{idx}, []interface{}{parentIndex, countered, value, position, clock}) stubRpc.SetResponse(methodClaim, batching.BlockLatest, []interface{}{idx}, []interface{}{parentIndex, countered, value, position, clock})
status, err := game.GetClaim(context.Background(), idx.Uint64()) status, err := game.GetClaim(context.Background(), idx.Uint64())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, faultTypes.Claim{ require.Equal(t, faultTypes.Claim{
...@@ -133,7 +133,7 @@ func TestGetAllClaims(t *testing.T) { ...@@ -133,7 +133,7 @@ func TestGetAllClaims(t *testing.T) {
ParentContractIndex: 1, ParentContractIndex: 1,
} }
expectedClaims := []faultTypes.Claim{claim0, claim1, claim2} expectedClaims := []faultTypes.Claim{claim0, claim1, claim2}
stubRpc.SetResponse(methodClaimCount, nil, []interface{}{big.NewInt(int64(len(expectedClaims)))}) stubRpc.SetResponse(methodClaimCount, batching.BlockLatest, nil, []interface{}{big.NewInt(int64(len(expectedClaims)))})
for _, claim := range expectedClaims { for _, claim := range expectedClaims {
expectGetClaim(stubRpc, claim) expectGetClaim(stubRpc, claim)
} }
...@@ -145,6 +145,7 @@ func TestGetAllClaims(t *testing.T) { ...@@ -145,6 +145,7 @@ func TestGetAllClaims(t *testing.T) {
func expectGetClaim(stubRpc *batchingTest.AbiBasedRpc, claim faultTypes.Claim) { func expectGetClaim(stubRpc *batchingTest.AbiBasedRpc, claim faultTypes.Claim) {
stubRpc.SetResponse( stubRpc.SetResponse(
methodClaim, methodClaim,
batching.BlockLatest,
[]interface{}{big.NewInt(int64(claim.ContractIndex))}, []interface{}{big.NewInt(int64(claim.ContractIndex))},
[]interface{}{ []interface{}{
uint32(claim.ParentContractIndex), uint32(claim.ParentContractIndex),
......
package contracts
import (
"context"
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum/go-ethereum/common"
)
const (
methodGameCount = "gameCount"
methodGameAtIndex = "gameAtIndex"
)
type DisputeGameFactoryContract struct {
multiCaller *batching.MultiCaller
contract *batching.BoundContract
}
func NewDisputeGameFactoryContract(addr common.Address, caller *batching.MultiCaller) (*DisputeGameFactoryContract, error) {
factoryAbi, err := bindings.DisputeGameFactoryMetaData.GetAbi()
if err != nil {
return nil, fmt.Errorf("failed to load dispute game factory ABI: %w", err)
}
return &DisputeGameFactoryContract{
multiCaller: caller,
contract: batching.NewBoundContract(factoryAbi, addr),
}, nil
}
func (f *DisputeGameFactoryContract) GetGameCount(ctx context.Context, blockNum uint64) (uint64, error) {
result, err := f.multiCaller.SingleCall(ctx, batching.BlockByNumber(blockNum), f.contract.Call(methodGameCount))
if err != nil {
return 0, fmt.Errorf("failed to load game count: %w", err)
}
return result.GetBigInt(0).Uint64(), nil
}
func (f *DisputeGameFactoryContract) GetGame(ctx context.Context, idx uint64, blockNum uint64) (types.GameMetadata, error) {
result, err := f.multiCaller.SingleCall(ctx, batching.BlockByNumber(blockNum), f.contract.Call(methodGameAtIndex, new(big.Int).SetUint64(idx)))
if err != nil {
return types.GameMetadata{}, fmt.Errorf("failed to load game %v: %w", idx, err)
}
return f.decodeGame(result), nil
}
func (f *DisputeGameFactoryContract) decodeGame(result *batching.CallResult) types.GameMetadata {
gameType := result.GetUint8(0)
timestamp := result.GetUint64(1)
proxy := result.GetAddress(2)
return types.GameMetadata{
GameType: gameType,
Timestamp: timestamp,
Proxy: proxy,
}
}
package contracts
import (
"context"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
batchingTest "github.com/ethereum-optimism/optimism/op-service/sources/batching/test"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func TestDisputeGameFactorySimpleGetters(t *testing.T) {
blockNum := uint64(23)
tests := []struct {
method string
args []interface{}
result interface{}
expected interface{} // Defaults to expecting the same as result
call func(game *DisputeGameFactoryContract) (any, error)
}{
{
method: methodGameCount,
result: big.NewInt(9876),
expected: uint64(9876),
call: func(game *DisputeGameFactoryContract) (any, error) {
return game.GetGameCount(context.Background(), blockNum)
},
},
}
for _, test := range tests {
test := test
t.Run(test.method, func(t *testing.T) {
stubRpc, factory := setupDisputeGameFactoryTest(t)
stubRpc.SetResponse(test.method, batching.BlockByNumber(blockNum), nil, []interface{}{test.result})
status, err := test.call(factory)
require.NoError(t, err)
expected := test.expected
if expected == nil {
expected = test.result
}
require.Equal(t, expected, status)
})
}
}
func TestLoadGame(t *testing.T) {
blockNum := uint64(23)
stubRpc, factory := setupDisputeGameFactoryTest(t)
game0 := types.GameMetadata{
GameType: 0,
Timestamp: 1234,
Proxy: common.Address{0xaa},
}
game1 := types.GameMetadata{
GameType: 1,
Timestamp: 5678,
Proxy: common.Address{0xbb},
}
game2 := types.GameMetadata{
GameType: 99,
Timestamp: 9988,
Proxy: common.Address{0xcc},
}
expectedGames := []types.GameMetadata{game0, game1, game2}
for idx, expected := range expectedGames {
expectGetGame(stubRpc, idx, blockNum, expected)
actual, err := factory.GetGame(context.Background(), uint64(idx), blockNum)
require.NoError(t, err)
require.Equal(t, expected, actual)
}
}
func expectGetGame(stubRpc *batchingTest.AbiBasedRpc, idx int, blockNum uint64, game types.GameMetadata) {
stubRpc.SetResponse(
methodGameAtIndex,
batching.BlockByNumber(blockNum),
[]interface{}{big.NewInt(int64(idx))},
[]interface{}{
game.GameType,
game.Timestamp,
game.Proxy,
})
}
func setupDisputeGameFactoryTest(t *testing.T) (*batchingTest.AbiBasedRpc, *DisputeGameFactoryContract) {
fdgAbi, err := bindings.DisputeGameFactoryMetaData.GetAbi()
require.NoError(t, err)
address := common.HexToAddress("0x24112842371dFC380576ebb09Ae16Cb6B6caD7CB")
stubRpc := batchingTest.NewAbiBasedRpc(t, fdgAbi, address)
caller := batching.NewMultiCaller(stubRpc, 100)
factory, err := NewDisputeGameFactoryContract(address, caller)
require.NoError(t, err)
return stubRpc, factory
}
...@@ -47,7 +47,7 @@ func NewGamePlayer( ...@@ -47,7 +47,7 @@ func NewGamePlayer(
creator resourceCreator, creator resourceCreator,
) (*GamePlayer, error) { ) (*GamePlayer, error) {
logger = logger.New("game", addr) logger = logger.New("game", addr)
loader, err := contracts.NewFaultDisputeGameContract(addr, batching.NewMultiCaller(client.Client(), 100)) loader, err := contracts.NewFaultDisputeGameContract(addr, batching.NewMultiCaller(client.Client(), batching.DefaultBatchSize))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create fault dispute game contract wrapper: %w", err) return nil, fmt.Errorf("failed to create fault dispute game contract wrapper: %w", err)
} }
......
...@@ -4,11 +4,8 @@ import ( ...@@ -4,11 +4,8 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
) )
var ( var (
...@@ -18,12 +15,8 @@ var ( ...@@ -18,12 +15,8 @@ var (
// MinimalDisputeGameFactoryCaller is a minimal interface around [bindings.DisputeGameFactoryCaller]. // MinimalDisputeGameFactoryCaller is a minimal interface around [bindings.DisputeGameFactoryCaller].
// This needs to be updated if the [bindings.DisputeGameFactoryCaller] interface changes. // This needs to be updated if the [bindings.DisputeGameFactoryCaller] interface changes.
type MinimalDisputeGameFactoryCaller interface { type MinimalDisputeGameFactoryCaller interface {
GameCount(opts *bind.CallOpts) (*big.Int, error) GetGameCount(ctx context.Context, blockNum uint64) (uint64, error)
GameAtIndex(opts *bind.CallOpts, _index *big.Int) (struct { GetGame(ctx context.Context, idx uint64, blockNum uint64) (types.GameMetadata, error)
GameType uint8
Timestamp uint64
Proxy common.Address
}, error)
} }
type GameLoader struct { type GameLoader struct {
...@@ -38,27 +31,17 @@ func NewGameLoader(caller MinimalDisputeGameFactoryCaller) *GameLoader { ...@@ -38,27 +31,17 @@ func NewGameLoader(caller MinimalDisputeGameFactoryCaller) *GameLoader {
} }
// FetchAllGamesAtBlock fetches all dispute games from the factory at a given block number. // FetchAllGamesAtBlock fetches all dispute games from the factory at a given block number.
func (l *GameLoader) FetchAllGamesAtBlock(ctx context.Context, earliestTimestamp uint64, blockNumber *big.Int) ([]types.GameMetadata, error) { func (l *GameLoader) FetchAllGamesAtBlock(ctx context.Context, earliestTimestamp uint64, blockNumber uint64) ([]types.GameMetadata, error) {
if blockNumber == nil { gameCount, err := l.caller.GetGameCount(ctx, blockNumber)
return nil, ErrMissingBlockNumber
}
callOpts := &bind.CallOpts{
Context: ctx,
BlockNumber: blockNumber,
}
gameCount, err := l.caller.GameCount(callOpts)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to fetch game count: %w", err) return nil, fmt.Errorf("failed to fetch game count: %w", err)
} }
games := make([]types.GameMetadata, 0) games := make([]types.GameMetadata, 0, gameCount)
if gameCount.Uint64() == 0 { for i := gameCount; i > 0; i-- {
return games, nil game, err := l.caller.GetGame(ctx, i-1, blockNumber)
}
for i := gameCount.Uint64(); i > 0; i-- {
game, err := l.caller.GameAtIndex(callOpts, big.NewInt(int64(i-1)))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to fetch game at index %d: %w", i, err) return nil, fmt.Errorf("failed to fetch game at index %d: %w", i-1, err)
} }
if game.Timestamp < earliestTimestamp { if game.Timestamp < earliestTimestamp {
break break
......
...@@ -7,7 +7,6 @@ import ( ...@@ -7,7 +7,6 @@ import (
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -25,44 +24,39 @@ func TestGameLoader_FetchAllGames(t *testing.T) { ...@@ -25,44 +24,39 @@ func TestGameLoader_FetchAllGames(t *testing.T) {
name string name string
caller *mockMinimalDisputeGameFactoryCaller caller *mockMinimalDisputeGameFactoryCaller
earliest uint64 earliest uint64
blockNumber *big.Int blockNumber uint64
expectedErr error expectedErr error
expectedLen int expectedLen int
}{ }{
{ {
name: "success", name: "success",
caller: newMockMinimalDisputeGameFactoryCaller(10, false, false), caller: newMockMinimalDisputeGameFactoryCaller(10, false, false),
blockNumber: big.NewInt(1), blockNumber: 1,
expectedLen: 10, expectedLen: 10,
}, },
{ {
name: "expired game ignored", name: "expired game ignored",
caller: newMockMinimalDisputeGameFactoryCaller(10, false, false), caller: newMockMinimalDisputeGameFactoryCaller(10, false, false),
earliest: 500, earliest: 500,
blockNumber: big.NewInt(1), blockNumber: 1,
expectedLen: 5, expectedLen: 5,
}, },
{ {
name: "game count error", name: "game count error",
caller: newMockMinimalDisputeGameFactoryCaller(10, true, false), caller: newMockMinimalDisputeGameFactoryCaller(10, true, false),
blockNumber: big.NewInt(1), blockNumber: 1,
expectedErr: gameCountErr, expectedErr: gameCountErr,
}, },
{ {
name: "game index error", name: "game index error",
caller: newMockMinimalDisputeGameFactoryCaller(10, false, true), caller: newMockMinimalDisputeGameFactoryCaller(10, false, true),
blockNumber: big.NewInt(1), blockNumber: 1,
expectedErr: gameIndexErr, expectedErr: gameIndexErr,
}, },
{ {
name: "no games", name: "no games",
caller: newMockMinimalDisputeGameFactoryCaller(0, false, false), caller: newMockMinimalDisputeGameFactoryCaller(0, false, false),
blockNumber: big.NewInt(1), blockNumber: 1,
},
{
name: "missing block number",
caller: newMockMinimalDisputeGameFactoryCaller(0, false, false),
expectedErr: ErrMissingBlockNumber,
}, },
} }
...@@ -144,20 +138,15 @@ func newMockMinimalDisputeGameFactoryCaller(count uint64, gameCountErr bool, ind ...@@ -144,20 +138,15 @@ func newMockMinimalDisputeGameFactoryCaller(count uint64, gameCountErr bool, ind
} }
} }
func (m *mockMinimalDisputeGameFactoryCaller) GameCount(opts *bind.CallOpts) (*big.Int, error) { func (m *mockMinimalDisputeGameFactoryCaller) GetGameCount(_ context.Context, blockNum uint64) (uint64, error) {
if m.gameCountErr { if m.gameCountErr {
return nil, gameCountErr return 0, gameCountErr
} }
return big.NewInt(int64(m.gameCount)), nil return m.gameCount, nil
} }
func (m *mockMinimalDisputeGameFactoryCaller) GameAtIndex(opts *bind.CallOpts, _index *big.Int) (struct { func (m *mockMinimalDisputeGameFactoryCaller) GetGame(_ context.Context, index uint64, blockNum uint64) (types.GameMetadata, error) {
GameType uint8
Timestamp uint64
Proxy common.Address
}, error) {
index := _index.Uint64()
if m.indexErrors[index] { if m.indexErrors[index] {
return struct { return struct {
GameType uint8 GameType uint8
...@@ -166,13 +155,5 @@ func (m *mockMinimalDisputeGameFactoryCaller) GameAtIndex(opts *bind.CallOpts, _ ...@@ -166,13 +155,5 @@ func (m *mockMinimalDisputeGameFactoryCaller) GameAtIndex(opts *bind.CallOpts, _
}{}, gameIndexErr }{}, gameIndexErr
} }
return struct { return m.games[index], nil
GameType uint8
Timestamp uint64
Proxy common.Address
}{
GameType: m.games[index].GameType,
Timestamp: m.games[index].Timestamp,
Proxy: m.games[index].Proxy,
}, nil
} }
...@@ -4,7 +4,6 @@ import ( ...@@ -4,7 +4,6 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"math/big"
"time" "time"
"github.com/ethereum-optimism/optimism/op-challenger/game/scheduler" "github.com/ethereum-optimism/optimism/op-challenger/game/scheduler"
...@@ -23,7 +22,7 @@ type blockNumberFetcher func(ctx context.Context) (uint64, error) ...@@ -23,7 +22,7 @@ type blockNumberFetcher func(ctx context.Context) (uint64, error)
// gameSource loads information about the games available to play // gameSource loads information about the games available to play
type gameSource interface { type gameSource interface {
FetchAllGamesAtBlock(ctx context.Context, earliest uint64, blockNumber *big.Int) ([]types.GameMetadata, error) FetchAllGamesAtBlock(ctx context.Context, earliest uint64, blockNumber uint64) ([]types.GameMetadata, error)
} }
type gameScheduler interface { type gameScheduler interface {
...@@ -101,7 +100,7 @@ func (m *gameMonitor) minGameTimestamp() uint64 { ...@@ -101,7 +100,7 @@ func (m *gameMonitor) minGameTimestamp() uint64 {
} }
func (m *gameMonitor) progressGames(ctx context.Context, blockNum uint64) error { func (m *gameMonitor) progressGames(ctx context.Context, blockNum uint64) error {
games, err := m.source.FetchAllGamesAtBlock(ctx, m.minGameTimestamp(), new(big.Int).SetUint64(blockNum)) games, err := m.source.FetchAllGamesAtBlock(ctx, m.minGameTimestamp(), blockNum)
if err != nil { if err != nil {
return fmt.Errorf("failed to load games: %w", err) return fmt.Errorf("failed to load games: %w", err)
} }
......
...@@ -230,7 +230,7 @@ type stubGameSource struct { ...@@ -230,7 +230,7 @@ type stubGameSource struct {
func (s *stubGameSource) FetchAllGamesAtBlock( func (s *stubGameSource) FetchAllGamesAtBlock(
ctx context.Context, ctx context.Context,
earliest uint64, earliest uint64,
blockNumber *big.Int, blockNumber uint64,
) ([]types.GameMetadata, error) { ) ([]types.GameMetadata, error) {
return s.games, nil return s.games, nil
} }
......
...@@ -5,9 +5,9 @@ import ( ...@@ -5,9 +5,9 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault" "github.com/ethereum-optimism/optimism/op-challenger/game/fault"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/loader" "github.com/ethereum-optimism/optimism/op-challenger/game/loader"
"github.com/ethereum-optimism/optimism/op-challenger/game/registry" "github.com/ethereum-optimism/optimism/op-challenger/game/registry"
"github.com/ethereum-optimism/optimism/op-challenger/game/scheduler" "github.com/ethereum-optimism/optimism/op-challenger/game/scheduler"
...@@ -18,6 +18,7 @@ import ( ...@@ -18,6 +18,7 @@ import (
"github.com/ethereum-optimism/optimism/op-service/dial" "github.com/ethereum-optimism/optimism/op-service/dial"
"github.com/ethereum-optimism/optimism/op-service/httputil" "github.com/ethereum-optimism/optimism/op-service/httputil"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof" oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
...@@ -88,7 +89,7 @@ func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*Se ...@@ -88,7 +89,7 @@ func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*Se
m.StartBalanceMetrics(ctx, logger, l1Client, txMgr.From()) m.StartBalanceMetrics(ctx, logger, l1Client, txMgr.From())
} }
factoryContract, err := bindings.NewDisputeGameFactory(cfg.GameFactoryAddress, l1Client) factoryContract, err := contracts.NewDisputeGameFactoryContract(cfg.GameFactoryAddress, batching.NewMultiCaller(l1Client.Client(), batching.DefaultBatchSize))
if err != nil { if err != nil {
return nil, errors.Join(fmt.Errorf("failed to bind the fault dispute game factory contract: %w", err), s.Stop(ctx)) return nil, errors.Join(fmt.Errorf("failed to bind the fault dispute game factory contract: %w", err), s.Stop(ctx))
} }
......
...@@ -21,7 +21,11 @@ test-ws: pre-test ...@@ -21,7 +21,11 @@ test-ws: pre-test
test-http: pre-test test-http: pre-test
OP_E2E_USE_HTTP=true $(go_test) $(go_test_flags) ./... OP_E2E_USE_HTTP=true $(go_test) $(go_test_flags) ./...
.PHONY: test-ws .PHONY: test-http
test-span-batch: pre-test
OP_E2E_USE_SPAN_BATCH=true $(go_test) $(go_test_flags) ./...
.PHONY: test-span-batch
cannon-prestate: cannon-prestate:
make -C .. cannon-prestate make -C .. cannon-prestate
......
...@@ -46,6 +46,10 @@ type BatcherCfg struct { ...@@ -46,6 +46,10 @@ type BatcherCfg struct {
GarbageCfg *GarbageChannelCfg GarbageCfg *GarbageChannelCfg
} }
type L2BlockRefs interface {
L2BlockRefByHash(ctx context.Context, hash common.Hash) (eth.L2BlockRef, error)
}
// L2Batcher buffers and submits L2 batches to L1. // L2Batcher buffers and submits L2 batches to L1.
// //
// TODO: note the batcher shares little logic/state with actual op-batcher, // TODO: note the batcher shares little logic/state with actual op-batcher,
...@@ -59,24 +63,26 @@ type L2Batcher struct { ...@@ -59,24 +63,26 @@ type L2Batcher struct {
syncStatusAPI SyncStatusAPI syncStatusAPI SyncStatusAPI
l2 BlocksAPI l2 BlocksAPI
l1 L1TxAPI l1 L1TxAPI
engCl L2BlockRefs
l1Signer types.Signer l1Signer types.Signer
l2ChannelOut ChannelOutIface 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.L2BlockRef
l2SubmittedBlock eth.BlockID l2SubmittedBlock eth.L2BlockRef
l2BatcherCfg *BatcherCfg l2BatcherCfg *BatcherCfg
batcherAddr common.Address batcherAddr common.Address
} }
func NewL2Batcher(log log.Logger, rollupCfg *rollup.Config, batcherCfg *BatcherCfg, api SyncStatusAPI, l1 L1TxAPI, l2 BlocksAPI) *L2Batcher { func NewL2Batcher(log log.Logger, rollupCfg *rollup.Config, batcherCfg *BatcherCfg, api SyncStatusAPI, l1 L1TxAPI, l2 BlocksAPI, engCl L2BlockRefs) *L2Batcher {
return &L2Batcher{ return &L2Batcher{
log: log, log: log,
rollupCfg: rollupCfg, rollupCfg: rollupCfg,
syncStatusAPI: api, syncStatusAPI: api,
l1: l1, l1: l1,
l2: l2, l2: l2,
engCl: engCl,
l2BatcherCfg: batcherCfg, l2BatcherCfg: batcherCfg,
l1Signer: types.LatestSignerForChainID(rollupCfg.L1ChainID), l1Signer: types.LatestSignerForChainID(rollupCfg.L1ChainID),
batcherAddr: crypto.PubkeyToAddress(batcherCfg.BatcherKey.PublicKey), batcherAddr: crypto.PubkeyToAddress(batcherCfg.BatcherKey.PublicKey),
...@@ -103,31 +109,39 @@ func (s *L2Batcher) Buffer(t Testing) error { ...@@ -103,31 +109,39 @@ func (s *L2Batcher) Buffer(t Testing) error {
syncStatus, err := s.syncStatusAPI.SyncStatus(t.Ctx()) syncStatus, err := s.syncStatusAPI.SyncStatus(t.Ctx())
require.NoError(t, err, "no sync status error") require.NoError(t, err, "no sync status error")
// If we just started, start at safe-head // If we just started, start at safe-head
if s.l2SubmittedBlock == (eth.BlockID{}) { if s.l2SubmittedBlock == (eth.L2BlockRef{}) {
s.log.Info("Starting batch-submitter work at safe-head", "safe", syncStatus.SafeL2) s.log.Info("Starting batch-submitter work at safe-head", "safe", syncStatus.SafeL2)
s.l2SubmittedBlock = syncStatus.SafeL2.ID() s.l2SubmittedBlock = syncStatus.SafeL2
s.l2BufferedBlock = syncStatus.SafeL2.ID() s.l2BufferedBlock = syncStatus.SafeL2
s.l2ChannelOut = nil s.l2ChannelOut = nil
} }
// If it's lagging behind, catch it up. // If it's lagging behind, catch it up.
if s.l2SubmittedBlock.Number < syncStatus.SafeL2.Number { if s.l2SubmittedBlock.Number < syncStatus.SafeL2.Number {
s.log.Warn("last submitted block lagged behind L2 safe head: batch submission will continue from the safe head now", "last", s.l2SubmittedBlock, "safe", syncStatus.SafeL2) s.log.Warn("last submitted block lagged behind L2 safe head: batch submission will continue from the safe head now", "last", s.l2SubmittedBlock, "safe", syncStatus.SafeL2)
s.l2SubmittedBlock = syncStatus.SafeL2.ID() s.l2SubmittedBlock = syncStatus.SafeL2
s.l2BufferedBlock = syncStatus.SafeL2.ID() s.l2BufferedBlock = syncStatus.SafeL2
s.l2ChannelOut = nil s.l2ChannelOut = nil
} }
// Add the next unsafe block to the channel // Add the next unsafe block to the channel
if s.l2BufferedBlock.Number >= syncStatus.UnsafeL2.Number { if s.l2BufferedBlock.Number >= syncStatus.UnsafeL2.Number {
if s.l2BufferedBlock.Number > syncStatus.UnsafeL2.Number || s.l2BufferedBlock.Hash != syncStatus.UnsafeL2.Hash { if s.l2BufferedBlock.Number > syncStatus.UnsafeL2.Number || s.l2BufferedBlock.Hash != syncStatus.UnsafeL2.Hash {
s.log.Error("detected a reorg in L2 chain vs previous buffered information, resetting to safe head now", "safe_head", syncStatus.SafeL2) s.log.Error("detected a reorg in L2 chain vs previous buffered information, resetting to safe head now", "safe_head", syncStatus.SafeL2)
s.l2SubmittedBlock = syncStatus.SafeL2.ID() s.l2SubmittedBlock = syncStatus.SafeL2
s.l2BufferedBlock = syncStatus.SafeL2.ID() s.l2BufferedBlock = syncStatus.SafeL2
s.l2ChannelOut = nil s.l2ChannelOut = nil
} else { } else {
s.log.Info("nothing left to submit") s.log.Info("nothing left to submit")
return nil return nil
} }
} }
block, err := s.l2.BlockByNumber(t.Ctx(), big.NewInt(int64(s.l2BufferedBlock.Number+1)))
require.NoError(t, err, "need l2 block %d from sync status", s.l2SubmittedBlock.Number+1)
if block.ParentHash() != s.l2BufferedBlock.Hash {
s.log.Error("detected a reorg in L2 chain vs previous submitted information, resetting to safe head now", "safe_head", syncStatus.SafeL2)
s.l2SubmittedBlock = syncStatus.SafeL2
s.l2BufferedBlock = syncStatus.SafeL2
s.l2ChannelOut = nil
}
// 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 {
var ch ChannelOutIface var ch ChannelOutIface
...@@ -140,23 +154,24 @@ func (s *L2Batcher) Buffer(t Testing) error { ...@@ -140,23 +154,24 @@ func (s *L2Batcher) Buffer(t Testing) error {
ApproxComprRatio: 1, ApproxComprRatio: 1,
}) })
require.NoError(t, e, "failed to create compressor") require.NoError(t, e, "failed to create compressor")
ch, err = derive.NewChannelOut(derive.SingularBatchType, c, nil)
var batchType uint = derive.SingularBatchType
var spanBatchBuilder *derive.SpanBatchBuilder = nil
if s.rollupCfg.IsSpanBatch(block.Time()) {
batchType = derive.SpanBatchType
spanBatchBuilder = derive.NewSpanBatchBuilder(s.rollupCfg.Genesis.L2Time, s.rollupCfg.L2ChainID)
}
ch, err = derive.NewChannelOut(batchType, c, spanBatchBuilder)
} }
require.NoError(t, err, "failed to create channel") require.NoError(t, err, "failed to create channel")
s.l2ChannelOut = ch s.l2ChannelOut = ch
} }
block, err := s.l2.BlockByNumber(t.Ctx(), big.NewInt(int64(s.l2BufferedBlock.Number+1)))
require.NoError(t, err, "need l2 block %d from sync status", s.l2SubmittedBlock.Number+1)
if block.ParentHash() != s.l2BufferedBlock.Hash {
s.log.Error("detected a reorg in L2 chain vs previous submitted information, resetting to safe head now", "safe_head", syncStatus.SafeL2)
s.l2SubmittedBlock = syncStatus.SafeL2.ID()
s.l2BufferedBlock = syncStatus.SafeL2.ID()
s.l2ChannelOut = nil
}
if _, err := s.l2ChannelOut.AddBlock(block); err != nil { // should always succeed if _, err := s.l2ChannelOut.AddBlock(block); err != nil { // should always succeed
return err return err
} }
s.l2BufferedBlock = eth.ToBlockID(block) ref, err := s.engCl.L2BlockRefByHash(t.Ctx(), block.Hash())
require.NoError(t, err, "failed to get L2BlockRef")
s.l2BufferedBlock = ref
return nil return nil
} }
......
...@@ -39,7 +39,7 @@ func TestBatcher(gt *testing.T) { ...@@ -39,7 +39,7 @@ func TestBatcher(gt *testing.T) {
MinL1TxSize: 0, MinL1TxSize: 0,
MaxL1TxSize: 128_000, MaxL1TxSize: 128_000,
BatcherKey: dp.Secrets.Batcher, BatcherKey: dp.Secrets.Batcher,
}, rollupSeqCl, miner.EthClient(), seqEngine.EthClient()) }, rollupSeqCl, miner.EthClient(), seqEngine.EthClient(), seqEngine.EngineClient(t, sd.RollupCfg))
// Alice makes a L2 tx // Alice makes a L2 tx
cl := seqEngine.EthClient() cl := seqEngine.EthClient()
...@@ -137,7 +137,7 @@ func TestL2Finalization(gt *testing.T) { ...@@ -137,7 +137,7 @@ func TestL2Finalization(gt *testing.T) {
MinL1TxSize: 0, MinL1TxSize: 0,
MaxL1TxSize: 128_000, MaxL1TxSize: 128_000,
BatcherKey: dp.Secrets.Batcher, BatcherKey: dp.Secrets.Batcher,
}, sequencer.RollupClient(), miner.EthClient(), engine.EthClient()) }, sequencer.RollupClient(), miner.EthClient(), engine.EthClient(), engine.EngineClient(t, sd.RollupCfg))
heightToSubmit := sequencer.SyncStatus().UnsafeL2.Number heightToSubmit := sequencer.SyncStatus().UnsafeL2.Number
...@@ -223,7 +223,7 @@ func TestL2FinalizationWithSparseL1(gt *testing.T) { ...@@ -223,7 +223,7 @@ func TestL2FinalizationWithSparseL1(gt *testing.T) {
MinL1TxSize: 0, MinL1TxSize: 0,
MaxL1TxSize: 128_000, MaxL1TxSize: 128_000,
BatcherKey: dp.Secrets.Batcher, BatcherKey: dp.Secrets.Batcher,
}, sequencer.RollupClient(), miner.EthClient(), engine.EthClient()) }, sequencer.RollupClient(), miner.EthClient(), engine.EthClient(), engine.EngineClient(t, sd.RollupCfg))
batcher.ActSubmitAll(t) batcher.ActSubmitAll(t)
// include in L1 // include in L1
...@@ -287,7 +287,7 @@ func TestGarbageBatch(gt *testing.T) { ...@@ -287,7 +287,7 @@ func TestGarbageBatch(gt *testing.T) {
} }
} }
batcher := NewL2Batcher(log, sd.RollupCfg, batcherCfg, sequencer.RollupClient(), miner.EthClient(), engine.EthClient()) batcher := NewL2Batcher(log, sd.RollupCfg, batcherCfg, sequencer.RollupClient(), miner.EthClient(), engine.EthClient(), engine.EngineClient(t, sd.RollupCfg))
sequencer.ActL2PipelineFull(t) sequencer.ActL2PipelineFull(t)
verifier.ActL2PipelineFull(t) verifier.ActL2PipelineFull(t)
...@@ -359,7 +359,7 @@ func TestExtendedTimeWithoutL1Batches(gt *testing.T) { ...@@ -359,7 +359,7 @@ func TestExtendedTimeWithoutL1Batches(gt *testing.T) {
MinL1TxSize: 0, MinL1TxSize: 0,
MaxL1TxSize: 128_000, MaxL1TxSize: 128_000,
BatcherKey: dp.Secrets.Batcher, BatcherKey: dp.Secrets.Batcher,
}, sequencer.RollupClient(), miner.EthClient(), engine.EthClient()) }, sequencer.RollupClient(), miner.EthClient(), engine.EthClient(), engine.EngineClient(t, sd.RollupCfg))
sequencer.ActL2PipelineFull(t) sequencer.ActL2PipelineFull(t)
verifier.ActL2PipelineFull(t) verifier.ActL2PipelineFull(t)
...@@ -417,7 +417,7 @@ func TestBigL2Txs(gt *testing.T) { ...@@ -417,7 +417,7 @@ func TestBigL2Txs(gt *testing.T) {
MinL1TxSize: 0, MinL1TxSize: 0,
MaxL1TxSize: 40_000, // try a small batch size, to force the data to be split between more frames MaxL1TxSize: 40_000, // try a small batch size, to force the data to be split between more frames
BatcherKey: dp.Secrets.Batcher, BatcherKey: dp.Secrets.Batcher,
}, sequencer.RollupClient(), miner.EthClient(), engine.EthClient()) }, sequencer.RollupClient(), miner.EthClient(), engine.EthClient(), engine.EngineClient(t, sd.RollupCfg))
sequencer.ActL2PipelineFull(t) sequencer.ActL2PipelineFull(t)
...@@ -470,7 +470,7 @@ func TestBigL2Txs(gt *testing.T) { ...@@ -470,7 +470,7 @@ func TestBigL2Txs(gt *testing.T) {
sequencer.ActL2EndBlock(t) sequencer.ActL2EndBlock(t)
for batcher.l2BufferedBlock.Number < sequencer.SyncStatus().UnsafeL2.Number { for batcher.l2BufferedBlock.Number < sequencer.SyncStatus().UnsafeL2.Number {
// if we run out of space, close the channel and submit all the txs // if we run out of space, close the channel and submit all the txs
if err := batcher.Buffer(t); errors.Is(err, derive.ErrTooManyRLPBytes) { if err := batcher.Buffer(t); errors.Is(err, derive.ErrTooManyRLPBytes) || errors.Is(err, derive.CompressorFullErr) {
log.Info("flushing filled channel to batch txs", "id", batcher.l2ChannelOut.ID()) log.Info("flushing filled channel to batch txs", "id", batcher.l2ChannelOut.ID())
batcher.ActL2ChannelClose(t) batcher.ActL2ChannelClose(t)
for batcher.l2ChannelOut != nil { for batcher.l2ChannelOut != nil {
......
...@@ -26,7 +26,7 @@ func TestProposer(gt *testing.T) { ...@@ -26,7 +26,7 @@ func TestProposer(gt *testing.T) {
MinL1TxSize: 0, MinL1TxSize: 0,
MaxL1TxSize: 128_000, MaxL1TxSize: 128_000,
BatcherKey: dp.Secrets.Batcher, BatcherKey: dp.Secrets.Batcher,
}, rollupSeqCl, miner.EthClient(), seqEngine.EthClient()) }, rollupSeqCl, miner.EthClient(), seqEngine.EthClient(), seqEngine.EngineClient(t, sd.RollupCfg))
proposer := NewL2Proposer(t, log, &ProposerCfg{ proposer := NewL2Proposer(t, log, &ProposerCfg{
OutputOracleAddr: sd.DeploymentsL1.L2OutputOracleProxy, OutputOracleAddr: sd.DeploymentsL1.L2OutputOracleProxy,
......
...@@ -135,6 +135,10 @@ func (s *L2Verifier) L2Safe() eth.L2BlockRef { ...@@ -135,6 +135,10 @@ func (s *L2Verifier) L2Safe() eth.L2BlockRef {
return s.derivation.SafeL2Head() return s.derivation.SafeL2Head()
} }
func (s *L2Verifier) L2PendingSafe() eth.L2BlockRef {
return s.derivation.PendingSafeL2Head()
}
func (s *L2Verifier) L2Unsafe() eth.L2BlockRef { func (s *L2Verifier) L2Unsafe() eth.L2BlockRef {
return s.derivation.UnsafeL2Head() return s.derivation.UnsafeL2Head()
} }
...@@ -153,6 +157,7 @@ func (s *L2Verifier) SyncStatus() *eth.SyncStatus { ...@@ -153,6 +157,7 @@ func (s *L2Verifier) SyncStatus() *eth.SyncStatus {
UnsafeL2: s.L2Unsafe(), UnsafeL2: s.L2Unsafe(),
SafeL2: s.L2Safe(), SafeL2: s.L2Safe(),
FinalizedL2: s.L2Finalized(), FinalizedL2: s.L2Finalized(),
PendingSafeL2: s.L2PendingSafe(),
UnsafeL2SyncTarget: s.derivation.UnsafeL2SyncTarget(), UnsafeL2SyncTarget: s.derivation.UnsafeL2SyncTarget(),
EngineSyncTarget: s.EngineSyncTarget(), EngineSyncTarget: s.EngineSyncTarget(),
} }
......
...@@ -40,7 +40,7 @@ func setupReorgTestActors(t Testing, dp *e2eutils.DeployParams, sd *e2eutils.Set ...@@ -40,7 +40,7 @@ func setupReorgTestActors(t Testing, dp *e2eutils.DeployParams, sd *e2eutils.Set
MinL1TxSize: 0, MinL1TxSize: 0,
MaxL1TxSize: 128_000, MaxL1TxSize: 128_000,
BatcherKey: dp.Secrets.Batcher, BatcherKey: dp.Secrets.Batcher,
}, rollupSeqCl, miner.EthClient(), seqEngine.EthClient()) }, rollupSeqCl, miner.EthClient(), seqEngine.EthClient(), seqEngine.EngineClient(t, sd.RollupCfg))
return sd, dp, miner, sequencer, seqEngine, verifier, verifEngine, batcher return sd, dp, miner, sequencer, seqEngine, verifier, verifEngine, batcher
} }
...@@ -189,8 +189,16 @@ func TestReorgFlipFlop(gt *testing.T) { ...@@ -189,8 +189,16 @@ func TestReorgFlipFlop(gt *testing.T) {
verifier.ActL2PipelineFull(t) verifier.ActL2PipelineFull(t)
require.Equal(t, sd.RollupCfg.Genesis.L1, verifier.L2Safe().L1Origin, "expected to be back at genesis origin after losing A0 and A1") require.Equal(t, sd.RollupCfg.Genesis.L1, verifier.L2Safe().L1Origin, "expected to be back at genesis origin after losing A0 and A1")
if sd.RollupCfg.SpanBatchTime == nil {
// before span batch hard fork
require.NotZero(t, verifier.L2Safe().Number, "still preserving old L2 blocks that did not reference reorged L1 chain (assuming more than one L2 block per L1 block)") require.NotZero(t, verifier.L2Safe().Number, "still preserving old L2 blocks that did not reference reorged L1 chain (assuming more than one L2 block per L1 block)")
require.Equal(t, verifier.L2Safe(), verifier.L2Unsafe(), "head is at safe block after L1 reorg") require.Equal(t, verifier.L2Safe(), verifier.L2Unsafe(), "head is at safe block after L1 reorg")
} else {
// after span batch hard fork
require.Zero(t, verifier.L2Safe().Number, "safe head is at genesis block because span batch referenced reorged L1 chain is not accepted")
require.Equal(t, verifier.L2Unsafe().ID(), sequencer.L2Unsafe().ParentID(), "head is at the highest unsafe block that references canonical L1 chain(genesis block)")
batcher.l2BufferedBlock = eth.L2BlockRef{} // must reset batcher to resubmit blocks included in the last batch
}
checkVerifEngine() checkVerifEngine()
// and sync the sequencer, then build some new L2 blocks, up to and including with L1 origin B2 // and sync the sequencer, then build some new L2 blocks, up to and including with L1 origin B2
...@@ -210,6 +218,7 @@ func TestReorgFlipFlop(gt *testing.T) { ...@@ -210,6 +218,7 @@ func TestReorgFlipFlop(gt *testing.T) {
verifier.ActL1HeadSignal(t) verifier.ActL1HeadSignal(t)
verifier.ActL2PipelineFull(t) verifier.ActL2PipelineFull(t)
require.Equal(t, verifier.L2Safe().L1Origin, blockB2.ID(), "B2 is the L1 origin of verifier now") require.Equal(t, verifier.L2Safe().L1Origin, blockB2.ID(), "B2 is the L1 origin of verifier now")
require.Equal(t, verifier.L2Unsafe(), sequencer.L2Unsafe(), "verifier unsafe head is reorged along sequencer")
checkVerifEngine() checkVerifEngine()
// Flop back to chain A! // Flop back to chain A!
...@@ -585,7 +594,7 @@ func TestRestartOpGeth(gt *testing.T) { ...@@ -585,7 +594,7 @@ func TestRestartOpGeth(gt *testing.T) {
MinL1TxSize: 0, MinL1TxSize: 0,
MaxL1TxSize: 128_000, MaxL1TxSize: 128_000,
BatcherKey: dp.Secrets.Batcher, BatcherKey: dp.Secrets.Batcher,
}, sequencer.RollupClient(), miner.EthClient(), seqEng.EthClient()) }, sequencer.RollupClient(), miner.EthClient(), seqEng.EthClient(), seqEng.EngineClient(t, sd.RollupCfg))
// start // start
sequencer.ActL2PipelineFull(t) sequencer.ActL2PipelineFull(t)
...@@ -674,7 +683,7 @@ func TestConflictingL2Blocks(gt *testing.T) { ...@@ -674,7 +683,7 @@ func TestConflictingL2Blocks(gt *testing.T) {
MinL1TxSize: 0, MinL1TxSize: 0,
MaxL1TxSize: 128_000, MaxL1TxSize: 128_000,
BatcherKey: dp.Secrets.Batcher, BatcherKey: dp.Secrets.Batcher,
}, altSequencer.RollupClient(), miner.EthClient(), altSeqEng.EthClient()) }, altSequencer.RollupClient(), miner.EthClient(), altSeqEng.EthClient(), altSeqEng.EngineClient(t, sd.RollupCfg))
// And set up user Alice, using the alternative sequencer endpoint // And set up user Alice, using the alternative sequencer endpoint
l2Cl := altSeqEng.EthClient() l2Cl := altSeqEng.EthClient()
...@@ -762,3 +771,108 @@ func TestConflictingL2Blocks(gt *testing.T) { ...@@ -762,3 +771,108 @@ func TestConflictingL2Blocks(gt *testing.T) {
require.Equal(t, verifier.L2Unsafe(), altSequencer.L2Unsafe(), "alt-sequencer gets back in harmony with verifier by reorging out its conflicting data") require.Equal(t, verifier.L2Unsafe(), altSequencer.L2Unsafe(), "alt-sequencer gets back in harmony with verifier by reorging out its conflicting data")
require.Equal(t, sequencer.L2Unsafe(), altSequencer.L2Unsafe(), "and gets back in harmony with original sequencer") require.Equal(t, sequencer.L2Unsafe(), altSequencer.L2Unsafe(), "and gets back in harmony with original sequencer")
} }
func TestSyncAfterReorg(gt *testing.T) {
t := NewDefaultTesting(gt)
testingParams := e2eutils.TestParams{
MaxSequencerDrift: 60,
SequencerWindowSize: 4,
ChannelTimeout: 2,
L1BlockTime: 12,
}
sd, dp, miner, sequencer, seqEngine, verifier, _, batcher := setupReorgTest(t, &testingParams)
l2Client := seqEngine.EthClient()
log := testlog.Logger(t, log.LvlDebug)
addresses := e2eutils.CollectAddresses(sd, dp)
l2UserEnv := &BasicUserEnv[*L2Bindings]{
EthCl: l2Client,
Signer: types.LatestSigner(sd.L2Cfg.Config),
AddressCorpora: addresses,
Bindings: NewL2Bindings(t, l2Client, seqEngine.GethClient()),
}
alice := NewCrossLayerUser(log, dp.Secrets.Alice, rand.New(rand.NewSource(0xa57b)))
alice.L2.SetUserEnv(l2UserEnv)
sequencer.ActL2PipelineFull(t)
verifier.ActL2PipelineFull(t)
// build empty L1 block: A0
miner.ActL1SetFeeRecipient(common.Address{'A', 0})
miner.ActEmptyBlock(t)
sequencer.ActL1HeadSignal(t)
for sequencer.derivation.UnsafeL2Head().L1Origin.Number < sequencer.l1State.L1Head().Number {
// build L2 blocks until the L1 origin is the current L1 head(A0)
sequencer.ActL2PipelineFull(t)
sequencer.ActL2StartBlock(t)
if sequencer.derivation.UnsafeL2Head().Number == 11 {
// include a user tx at L2 block #12 to make a state transition
alice.L2.ActResetTxOpts(t)
alice.L2.ActSetTxToAddr(&dp.Addresses.Bob)(t)
alice.L2.ActMakeTx(t)
// Include the tx in the block we're making
seqEngine.ActL2IncludeTx(alice.Address())(t)
}
sequencer.ActL2EndBlock(t)
}
// submit all new L2 blocks: #1 ~ #12
batcher.ActSubmitAll(t)
// build an L1 block included batch TX: A1
miner.ActL1SetFeeRecipient(common.Address{'A', 1})
miner.ActL1StartBlock(12)(t)
miner.ActL1IncludeTx(sd.RollupCfg.Genesis.SystemConfig.BatcherAddr)(t)
miner.ActL1EndBlock(t)
for i := 2; i < 6; i++ {
// build L2 blocks until the L1 origin is the current L1 head
sequencer.ActL1HeadSignal(t)
sequencer.ActBuildToL1Head(t)
// submt all new L2 blocks
batcher.ActSubmitAll(t)
// build an L1 block included batch TX: A2 ~ A5
miner.ActL1SetFeeRecipient(common.Address{'A', byte(i)})
miner.ActL1StartBlock(12)(t)
miner.ActL1IncludeTx(sd.RollupCfg.Genesis.SystemConfig.BatcherAddr)(t)
miner.ActL1EndBlock(t)
}
sequencer.ActL1HeadSignal(t)
sequencer.ActL2PipelineFull(t)
// capture current L2 safe head
submittedSafeHead := sequencer.L2Safe().ID()
// build L2 blocks until the L1 origin is the current L1 head(A5)
sequencer.ActBuildToL1Head(t)
batcher.ActSubmitAll(t)
// build an L1 block included batch TX: A6
miner.ActL1SetFeeRecipient(common.Address{'A', 6})
miner.ActL1StartBlock(12)(t)
miner.ActL1IncludeTx(sd.RollupCfg.Genesis.SystemConfig.BatcherAddr)(t)
miner.ActL1EndBlock(t)
sequencer.ActL1HeadSignal(t)
sequencer.ActL2PipelineFull(t)
verifier.ActL1HeadSignal(t)
verifier.ActL2PipelineFull(t)
// reorg L1
miner.ActL1RewindToParent(t) // undo A6
miner.ActL1SetFeeRecipient(common.Address{'B', 6}) // build B6
miner.ActEmptyBlock(t)
miner.ActL1SetFeeRecipient(common.Address{'B', 7}) // build B7
miner.ActEmptyBlock(t)
// sequencer and verifier detect L1 reorg
// derivation pipeline is reset
// safe head may be reset to block #11
sequencer.ActL1HeadSignal(t)
sequencer.ActL2PipelineFull(t)
verifier.ActL1HeadSignal(t)
verifier.ActL2PipelineFull(t)
// sequencer and verifier must derive all submitted batches and reach to the captured block
require.Equal(t, sequencer.L2Safe().ID(), submittedSafeHead)
require.Equal(t, verifier.L2Safe().ID(), submittedSafeHead)
}
package actions
import (
"crypto/ecdsa"
crand "crypto/rand"
"fmt"
"math/big"
"math/rand"
"testing"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/rollup/sync"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require"
)
// TestDropSpanBatchBeforeHardfork tests behavior of op-node before SpanBatch hardfork.
// op-node must drop SpanBatch before SpanBatch hardfork.
func TestDropSpanBatchBeforeHardfork(gt *testing.T) {
t := NewDefaultTesting(gt)
p := &e2eutils.TestParams{
MaxSequencerDrift: 20, // larger than L1 block time we simulate in this test (12)
SequencerWindowSize: 24,
ChannelTimeout: 20,
L1BlockTime: 12,
}
dp := e2eutils.MakeDeployParams(t, p)
// do not activate SpanBatch hardfork for verifier
dp.DeployConfig.L2GenesisSpanBatchTimeOffset = nil
sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlError)
miner, seqEngine, sequencer := setupSequencerTest(t, sd, log)
verifEngine, verifier := setupVerifier(t, sd, log, miner.L1Client(t, sd.RollupCfg), &sync.Config{})
rollupSeqCl := sequencer.RollupClient()
dp2 := e2eutils.MakeDeployParams(t, p)
minTs := hexutil.Uint64(0)
// activate SpanBatch hardfork for batcher. so batcher will submit SpanBatches to L1.
dp2.DeployConfig.L2GenesisSpanBatchTimeOffset = &minTs
sd2 := e2eutils.Setup(t, dp2, defaultAlloc)
batcher := NewL2Batcher(log, sd2.RollupCfg, &BatcherCfg{
MinL1TxSize: 0,
MaxL1TxSize: 128_000,
BatcherKey: dp.Secrets.Batcher,
}, rollupSeqCl, miner.EthClient(), seqEngine.EthClient(), seqEngine.EngineClient(t, sd.RollupCfg))
// Alice makes a L2 tx
cl := seqEngine.EthClient()
n, err := cl.PendingNonceAt(t.Ctx(), dp.Addresses.Alice)
require.NoError(t, err)
signer := types.LatestSigner(sd.L2Cfg.Config)
tx := types.MustSignNewTx(dp.Secrets.Alice, signer, &types.DynamicFeeTx{
ChainID: sd.L2Cfg.Config.ChainID,
Nonce: n,
GasTipCap: big.NewInt(2 * params.GWei),
GasFeeCap: new(big.Int).Add(miner.l1Chain.CurrentBlock().BaseFee, big.NewInt(2*params.GWei)),
Gas: params.TxGas,
To: &dp.Addresses.Bob,
Value: e2eutils.Ether(2),
})
require.NoError(gt, cl.SendTransaction(t.Ctx(), tx))
sequencer.ActL2PipelineFull(t)
verifier.ActL2PipelineFull(t)
// Make L2 block
sequencer.ActL2StartBlock(t)
seqEngine.ActL2IncludeTx(dp.Addresses.Alice)(t)
sequencer.ActL2EndBlock(t)
// batch submit to L1. batcher should submit span batches.
batcher.ActL2BatchBuffer(t)
batcher.ActL2ChannelClose(t)
batcher.ActL2BatchSubmit(t)
// confirm batch on L1
miner.ActL1StartBlock(12)(t)
miner.ActL1IncludeTx(dp.Addresses.Batcher)(t)
miner.ActL1EndBlock(t)
bl := miner.l1Chain.CurrentBlock()
log.Info("bl", "txs", len(miner.l1Chain.GetBlockByHash(bl.Hash()).Transactions()))
// Now make enough L1 blocks that the verifier will have to derive a L2 block
// It will also eagerly derive the block from the batcher
for i := uint64(0); i < sd.RollupCfg.SeqWindowSize; i++ {
miner.ActL1StartBlock(12)(t)
miner.ActL1EndBlock(t)
}
// try to sync verifier from L1 batch. but verifier should drop every span batch.
verifier.ActL1HeadSignal(t)
verifier.ActL2PipelineFull(t)
require.Equal(t, uint64(1), verifier.SyncStatus().SafeL2.L1Origin.Number)
verifCl := verifEngine.EthClient()
for i := int64(1); i < int64(verifier.L2Safe().Number); i++ {
block, _ := verifCl.BlockByNumber(t.Ctx(), big.NewInt(i))
require.NoError(t, err)
// because verifier drops every span batch, it should generate empty blocks.
// so every block has only L1 attribute deposit transaction.
require.Equal(t, block.Transactions().Len(), 1)
}
// check that the tx from alice is not included in verifier's chain
_, _, err = verifCl.TransactionByHash(t.Ctx(), tx.Hash())
require.ErrorIs(t, err, ethereum.NotFound)
}
// TestAcceptSingularBatchAfterHardfork tests behavior of op-node after SpanBatch hardfork.
// op-node must accept SingularBatch after SpanBatch hardfork.
func TestAcceptSingularBatchAfterHardfork(gt *testing.T) {
t := NewDefaultTesting(gt)
p := &e2eutils.TestParams{
MaxSequencerDrift: 20, // larger than L1 block time we simulate in this test (12)
SequencerWindowSize: 24,
ChannelTimeout: 20,
L1BlockTime: 12,
}
minTs := hexutil.Uint64(0)
dp := e2eutils.MakeDeployParams(t, p)
// activate SpanBatch hardfork for verifier.
dp.DeployConfig.L2GenesisSpanBatchTimeOffset = &minTs
sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlError)
miner, seqEngine, sequencer := setupSequencerTest(t, sd, log)
verifEngine, verifier := setupVerifier(t, sd, log, miner.L1Client(t, sd.RollupCfg), &sync.Config{})
rollupSeqCl := sequencer.RollupClient()
dp2 := e2eutils.MakeDeployParams(t, p)
// not activate SpanBatch hardfork for batcher
dp2.DeployConfig.L2GenesisSpanBatchTimeOffset = nil
sd2 := e2eutils.Setup(t, dp2, defaultAlloc)
batcher := NewL2Batcher(log, sd2.RollupCfg, &BatcherCfg{
MinL1TxSize: 0,
MaxL1TxSize: 128_000,
BatcherKey: dp.Secrets.Batcher,
}, rollupSeqCl, miner.EthClient(), seqEngine.EthClient(), seqEngine.EngineClient(t, sd.RollupCfg))
// Alice makes a L2 tx
cl := seqEngine.EthClient()
n, err := cl.PendingNonceAt(t.Ctx(), dp.Addresses.Alice)
require.NoError(t, err)
signer := types.LatestSigner(sd.L2Cfg.Config)
tx := types.MustSignNewTx(dp.Secrets.Alice, signer, &types.DynamicFeeTx{
ChainID: sd.L2Cfg.Config.ChainID,
Nonce: n,
GasTipCap: big.NewInt(2 * params.GWei),
GasFeeCap: new(big.Int).Add(miner.l1Chain.CurrentBlock().BaseFee, big.NewInt(2*params.GWei)),
Gas: params.TxGas,
To: &dp.Addresses.Bob,
Value: e2eutils.Ether(2),
})
require.NoError(gt, cl.SendTransaction(t.Ctx(), tx))
sequencer.ActL2PipelineFull(t)
verifier.ActL2PipelineFull(t)
// Make L2 block
sequencer.ActL2StartBlock(t)
seqEngine.ActL2IncludeTx(dp.Addresses.Alice)(t)
sequencer.ActL2EndBlock(t)
// batch submit to L1. batcher should submit singular batches.
batcher.ActL2BatchBuffer(t)
batcher.ActL2ChannelClose(t)
batcher.ActL2BatchSubmit(t)
// confirm batch on L1
miner.ActL1StartBlock(12)(t)
miner.ActL1IncludeTx(dp.Addresses.Batcher)(t)
miner.ActL1EndBlock(t)
bl := miner.l1Chain.CurrentBlock()
log.Info("bl", "txs", len(miner.l1Chain.GetBlockByHash(bl.Hash()).Transactions()))
// Now make enough L1 blocks that the verifier will have to derive a L2 block
// It will also eagerly derive the block from the batcher
for i := uint64(0); i < sd.RollupCfg.SeqWindowSize; i++ {
miner.ActL1StartBlock(12)(t)
miner.ActL1EndBlock(t)
}
// sync verifier from L1 batch in otherwise empty sequence window
verifier.ActL1HeadSignal(t)
verifier.ActL2PipelineFull(t)
require.Equal(t, uint64(1), verifier.SyncStatus().SafeL2.L1Origin.Number)
// check that the tx from alice made it into the L2 chain
verifCl := verifEngine.EthClient()
vTx, isPending, err := verifCl.TransactionByHash(t.Ctx(), tx.Hash())
require.NoError(t, err)
require.False(t, isPending)
require.NotNil(t, vTx)
}
// TestSpanBatchEmptyChain tests derivation of empty chain using SpanBatch.
func TestSpanBatchEmptyChain(gt *testing.T) {
t := NewDefaultTesting(gt)
p := &e2eutils.TestParams{
MaxSequencerDrift: 20,
SequencerWindowSize: 24,
ChannelTimeout: 20,
L1BlockTime: 12,
}
dp := e2eutils.MakeDeployParams(t, p)
minTs := hexutil.Uint64(0)
// Activate SpanBatch hardfork
dp.DeployConfig.L2GenesisSpanBatchTimeOffset = &minTs
sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlError)
miner, seqEngine, sequencer := setupSequencerTest(t, sd, log)
_, verifier := setupVerifier(t, sd, log, miner.L1Client(t, sd.RollupCfg), &sync.Config{})
rollupSeqCl := sequencer.RollupClient()
batcher := NewL2Batcher(log, sd.RollupCfg, &BatcherCfg{
MinL1TxSize: 0,
MaxL1TxSize: 128_000,
BatcherKey: dp.Secrets.Batcher,
}, rollupSeqCl, miner.EthClient(), seqEngine.EthClient(), seqEngine.EngineClient(t, sd.RollupCfg))
sequencer.ActL2PipelineFull(t)
verifier.ActL2PipelineFull(t)
miner.ActEmptyBlock(t)
// Make 1200 empty L2 blocks (L1BlockTime / L2BlockTime * 100)
for i := 0; i < 100; i++ {
sequencer.ActL1HeadSignal(t)
sequencer.ActBuildToL1Head(t)
if i%10 == 9 {
// batch submit to L1
batcher.ActSubmitAll(t)
// confirm batch on L1
miner.ActL1StartBlock(12)(t)
miner.ActL1IncludeTx(dp.Addresses.Batcher)(t)
miner.ActL1EndBlock(t)
} else {
miner.ActEmptyBlock(t)
}
}
sequencer.ActL1HeadSignal(t)
sequencer.ActL2PipelineFull(t)
verifier.ActL1HeadSignal(t)
verifier.ActL2PipelineFull(t)
require.Equal(t, sequencer.L2Unsafe(), sequencer.L2Safe())
require.Equal(t, verifier.L2Unsafe(), verifier.L2Safe())
require.Equal(t, sequencer.L2Safe(), verifier.L2Safe())
}
// TestSpanBatchLowThroughputChain tests derivation of low-throughput chain using SpanBatch.
func TestSpanBatchLowThroughputChain(gt *testing.T) {
t := NewDefaultTesting(gt)
p := &e2eutils.TestParams{
MaxSequencerDrift: 20,
SequencerWindowSize: 24,
ChannelTimeout: 20,
L1BlockTime: 12,
}
dp := e2eutils.MakeDeployParams(t, p)
minTs := hexutil.Uint64(0)
// Activate SpanBatch hardfork
dp.DeployConfig.L2GenesisSpanBatchTimeOffset = &minTs
sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlError)
miner, seqEngine, sequencer := setupSequencerTest(t, sd, log)
_, verifier := setupVerifier(t, sd, log, miner.L1Client(t, sd.RollupCfg), &sync.Config{})
rollupSeqCl := sequencer.RollupClient()
batcher := NewL2Batcher(log, sd.RollupCfg, &BatcherCfg{
MinL1TxSize: 0,
MaxL1TxSize: 128_000,
BatcherKey: dp.Secrets.Batcher,
}, rollupSeqCl, miner.EthClient(), seqEngine.EthClient(), seqEngine.EngineClient(t, sd.RollupCfg))
cl := seqEngine.EthClient()
const numTestUsers = 5
var privKeys [numTestUsers]*ecdsa.PrivateKey
var addrs [numTestUsers]common.Address
for i := 0; i < numTestUsers; i++ {
// Create a new test account
privateKey, err := dp.Secrets.Wallet.PrivateKey(accounts.Account{
URL: accounts.URL{
Path: fmt.Sprintf("m/44'/60'/0'/0/%d", 10+i),
},
})
privKeys[i] = privateKey
addr := crypto.PubkeyToAddress(privateKey.PublicKey)
require.NoError(t, err)
addrs[i] = addr
}
sequencer.ActL2PipelineFull(t)
verifier.ActL2PipelineFull(t)
miner.ActEmptyBlock(t)
totalTxCount := 0
// Make 600 L2 blocks (L1BlockTime / L2BlockTime * 50) including 1~3 txs
for i := 0; i < 50; i++ {
sequencer.ActL1HeadSignal(t)
for sequencer.derivation.UnsafeL2Head().L1Origin.Number < sequencer.l1State.L1Head().Number {
sequencer.ActL2PipelineFull(t)
sequencer.ActL2StartBlock(t)
// fill the block with random number of L2 txs
for j := 0; j < rand.Intn(3); j++ {
userIdx := totalTxCount % numTestUsers
signer := types.LatestSigner(sd.L2Cfg.Config)
data := make([]byte, rand.Intn(100))
_, err := crand.Read(data[:]) // fill with random bytes
require.NoError(t, err)
gas, err := core.IntrinsicGas(data, nil, false, true, true, false)
require.NoError(t, err)
baseFee := seqEngine.l2Chain.CurrentBlock().BaseFee
nonce, err := cl.PendingNonceAt(t.Ctx(), addrs[userIdx])
require.NoError(t, err)
tx := types.MustSignNewTx(privKeys[userIdx], signer, &types.DynamicFeeTx{
ChainID: sd.L2Cfg.Config.ChainID,
Nonce: nonce,
GasTipCap: big.NewInt(2 * params.GWei),
GasFeeCap: new(big.Int).Add(new(big.Int).Mul(baseFee, big.NewInt(2)), big.NewInt(2*params.GWei)),
Gas: gas,
To: &dp.Addresses.Bob,
Value: big.NewInt(0),
Data: data,
})
require.NoError(gt, cl.SendTransaction(t.Ctx(), tx))
seqEngine.ActL2IncludeTx(addrs[userIdx])(t)
totalTxCount += 1
}
sequencer.ActL2EndBlock(t)
}
if i%10 == 9 {
// batch submit to L1
batcher.ActSubmitAll(t)
// confirm batch on L1
miner.ActL1StartBlock(12)(t)
miner.ActL1IncludeTx(dp.Addresses.Batcher)(t)
miner.ActL1EndBlock(t)
} else {
miner.ActEmptyBlock(t)
}
}
sequencer.ActL1HeadSignal(t)
sequencer.ActL2PipelineFull(t)
verifier.ActL1HeadSignal(t)
verifier.ActL2PipelineFull(t)
require.Equal(t, sequencer.L2Unsafe(), sequencer.L2Safe())
require.Equal(t, verifier.L2Unsafe(), verifier.L2Safe())
require.Equal(t, sequencer.L2Safe(), verifier.L2Safe())
}
...@@ -2,15 +2,24 @@ package actions ...@@ -2,15 +2,24 @@ package actions
import ( import (
"errors" "errors"
"math/big"
"math/rand" "math/rand"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-batcher/compressor"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-node/rollup/sync" "github.com/ethereum-optimism/optimism/op-node/rollup/sync"
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/sources" "github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-service/testutils"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -166,3 +175,232 @@ func TestEngineP2PSync(gt *testing.T) { ...@@ -166,3 +175,232 @@ func TestEngineP2PSync(gt *testing.T) {
require.Equal(t, sequencer.L2Unsafe().Hash, verifier.EngineSyncTarget().Hash) require.Equal(t, sequencer.L2Unsafe().Hash, verifier.EngineSyncTarget().Hash)
} }
} }
func TestInvalidPayloadInSpanBatch(gt *testing.T) {
t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
minTs := hexutil.Uint64(0)
// Activate SpanBatch hardfork
dp.DeployConfig.L2GenesisSpanBatchTimeOffset = &minTs
dp.DeployConfig.L2BlockTime = 2
sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlInfo)
_, _, miner, sequencer, seqEng, verifier, _, batcher := setupReorgTestActors(t, dp, sd, log)
l2Cl := seqEng.EthClient()
rng := rand.New(rand.NewSource(1234))
signer := types.LatestSigner(sd.L2Cfg.Config)
sequencer.ActL2PipelineFull(t)
verifier.ActL2PipelineFull(t)
c, e := compressor.NewRatioCompressor(compressor.Config{
TargetFrameSize: 128_000,
TargetNumFrames: 1,
ApproxComprRatio: 1,
})
require.NoError(t, e)
spanBatchBuilder := derive.NewSpanBatchBuilder(sd.RollupCfg.Genesis.L2Time, sd.RollupCfg.L2ChainID)
// Create new span batch channel
channelOut, err := derive.NewChannelOut(derive.SpanBatchType, c, spanBatchBuilder)
require.NoError(t, err)
// Create block A1 ~ A12 for L1 block #0 ~ #2
miner.ActEmptyBlock(t)
miner.ActEmptyBlock(t)
sequencer.ActL1HeadSignal(t)
sequencer.ActBuildToL1HeadUnsafe(t)
for i := uint64(1); i <= sequencer.L2Unsafe().Number; i++ {
block, err := l2Cl.BlockByNumber(t.Ctx(), new(big.Int).SetUint64(i))
require.NoError(t, err)
if i == 8 {
// Make block A8 as an invalid block
invalidTx := testutils.RandomTx(rng, big.NewInt(100), signer)
block = block.WithBody([]*types.Transaction{block.Transactions()[0], invalidTx}, []*types.Header{})
}
// Add A1 ~ A12 into the channel
_, err = channelOut.AddBlock(block)
require.NoError(t, err)
}
// Submit span batch(A1, ..., A7, invalid A8, A9, ..., A12)
batcher.l2ChannelOut = channelOut
batcher.ActL2ChannelClose(t)
batcher.ActL2BatchSubmit(t)
miner.ActL1StartBlock(12)(t)
miner.ActL1IncludeTx(dp.Addresses.Batcher)(t)
miner.ActL1EndBlock(t)
miner.ActL1SafeNext(t)
miner.ActL1FinalizeNext(t)
// After the verifier processed the span batch, only unsafe head should be advanced to A7.
// Safe head is not updated because the span batch is not fully processed.
verifier.ActL1HeadSignal(t)
verifier.ActL2PipelineFull(t)
require.Equal(t, verifier.L2Unsafe().Number, uint64(7))
require.Equal(t, verifier.L2Safe().Number, uint64(0))
// Create new span batch channel
c, e = compressor.NewRatioCompressor(compressor.Config{
TargetFrameSize: 128_000,
TargetNumFrames: 1,
ApproxComprRatio: 1,
})
require.NoError(t, e)
spanBatchBuilder = derive.NewSpanBatchBuilder(sd.RollupCfg.Genesis.L2Time, sd.RollupCfg.L2ChainID)
channelOut, err = derive.NewChannelOut(derive.SpanBatchType, c, spanBatchBuilder)
require.NoError(t, err)
for i := uint64(1); i <= sequencer.L2Unsafe().Number; i++ {
block, err := l2Cl.BlockByNumber(t.Ctx(), new(big.Int).SetUint64(i))
require.NoError(t, err)
if i == 1 {
// Create valid TX
aliceNonce, err := seqEng.EthClient().PendingNonceAt(t.Ctx(), dp.Addresses.Alice)
require.NoError(t, err)
data := make([]byte, rand.Intn(100))
gas, err := core.IntrinsicGas(data, nil, false, true, true, false)
require.NoError(t, err)
baseFee := seqEng.l2Chain.CurrentBlock().BaseFee
tx := types.MustSignNewTx(dp.Secrets.Alice, signer, &types.DynamicFeeTx{
ChainID: sd.L2Cfg.Config.ChainID,
Nonce: aliceNonce,
GasTipCap: big.NewInt(2 * params.GWei),
GasFeeCap: new(big.Int).Add(new(big.Int).Mul(baseFee, big.NewInt(2)), big.NewInt(2*params.GWei)),
Gas: gas,
To: &dp.Addresses.Bob,
Value: big.NewInt(0),
Data: data,
})
// Create valid new block B1 at the same height as A1
block = block.WithBody([]*types.Transaction{block.Transactions()[0], tx}, []*types.Header{})
}
// Add B1, A2 ~ A12 into the channel
_, err = channelOut.AddBlock(block)
require.NoError(t, err)
}
// Submit span batch(B1, A2, ... A12)
batcher.l2ChannelOut = channelOut
batcher.ActL2ChannelClose(t)
batcher.ActL2BatchSubmit(t)
miner.ActL1StartBlock(12)(t)
miner.ActL1IncludeTx(dp.Addresses.Batcher)(t)
miner.ActL1EndBlock(t)
miner.ActL1SafeNext(t)
miner.ActL1FinalizeNext(t)
verifier.ActL1HeadSignal(t)
verifier.ActL2PipelineFull(t)
// verifier should advance its unsafe and safe head to the height of A12.
require.Equal(t, verifier.L2Unsafe().Number, uint64(12))
require.Equal(t, verifier.L2Safe().Number, uint64(12))
}
func TestSpanBatchAtomicity_Consolidation(gt *testing.T) {
t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
minTs := hexutil.Uint64(0)
// Activate SpanBatch hardfork
dp.DeployConfig.L2GenesisSpanBatchTimeOffset = &minTs
dp.DeployConfig.L2BlockTime = 2
sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlInfo)
_, _, miner, sequencer, seqEng, verifier, _, batcher := setupReorgTestActors(t, dp, sd, log)
seqEngCl, err := sources.NewEngineClient(seqEng.RPCClient(), log, nil, sources.EngineClientDefaultConfig(sd.RollupCfg))
require.NoError(t, err)
targetHeadNumber := uint64(6) // L1 block time / L2 block time
sequencer.ActL2PipelineFull(t)
verifier.ActL2PipelineFull(t)
// Create 6 blocks
miner.ActEmptyBlock(t)
sequencer.ActL1HeadSignal(t)
sequencer.ActBuildToL1HeadUnsafe(t)
require.Equal(t, sequencer.L2Unsafe().Number, targetHeadNumber)
// Gossip unsafe blocks to the verifier
for i := uint64(1); i <= sequencer.L2Unsafe().Number; i++ {
seqHead, err := seqEngCl.PayloadByNumber(t.Ctx(), i)
require.NoError(t, err)
verifier.ActL2UnsafeGossipReceive(seqHead)(t)
}
verifier.ActL2PipelineFull(t)
// Check if the verifier's unsafe sync is done
require.Equal(t, sequencer.L2Unsafe().Hash, verifier.L2Unsafe().Hash)
// Build and submit a span batch with 6 blocks
batcher.ActSubmitAll(t)
miner.ActL1StartBlock(12)(t)
miner.ActL1IncludeTx(dp.Addresses.Batcher)(t)
miner.ActL1EndBlock(t)
// Start verifier safe sync
verifier.ActL1HeadSignal(t)
verifier.l2PipelineIdle = false
for !verifier.l2PipelineIdle {
verifier.ActL2PipelineStep(t)
if verifier.L2PendingSafe().Number < targetHeadNumber {
// If the span batch is not fully processed, the safe head must not advance.
require.Equal(t, verifier.L2Safe().Number, uint64(0))
} else {
// Once the span batch is fully processed, the safe head must advance to the end of span batch.
require.Equal(t, verifier.L2Safe().Number, targetHeadNumber)
require.Equal(t, verifier.L2Safe(), verifier.L2PendingSafe())
}
// The unsafe head must not be changed
require.Equal(t, verifier.L2Unsafe(), sequencer.L2Unsafe())
}
}
func TestSpanBatchAtomicity_ForceAdvance(gt *testing.T) {
t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
minTs := hexutil.Uint64(0)
// Activate SpanBatch hardfork
dp.DeployConfig.L2GenesisSpanBatchTimeOffset = &minTs
dp.DeployConfig.L2BlockTime = 2
sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlInfo)
_, _, miner, sequencer, _, verifier, _, batcher := setupReorgTestActors(t, dp, sd, log)
targetHeadNumber := uint64(6) // L1 block time / L2 block time
sequencer.ActL2PipelineFull(t)
verifier.ActL2PipelineFull(t)
require.Equal(t, verifier.L2Unsafe().Number, uint64(0))
// Create 6 blocks
miner.ActEmptyBlock(t)
sequencer.ActL1HeadSignal(t)
sequencer.ActBuildToL1HeadUnsafe(t)
require.Equal(t, sequencer.L2Unsafe().Number, targetHeadNumber)
// Build and submit a span batch with 6 blocks
batcher.ActSubmitAll(t)
miner.ActL1StartBlock(12)(t)
miner.ActL1IncludeTx(dp.Addresses.Batcher)(t)
miner.ActL1EndBlock(t)
// Start verifier safe sync
verifier.ActL1HeadSignal(t)
verifier.l2PipelineIdle = false
for !verifier.l2PipelineIdle {
verifier.ActL2PipelineStep(t)
if verifier.L2PendingSafe().Number < targetHeadNumber {
// If the span batch is not fully processed, the safe head must not advance.
require.Equal(t, verifier.L2Safe().Number, uint64(0))
} else {
// Once the span batch is fully processed, the safe head must advance to the end of span batch.
require.Equal(t, verifier.L2Safe().Number, targetHeadNumber)
require.Equal(t, verifier.L2Safe(), verifier.L2PendingSafe())
}
// The unsafe head and the pending safe head must be the same
require.Equal(t, verifier.L2Unsafe(), verifier.L2PendingSafe())
}
}
...@@ -39,14 +39,14 @@ func TestBatcherKeyRotation(gt *testing.T) { ...@@ -39,14 +39,14 @@ func TestBatcherKeyRotation(gt *testing.T) {
MinL1TxSize: 0, MinL1TxSize: 0,
MaxL1TxSize: 128_000, MaxL1TxSize: 128_000,
BatcherKey: dp.Secrets.Batcher, BatcherKey: dp.Secrets.Batcher,
}, rollupSeqCl, miner.EthClient(), seqEngine.EthClient()) }, rollupSeqCl, miner.EthClient(), seqEngine.EthClient(), seqEngine.EngineClient(t, sd.RollupCfg))
// a batcher with a new key // a batcher with a new key
batcherB := NewL2Batcher(log, sd.RollupCfg, &BatcherCfg{ batcherB := NewL2Batcher(log, sd.RollupCfg, &BatcherCfg{
MinL1TxSize: 0, MinL1TxSize: 0,
MaxL1TxSize: 128_000, MaxL1TxSize: 128_000,
BatcherKey: dp.Secrets.Bob, BatcherKey: dp.Secrets.Bob,
}, rollupSeqCl, miner.EthClient(), seqEngine.EthClient()) }, rollupSeqCl, miner.EthClient(), seqEngine.EthClient(), seqEngine.EngineClient(t, sd.RollupCfg))
sequencer.ActL2PipelineFull(t) sequencer.ActL2PipelineFull(t)
verifier.ActL2PipelineFull(t) verifier.ActL2PipelineFull(t)
...@@ -210,7 +210,7 @@ func TestGPOParamsChange(gt *testing.T) { ...@@ -210,7 +210,7 @@ func TestGPOParamsChange(gt *testing.T) {
MinL1TxSize: 0, MinL1TxSize: 0,
MaxL1TxSize: 128_000, MaxL1TxSize: 128_000,
BatcherKey: dp.Secrets.Batcher, BatcherKey: dp.Secrets.Batcher,
}, sequencer.RollupClient(), miner.EthClient(), seqEngine.EthClient()) }, sequencer.RollupClient(), miner.EthClient(), seqEngine.EthClient(), seqEngine.EngineClient(t, sd.RollupCfg))
alice := NewBasicUser[any](log, dp.Secrets.Alice, rand.New(rand.NewSource(1234))) alice := NewBasicUser[any](log, dp.Secrets.Alice, rand.New(rand.NewSource(1234)))
alice.SetUserEnv(&BasicUserEnv[any]{ alice.SetUserEnv(&BasicUserEnv[any]{
...@@ -339,7 +339,7 @@ func TestGasLimitChange(gt *testing.T) { ...@@ -339,7 +339,7 @@ func TestGasLimitChange(gt *testing.T) {
MinL1TxSize: 0, MinL1TxSize: 0,
MaxL1TxSize: 128_000, MaxL1TxSize: 128_000,
BatcherKey: dp.Secrets.Batcher, BatcherKey: dp.Secrets.Batcher,
}, sequencer.RollupClient(), miner.EthClient(), seqEngine.EthClient()) }, sequencer.RollupClient(), miner.EthClient(), seqEngine.EthClient(), seqEngine.EngineClient(t, sd.RollupCfg))
sequencer.ActL2PipelineFull(t) sequencer.ActL2PipelineFull(t)
miner.ActEmptyBlock(t) miner.ActEmptyBlock(t)
......
...@@ -60,7 +60,7 @@ func runCrossLayerUserTest(gt *testing.T, test regolithScheduledTest) { ...@@ -60,7 +60,7 @@ func runCrossLayerUserTest(gt *testing.T, test regolithScheduledTest) {
MinL1TxSize: 0, MinL1TxSize: 0,
MaxL1TxSize: 128_000, MaxL1TxSize: 128_000,
BatcherKey: dp.Secrets.Batcher, BatcherKey: dp.Secrets.Batcher,
}, seq.RollupClient(), miner.EthClient(), seqEngine.EthClient()) }, seq.RollupClient(), miner.EthClient(), seqEngine.EthClient(), seqEngine.EngineClient(t, sd.RollupCfg))
proposer := NewL2Proposer(t, log, &ProposerCfg{ proposer := NewL2Proposer(t, log, &ProposerCfg{
OutputOracleAddr: sd.DeploymentsL1.L2OutputOracleProxy, OutputOracleAddr: sd.DeploymentsL1.L2OutputOracleProxy,
ProposerKey: dp.Secrets.Proposer, ProposerKey: dp.Secrets.Proposer,
......
...@@ -6,6 +6,8 @@ import ( ...@@ -6,6 +6,8 @@ import (
"math/big" "math/big"
"time" "time"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
) )
...@@ -57,3 +59,12 @@ func ForNextBlock(ctx context.Context, client BlockCaller) error { ...@@ -57,3 +59,12 @@ func ForNextBlock(ctx context.Context, client BlockCaller) error {
} }
return ForBlock(ctx, client, current+1) return ForBlock(ctx, client, current+1)
} }
func ForProcessingFullBatch(ctx context.Context, rollupCl *sources.RollupClient) error {
_, err := AndGet(ctx, time.Second, func() (*eth.SyncStatus, error) {
return rollupCl.SyncStatus(ctx)
}, func(syncStatus *eth.SyncStatus) bool {
return syncStatus.PendingSafeL2 == syncStatus.SafeL2
})
return err
}
...@@ -678,14 +678,13 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste ...@@ -678,14 +678,13 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to setup l2 output submitter: %w", err) return nil, fmt.Errorf("unable to setup l2 output submitter: %w", err)
} }
if err := proposer.Start(context.Background()); err != nil { if err := proposer.Start(context.Background()); err != nil {
return nil, fmt.Errorf("unable to start l2 output submitter: %w", err) return nil, fmt.Errorf("unable to start l2 output submitter: %w", err)
} }
sys.L2OutputSubmitter = proposer sys.L2OutputSubmitter = proposer
batchType := derive.SingularBatchType var batchType uint = derive.SingularBatchType
if os.Getenv("OP_E2E_USE_SPAN_BATCH") == "true" { if os.Getenv("OP_E2E_USE_SPAN_BATCH") == "true" {
batchType = derive.SpanBatchType batchType = derive.SpanBatchType
} }
...@@ -713,7 +712,7 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste ...@@ -713,7 +712,7 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste
Format: oplog.FormatText, Format: oplog.FormatText,
}, },
Stopped: sys.cfg.DisableBatcher, // Batch submitter may be enabled later Stopped: sys.cfg.DisableBatcher, // Batch submitter may be enabled later
BatchType: uint(batchType), BatchType: batchType,
} }
// Batch Submitter // Batch Submitter
batcher, err := bss.BatcherServiceFromCLIConfig(context.Background(), "0.0.1", batcherCLIConfig, sys.cfg.Loggers["batcher"]) batcher, err := bss.BatcherServiceFromCLIConfig(context.Background(), "0.0.1", batcherCLIConfig, sys.cfg.Loggers["batcher"])
......
...@@ -1259,6 +1259,7 @@ func TestStopStartBatcher(t *testing.T) { ...@@ -1259,6 +1259,7 @@ func TestStopStartBatcher(t *testing.T) {
safeBlockInclusionDuration := time.Duration(6*cfg.DeployConfig.L1BlockTime) * time.Second safeBlockInclusionDuration := time.Duration(6*cfg.DeployConfig.L1BlockTime) * time.Second
_, err = geth.WaitForBlock(receipt.BlockNumber, l2Verif, safeBlockInclusionDuration) _, err = geth.WaitForBlock(receipt.BlockNumber, l2Verif, safeBlockInclusionDuration)
require.Nil(t, err, "Waiting for block on verifier") require.Nil(t, err, "Waiting for block on verifier")
require.NoError(t, wait.ForProcessingFullBatch(context.Background(), rollupClient))
// ensure the safe chain advances // ensure the safe chain advances
newSeqStatus, err := rollupClient.SyncStatus(context.Background()) newSeqStatus, err := rollupClient.SyncStatus(context.Background())
...@@ -1296,6 +1297,7 @@ func TestStopStartBatcher(t *testing.T) { ...@@ -1296,6 +1297,7 @@ func TestStopStartBatcher(t *testing.T) {
// wait until the block the tx was first included in shows up in the safe chain on the verifier // wait until the block the tx was first included in shows up in the safe chain on the verifier
_, err = geth.WaitForBlock(receipt.BlockNumber, l2Verif, safeBlockInclusionDuration) _, err = geth.WaitForBlock(receipt.BlockNumber, l2Verif, safeBlockInclusionDuration)
require.Nil(t, err, "Waiting for block on verifier") require.Nil(t, err, "Waiting for block on verifier")
require.NoError(t, wait.ForProcessingFullBatch(context.Background(), rollupClient))
// ensure that the safe chain advances after restarting the batcher // ensure that the safe chain advances after restarting the batcher
newSeqStatus, err = rollupClient.SyncStatus(context.Background()) newSeqStatus, err = rollupClient.SyncStatus(context.Background())
......
...@@ -21,6 +21,7 @@ import ( ...@@ -21,6 +21,7 @@ import (
"github.com/ethereum-optimism/optimism/op-service/cliapp" "github.com/ethereum-optimism/optimism/op-service/cliapp"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/metrics/doc" "github.com/ethereum-optimism/optimism/op-service/metrics/doc"
"github.com/ethereum-optimism/optimism/op-service/opio"
) )
var ( var (
...@@ -58,7 +59,8 @@ func main() { ...@@ -58,7 +59,8 @@ func main() {
}, },
} }
err := app.Run(os.Args) ctx := opio.WithInterruptBlocker(context.Background())
err := app.RunContext(ctx, os.Args)
if err != nil { if err != nil {
log.Crit("Application failed", "message", err) log.Crit("Application failed", "message", err)
} }
......
...@@ -190,7 +190,8 @@ func P2PFlags(envPrefix string) []cli.Flag { ...@@ -190,7 +190,8 @@ func P2PFlags(envPrefix string) []cli.Flag {
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: StaticPeersName, Name: StaticPeersName,
Usage: "Comma-separated multiaddr-format peer list. Static connections to make and maintain, these peers will be regarded as trusted.", Usage: "Comma-separated multiaddr-format peer list. Static connections to make and maintain, these peers will be regarded as trusted. " +
"Addresses of the local peer are ignored. Duplicate/Alternative addresses for the same peer all apply, but only a single connection per peer is maintained.",
Required: false, Required: false,
Value: "", Value: "",
EnvVars: p2pEnv(envPrefix, "STATIC"), EnvVars: p2pEnv(envPrefix, "STATIC"),
......
...@@ -161,6 +161,7 @@ func randomSyncStatus(rng *rand.Rand) *eth.SyncStatus { ...@@ -161,6 +161,7 @@ func randomSyncStatus(rng *rand.Rand) *eth.SyncStatus {
UnsafeL2: testutils.RandomL2BlockRef(rng), UnsafeL2: testutils.RandomL2BlockRef(rng),
SafeL2: testutils.RandomL2BlockRef(rng), SafeL2: testutils.RandomL2BlockRef(rng),
FinalizedL2: testutils.RandomL2BlockRef(rng), FinalizedL2: testutils.RandomL2BlockRef(rng),
PendingSafeL2: testutils.RandomL2BlockRef(rng),
UnsafeL2SyncTarget: testutils.RandomL2BlockRef(rng), UnsafeL2SyncTarget: testutils.RandomL2BlockRef(rng),
EngineSyncTarget: testutils.RandomL2BlockRef(rng), EngineSyncTarget: testutils.RandomL2BlockRef(rng),
} }
......
...@@ -229,13 +229,17 @@ func (conf *Config) Host(log log.Logger, reporter metrics.Reporter, metrics Host ...@@ -229,13 +229,17 @@ func (conf *Config) Host(log log.Logger, reporter metrics.Reporter, metrics Host
return nil, err return nil, err
} }
staticPeers := make([]*peer.AddrInfo, len(conf.StaticPeers)) staticPeers := make([]*peer.AddrInfo, 0, len(conf.StaticPeers))
for i, peerAddr := range conf.StaticPeers { for _, peerAddr := range conf.StaticPeers {
addr, err := peer.AddrInfoFromP2pAddr(peerAddr) addr, err := peer.AddrInfoFromP2pAddr(peerAddr)
if err != nil { if err != nil {
return nil, fmt.Errorf("bad peer address: %w", err) return nil, fmt.Errorf("bad peer address: %w", err)
} }
staticPeers[i] = addr if addr.ID == h.ID() {
log.Info("Static-peer list contains address of local peer, ignoring the address.", "peer_id", addr.ID, "addrs", addr.Addrs)
continue
}
staticPeers = append(staticPeers, addr)
} }
out := &extraHost{ out := &extraHost{
......
...@@ -15,6 +15,7 @@ import ( ...@@ -15,6 +15,7 @@ import (
"github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/peer"
mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock"
ma "github.com/multiformats/go-multiaddr"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
...@@ -139,6 +140,13 @@ func TestP2PFull(t *testing.T) { ...@@ -139,6 +140,13 @@ func TestP2PFull(t *testing.T) {
confB.StaticPeers, err = peer.AddrInfoToP2pAddrs(&peer.AddrInfo{ID: hostA.ID(), Addrs: hostA.Addrs()}) confB.StaticPeers, err = peer.AddrInfoToP2pAddrs(&peer.AddrInfo{ID: hostA.ID(), Addrs: hostA.Addrs()})
require.NoError(t, err) require.NoError(t, err)
// Add address of host B itself, it shouldn't connect or cause issues.
idB, err := peer.IDFromPublicKey(confB.Priv.GetPublic())
require.NoError(t, err)
altAddrB, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/12345/p2p/" + idB.String())
require.NoError(t, err)
confB.StaticPeers = append(confB.StaticPeers, altAddrB)
logB := testlog.Logger(t, log.LvlError).New("host", "B") logB := testlog.Logger(t, log.LvlError).New("host", "B")
nodeB, err := NewNodeP2P(context.Background(), &rollup.Config{}, logB, &confB, &mockGossipIn{}, nil, runCfgB, metrics.NoopMetrics) nodeB, err := NewNodeP2P(context.Background(), &rollup.Config{}, logB, &confB, &mockGossipIn{}, nil, runCfgB, metrics.NoopMetrics)
...@@ -146,6 +154,9 @@ func TestP2PFull(t *testing.T) { ...@@ -146,6 +154,9 @@ func TestP2PFull(t *testing.T) {
defer nodeB.Close() defer nodeB.Close()
hostB := nodeB.Host() hostB := nodeB.Host()
require.True(t, nodeB.IsStatic(hostA.ID()), "node A must be static peer of node B")
require.False(t, nodeB.IsStatic(hostB.ID()), "node B must not be static peer of node B itself")
select { select {
case <-time.After(time.Second): case <-time.After(time.Second):
t.Fatal("failed to connect new host") t.Fatal("failed to connect new host")
......
...@@ -33,6 +33,7 @@ type AttributesQueue struct { ...@@ -33,6 +33,7 @@ type AttributesQueue struct {
builder AttributesBuilder builder AttributesBuilder
prev *BatchQueue prev *BatchQueue
batch *SingularBatch batch *SingularBatch
isLastInSpan bool
} }
func NewAttributesQueue(log log.Logger, cfg *rollup.Config, builder AttributesBuilder, prev *BatchQueue) *AttributesQueue { func NewAttributesQueue(log log.Logger, cfg *rollup.Config, builder AttributesBuilder, prev *BatchQueue) *AttributesQueue {
...@@ -48,23 +49,26 @@ func (aq *AttributesQueue) Origin() eth.L1BlockRef { ...@@ -48,23 +49,26 @@ func (aq *AttributesQueue) Origin() eth.L1BlockRef {
return aq.prev.Origin() return aq.prev.Origin()
} }
func (aq *AttributesQueue) NextAttributes(ctx context.Context, l2SafeHead eth.L2BlockRef) (*eth.PayloadAttributes, error) { func (aq *AttributesQueue) NextAttributes(ctx context.Context, parent eth.L2BlockRef) (*AttributesWithParent, error) {
// Get a batch if we need it // Get a batch if we need it
if aq.batch == nil { if aq.batch == nil {
batch, err := aq.prev.NextBatch(ctx, l2SafeHead) batch, isLastInSpan, err := aq.prev.NextBatch(ctx, parent)
if err != nil { if err != nil {
return nil, err return nil, err
} }
aq.batch = batch aq.batch = batch
aq.isLastInSpan = isLastInSpan
} }
// Actually generate the next attributes // Actually generate the next attributes
if attrs, err := aq.createNextAttributes(ctx, aq.batch, l2SafeHead); err != nil { if attrs, err := aq.createNextAttributes(ctx, aq.batch, parent); err != nil {
return nil, err return nil, err
} else { } else {
// Clear out the local state once we will succeed // Clear out the local state once we will succeed
attr := AttributesWithParent{attrs, parent, aq.isLastInSpan}
aq.batch = nil aq.batch = nil
return attrs, nil aq.isLastInSpan = false
return &attr, nil
} }
} }
...@@ -99,5 +103,6 @@ func (aq *AttributesQueue) createNextAttributes(ctx context.Context, batch *Sing ...@@ -99,5 +103,6 @@ func (aq *AttributesQueue) createNextAttributes(ctx context.Context, batch *Sing
func (aq *AttributesQueue) Reset(ctx context.Context, _ eth.L1BlockRef, _ eth.SystemConfig) error { func (aq *AttributesQueue) Reset(ctx context.Context, _ eth.L1BlockRef, _ eth.SystemConfig) error {
aq.batch = nil aq.batch = nil
aq.isLastInSpan = false // overwritten later, but set for consistency
return io.EOF return io.EOF
} }
...@@ -78,40 +78,54 @@ func (bq *BatchQueue) Origin() eth.L1BlockRef { ...@@ -78,40 +78,54 @@ func (bq *BatchQueue) Origin() eth.L1BlockRef {
// popNextBatch pops the next batch from the current queued up span-batch nextSpan. // popNextBatch pops the next batch from the current queued up span-batch nextSpan.
// The queue must be non-empty, or the function will panic. // The queue must be non-empty, or the function will panic.
func (bq *BatchQueue) popNextBatch(safeL2Head eth.L2BlockRef) *SingularBatch { func (bq *BatchQueue) popNextBatch(parent eth.L2BlockRef) *SingularBatch {
if len(bq.nextSpan) == 0 { if len(bq.nextSpan) == 0 {
panic("popping non-existent span-batch, invalid state") panic("popping non-existent span-batch, invalid state")
} }
nextBatch := bq.nextSpan[0] nextBatch := bq.nextSpan[0]
bq.nextSpan = bq.nextSpan[1:] bq.nextSpan = bq.nextSpan[1:]
// Must set ParentHash before return. we can use safeL2Head because the parentCheck is verified in CheckBatch(). // Must set ParentHash before return. we can use parent because the parentCheck is verified in CheckBatch().
nextBatch.ParentHash = safeL2Head.Hash nextBatch.ParentHash = parent.Hash
return nextBatch return nextBatch
} }
func (bq *BatchQueue) maybeAdvanceEpoch(nextBatch *SingularBatch) { // NextBatch return next valid batch upon the given safe head.
if len(bq.l1Blocks) == 0 { // It also returns the boolean that indicates if the batch is the last block in the batch.
return func (bq *BatchQueue) NextBatch(ctx context.Context, parent eth.L2BlockRef) (*SingularBatch, bool, error) {
if len(bq.nextSpan) > 0 {
// There are cached singular batches derived from the span batch.
// Check if the next cached batch matches the given parent block.
if bq.nextSpan[0].Timestamp == parent.Time+bq.config.BlockTime {
// Pop first one and return.
nextBatch := bq.popNextBatch(parent)
// len(bq.nextSpan) == 0 means it's the last batch of the span.
return nextBatch, len(bq.nextSpan) == 0, nil
} else {
// Given parent block does not match the next batch. It means the previously returned batch is invalid.
// Drop cached batches and find another batch.
bq.nextSpan = bq.nextSpan[:0]
} }
if nextBatch.GetEpochNum() == rollup.Epoch(bq.l1Blocks[0].Number)+1 {
// Advance epoch if necessary
bq.l1Blocks = bq.l1Blocks[1:]
} }
}
func (bq *BatchQueue) NextBatch(ctx context.Context, safeL2Head eth.L2BlockRef) (*SingularBatch, error) { // If the epoch is advanced, update bq.l1Blocks
if len(bq.nextSpan) > 0 { // Advancing epoch must be done after the pipeline successfully apply the entire span batch to the chain.
// If there are cached singular batches, pop first one and return. // Because the span batch can be reverted during processing the batch, then we must preserve existing l1Blocks
nextBatch := bq.popNextBatch(safeL2Head) // to verify the epochs of the next candidate batch.
bq.maybeAdvanceEpoch(nextBatch) if len(bq.l1Blocks) > 0 && parent.L1Origin.Number > bq.l1Blocks[0].Number {
return nextBatch, nil for i, l1Block := range bq.l1Blocks {
if parent.L1Origin.Number == l1Block.Number {
bq.l1Blocks = bq.l1Blocks[i:]
break
}
}
// If we can't find the origin of parent block, we have to advance bq.origin.
} }
// Note: We use the origin that we will have to determine if it's behind. This is important // Note: We use the origin that we will have to determine if it's behind. This is important
// because it's the future origin that gets saved into the l1Blocks array. // because it's the future origin that gets saved into the l1Blocks array.
// We always update the origin of this stage if it is not the same so after the update code // We always update the origin of this stage if it is not the same so after the update code
// runs, this is consistent. // runs, this is consistent.
originBehind := bq.prev.Origin().Number < safeL2Head.L1Origin.Number originBehind := bq.prev.Origin().Number < parent.L1Origin.Number
// Advance origin if needed // Advance origin if needed
// Note: The entire pipeline has the same origin // Note: The entire pipeline has the same origin
...@@ -134,29 +148,29 @@ func (bq *BatchQueue) NextBatch(ctx context.Context, safeL2Head eth.L2BlockRef) ...@@ -134,29 +148,29 @@ func (bq *BatchQueue) NextBatch(ctx context.Context, safeL2Head eth.L2BlockRef)
if batch, err := bq.prev.NextBatch(ctx); err == io.EOF { if batch, err := bq.prev.NextBatch(ctx); err == io.EOF {
outOfData = true outOfData = true
} else if err != nil { } else if err != nil {
return nil, err return nil, false, err
} else if !originBehind { } else if !originBehind {
bq.AddBatch(ctx, batch, safeL2Head) bq.AddBatch(ctx, batch, parent)
} }
// Skip adding data unless we are up to date with the origin, but do fully // Skip adding data unless we are up to date with the origin, but do fully
// empty the previous stages // empty the previous stages
if originBehind { if originBehind {
if outOfData { if outOfData {
return nil, io.EOF return nil, false, io.EOF
} else { } else {
return nil, NotEnoughData return nil, false, NotEnoughData
} }
} }
// Finally attempt to derive more batches // Finally attempt to derive more batches
batch, err := bq.deriveNextBatch(ctx, outOfData, safeL2Head) batch, err := bq.deriveNextBatch(ctx, outOfData, parent)
if err == io.EOF && outOfData { if err == io.EOF && outOfData {
return nil, io.EOF return nil, false, io.EOF
} else if err == io.EOF { } else if err == io.EOF {
return nil, NotEnoughData return nil, false, NotEnoughData
} else if err != nil { } else if err != nil {
return nil, err return nil, false, err
} }
var nextBatch *SingularBatch var nextBatch *SingularBatch
...@@ -164,28 +178,29 @@ func (bq *BatchQueue) NextBatch(ctx context.Context, safeL2Head eth.L2BlockRef) ...@@ -164,28 +178,29 @@ func (bq *BatchQueue) NextBatch(ctx context.Context, safeL2Head eth.L2BlockRef)
case SingularBatchType: case SingularBatchType:
singularBatch, ok := batch.(*SingularBatch) singularBatch, ok := batch.(*SingularBatch)
if !ok { if !ok {
return nil, NewCriticalError(errors.New("failed type assertion to SingularBatch")) return nil, false, NewCriticalError(errors.New("failed type assertion to SingularBatch"))
} }
nextBatch = singularBatch nextBatch = singularBatch
case SpanBatchType: case SpanBatchType:
spanBatch, ok := batch.(*SpanBatch) spanBatch, ok := batch.(*SpanBatch)
if !ok { if !ok {
return nil, NewCriticalError(errors.New("failed type assertion to SpanBatch")) return nil, false, NewCriticalError(errors.New("failed type assertion to SpanBatch"))
} }
// If next batch is SpanBatch, convert it to SingularBatches. // If next batch is SpanBatch, convert it to SingularBatches.
singularBatches, err := spanBatch.GetSingularBatches(bq.l1Blocks, safeL2Head) singularBatches, err := spanBatch.GetSingularBatches(bq.l1Blocks, parent)
if err != nil { if err != nil {
return nil, NewCriticalError(err) return nil, false, NewCriticalError(err)
} }
bq.nextSpan = singularBatches bq.nextSpan = singularBatches
// span-batches are non-empty, so the below pop is safe. // span-batches are non-empty, so the below pop is safe.
nextBatch = bq.popNextBatch(safeL2Head) nextBatch = bq.popNextBatch(parent)
default: default:
return nil, NewCriticalError(fmt.Errorf("unrecognized batch type: %d", batch.GetBatchType())) return nil, false, NewCriticalError(fmt.Errorf("unrecognized batch type: %d", batch.GetBatchType()))
} }
bq.maybeAdvanceEpoch(nextBatch) // If the nextBatch is derived from the span batch, len(bq.nextSpan) == 0 means it's the last batch of the span.
return nextBatch, nil // For singular batches, len(bq.nextSpan) == 0 is always true.
return nextBatch, len(bq.nextSpan) == 0, nil
} }
func (bq *BatchQueue) Reset(ctx context.Context, base eth.L1BlockRef, _ eth.SystemConfig) error { func (bq *BatchQueue) Reset(ctx context.Context, base eth.L1BlockRef, _ eth.SystemConfig) error {
...@@ -202,7 +217,7 @@ func (bq *BatchQueue) Reset(ctx context.Context, base eth.L1BlockRef, _ eth.Syst ...@@ -202,7 +217,7 @@ func (bq *BatchQueue) Reset(ctx context.Context, base eth.L1BlockRef, _ eth.Syst
return io.EOF return io.EOF
} }
func (bq *BatchQueue) AddBatch(ctx context.Context, batch Batch, l2SafeHead eth.L2BlockRef) { func (bq *BatchQueue) AddBatch(ctx context.Context, batch Batch, parent eth.L2BlockRef) {
if len(bq.l1Blocks) == 0 { if len(bq.l1Blocks) == 0 {
panic(fmt.Errorf("cannot add batch with timestamp %d, no origin was prepared", batch.GetTimestamp())) panic(fmt.Errorf("cannot add batch with timestamp %d, no origin was prepared", batch.GetTimestamp()))
} }
...@@ -210,7 +225,7 @@ func (bq *BatchQueue) AddBatch(ctx context.Context, batch Batch, l2SafeHead eth. ...@@ -210,7 +225,7 @@ func (bq *BatchQueue) AddBatch(ctx context.Context, batch Batch, l2SafeHead eth.
L1InclusionBlock: bq.origin, L1InclusionBlock: bq.origin,
Batch: batch, Batch: batch,
} }
validity := CheckBatch(ctx, bq.config, bq.log, bq.l1Blocks, l2SafeHead, &data, bq.l2) validity := CheckBatch(ctx, bq.config, bq.log, bq.l1Blocks, parent, &data, bq.l2)
if validity == BatchDrop { if validity == BatchDrop {
return // if we do drop the batch, CheckBatch will log the drop reason with WARN level. return // if we do drop the batch, CheckBatch will log the drop reason with WARN level.
} }
...@@ -222,24 +237,24 @@ func (bq *BatchQueue) AddBatch(ctx context.Context, batch Batch, l2SafeHead eth. ...@@ -222,24 +237,24 @@ func (bq *BatchQueue) AddBatch(ctx context.Context, batch Batch, l2SafeHead eth.
// following the validity rules imposed on consecutive batches, // following the validity rules imposed on consecutive batches,
// based on currently available buffered batch and L1 origin information. // based on currently available buffered batch and L1 origin information.
// If no batch can be derived yet, then (nil, io.EOF) is returned. // If no batch can be derived yet, then (nil, io.EOF) is returned.
func (bq *BatchQueue) deriveNextBatch(ctx context.Context, outOfData bool, l2SafeHead eth.L2BlockRef) (Batch, error) { func (bq *BatchQueue) deriveNextBatch(ctx context.Context, outOfData bool, parent eth.L2BlockRef) (Batch, error) {
if len(bq.l1Blocks) == 0 { if len(bq.l1Blocks) == 0 {
return nil, NewCriticalError(errors.New("cannot derive next batch, no origin was prepared")) return nil, NewCriticalError(errors.New("cannot derive next batch, no origin was prepared"))
} }
epoch := bq.l1Blocks[0] epoch := bq.l1Blocks[0]
bq.log.Trace("Deriving the next batch", "epoch", epoch, "l2SafeHead", l2SafeHead, "outOfData", outOfData) bq.log.Trace("Deriving the next batch", "epoch", epoch, "parent", parent, "outOfData", outOfData)
// Note: epoch origin can now be one block ahead of the L2 Safe Head // Note: epoch origin can now be one block ahead of the L2 Safe Head
// This is in the case where we auto generate all batches in an epoch & advance the epoch // This is in the case where we auto generate all batches in an epoch & advance the epoch
// but don't advance the L2 Safe Head's epoch // but don't advance the L2 Safe Head's epoch
if l2SafeHead.L1Origin != epoch.ID() && l2SafeHead.L1Origin.Number != epoch.Number-1 { if parent.L1Origin != epoch.ID() && parent.L1Origin.Number != epoch.Number-1 {
return nil, NewResetError(fmt.Errorf("buffered L1 chain epoch %s in batch queue does not match safe head origin %s", epoch, l2SafeHead.L1Origin)) return nil, NewResetError(fmt.Errorf("buffered L1 chain epoch %s in batch queue does not match safe head origin %s", epoch, parent.L1Origin))
} }
// Find the first-seen batch that matches all validity conditions. // Find the first-seen batch that matches all validity conditions.
// We may not have sufficient information to proceed filtering, and then we stop. // We may not have sufficient information to proceed filtering, and then we stop.
// There may be none: in that case we force-create an empty batch // There may be none: in that case we force-create an empty batch
nextTimestamp := l2SafeHead.Time + bq.config.BlockTime nextTimestamp := parent.Time + bq.config.BlockTime
var nextBatch *BatchWithL1InclusionBlock var nextBatch *BatchWithL1InclusionBlock
// Go over all batches, in order of inclusion, and find the first batch we can accept. // Go over all batches, in order of inclusion, and find the first batch we can accept.
...@@ -247,15 +262,15 @@ func (bq *BatchQueue) deriveNextBatch(ctx context.Context, outOfData bool, l2Saf ...@@ -247,15 +262,15 @@ func (bq *BatchQueue) deriveNextBatch(ctx context.Context, outOfData bool, l2Saf
var remaining []*BatchWithL1InclusionBlock var remaining []*BatchWithL1InclusionBlock
batchLoop: batchLoop:
for i, batch := range bq.batches { for i, batch := range bq.batches {
validity := CheckBatch(ctx, bq.config, bq.log.New("batch_index", i), bq.l1Blocks, l2SafeHead, batch, bq.l2) validity := CheckBatch(ctx, bq.config, bq.log.New("batch_index", i), bq.l1Blocks, parent, batch, bq.l2)
switch validity { switch validity {
case BatchFuture: case BatchFuture:
remaining = append(remaining, batch) remaining = append(remaining, batch)
continue continue
case BatchDrop: case BatchDrop:
batch.Batch.LogContext(bq.log).Warn("Dropping batch", batch.Batch.LogContext(bq.log).Warn("Dropping batch",
"l2_safe_head", l2SafeHead.ID(), "parent", parent.ID(),
"l2_safe_head_time", l2SafeHead.Time, "parent_time", parent.Time,
) )
continue continue
case BatchAccept: case BatchAccept:
...@@ -283,7 +298,7 @@ batchLoop: ...@@ -283,7 +298,7 @@ batchLoop:
// i.e. if the sequence window expired, we create empty batches for the current epoch // i.e. if the sequence window expired, we create empty batches for the current epoch
expiryEpoch := epoch.Number + bq.config.SeqWindowSize expiryEpoch := epoch.Number + bq.config.SeqWindowSize
forceEmptyBatches := (expiryEpoch == bq.origin.Number && outOfData) || expiryEpoch < bq.origin.Number forceEmptyBatches := (expiryEpoch == bq.origin.Number && outOfData) || expiryEpoch < bq.origin.Number
firstOfEpoch := epoch.Number == l2SafeHead.L1Origin.Number+1 firstOfEpoch := epoch.Number == parent.L1Origin.Number+1
bq.log.Trace("Potentially generating an empty batch", bq.log.Trace("Potentially generating an empty batch",
"expiryEpoch", expiryEpoch, "forceEmptyBatches", forceEmptyBatches, "nextTimestamp", nextTimestamp, "expiryEpoch", expiryEpoch, "forceEmptyBatches", forceEmptyBatches, "nextTimestamp", nextTimestamp,
...@@ -306,7 +321,7 @@ batchLoop: ...@@ -306,7 +321,7 @@ batchLoop:
if nextTimestamp < nextEpoch.Time || firstOfEpoch { if nextTimestamp < nextEpoch.Time || firstOfEpoch {
bq.log.Info("Generating next batch", "epoch", epoch, "timestamp", nextTimestamp) bq.log.Info("Generating next batch", "epoch", epoch, "timestamp", nextTimestamp)
return &SingularBatch{ return &SingularBatch{
ParentHash: l2SafeHead.Hash, ParentHash: parent.Hash,
EpochNum: rollup.Epoch(epoch.Number), EpochNum: rollup.Epoch(epoch.Number),
EpochHash: epoch.Hash, EpochHash: epoch.Hash,
Timestamp: nextTimestamp, Timestamp: nextTimestamp,
......
...@@ -197,7 +197,7 @@ func BatchQueueNewOrigin(t *testing.T, batchType int) { ...@@ -197,7 +197,7 @@ func BatchQueueNewOrigin(t *testing.T, batchType int) {
// Prev Origin: 0; Safehead Origin: 2; Internal Origin: 0 // Prev Origin: 0; Safehead Origin: 2; Internal Origin: 0
// Should return no data but keep the same origin // Should return no data but keep the same origin
data, err := bq.NextBatch(context.Background(), safeHead) data, _, err := bq.NextBatch(context.Background(), safeHead)
require.Nil(t, data) require.Nil(t, data)
require.Equal(t, io.EOF, err) require.Equal(t, io.EOF, err)
require.Equal(t, []eth.L1BlockRef{l1[0]}, bq.l1Blocks) require.Equal(t, []eth.L1BlockRef{l1[0]}, bq.l1Blocks)
...@@ -206,7 +206,7 @@ func BatchQueueNewOrigin(t *testing.T, batchType int) { ...@@ -206,7 +206,7 @@ func BatchQueueNewOrigin(t *testing.T, batchType int) {
// Prev Origin: 1; Safehead Origin: 2; Internal Origin: 0 // Prev Origin: 1; Safehead Origin: 2; Internal Origin: 0
// Should wipe l1blocks + advance internal origin // Should wipe l1blocks + advance internal origin
input.origin = l1[1] input.origin = l1[1]
data, err = bq.NextBatch(context.Background(), safeHead) data, _, err = bq.NextBatch(context.Background(), safeHead)
require.Nil(t, data) require.Nil(t, data)
require.Equal(t, io.EOF, err) require.Equal(t, io.EOF, err)
require.Empty(t, bq.l1Blocks) require.Empty(t, bq.l1Blocks)
...@@ -215,7 +215,7 @@ func BatchQueueNewOrigin(t *testing.T, batchType int) { ...@@ -215,7 +215,7 @@ func BatchQueueNewOrigin(t *testing.T, batchType int) {
// Prev Origin: 2; Safehead Origin: 2; Internal Origin: 1 // Prev Origin: 2; Safehead Origin: 2; Internal Origin: 1
// Should add to l1Blocks + advance internal origin // Should add to l1Blocks + advance internal origin
input.origin = l1[2] input.origin = l1[2]
data, err = bq.NextBatch(context.Background(), safeHead) data, _, err = bq.NextBatch(context.Background(), safeHead)
require.Nil(t, data) require.Nil(t, data)
require.Equal(t, io.EOF, err) require.Equal(t, io.EOF, err)
require.Equal(t, []eth.L1BlockRef{l1[2]}, bq.l1Blocks) require.Equal(t, []eth.L1BlockRef{l1[2]}, bq.l1Blocks)
...@@ -286,7 +286,7 @@ func BatchQueueEager(t *testing.T, batchType int) { ...@@ -286,7 +286,7 @@ func BatchQueueEager(t *testing.T, batchType int) {
input.origin = l1[1] input.origin = l1[1]
for i := 0; i < len(expectedOutputBatches); i++ { for i := 0; i < len(expectedOutputBatches); i++ {
b, e := bq.NextBatch(context.Background(), safeHead) b, _, e := bq.NextBatch(context.Background(), safeHead)
require.ErrorIs(t, e, expectedOutputErrors[i]) require.ErrorIs(t, e, expectedOutputErrors[i])
if b == nil { if b == nil {
require.Nil(t, expectedOutputBatches[i]) require.Nil(t, expectedOutputBatches[i])
...@@ -363,7 +363,7 @@ func BatchQueueInvalidInternalAdvance(t *testing.T, batchType int) { ...@@ -363,7 +363,7 @@ func BatchQueueInvalidInternalAdvance(t *testing.T, batchType int) {
// Load continuous batches for epoch 0 // Load continuous batches for epoch 0
for i := 0; i < len(expectedOutputBatches); i++ { for i := 0; i < len(expectedOutputBatches); i++ {
b, e := bq.NextBatch(context.Background(), safeHead) b, _, e := bq.NextBatch(context.Background(), safeHead)
require.ErrorIs(t, e, expectedOutputErrors[i]) require.ErrorIs(t, e, expectedOutputErrors[i])
if b == nil { if b == nil {
require.Nil(t, expectedOutputBatches[i]) require.Nil(t, expectedOutputBatches[i])
...@@ -378,20 +378,20 @@ func BatchQueueInvalidInternalAdvance(t *testing.T, batchType int) { ...@@ -378,20 +378,20 @@ func BatchQueueInvalidInternalAdvance(t *testing.T, batchType int) {
// Advance to origin 1. No forced batches yet. // Advance to origin 1. No forced batches yet.
input.origin = l1[1] input.origin = l1[1]
b, e := bq.NextBatch(context.Background(), safeHead) b, _, e := bq.NextBatch(context.Background(), safeHead)
require.ErrorIs(t, e, io.EOF) require.ErrorIs(t, e, io.EOF)
require.Nil(t, b) require.Nil(t, b)
// Advance to origin 2. No forced batches yet because we are still on epoch 0 // Advance to origin 2. No forced batches yet because we are still on epoch 0
// & have batches for epoch 0. // & have batches for epoch 0.
input.origin = l1[2] input.origin = l1[2]
b, e = bq.NextBatch(context.Background(), safeHead) b, _, e = bq.NextBatch(context.Background(), safeHead)
require.ErrorIs(t, e, io.EOF) require.ErrorIs(t, e, io.EOF)
require.Nil(t, b) require.Nil(t, b)
// Advance to origin 3. Should generate one empty batch. // Advance to origin 3. Should generate one empty batch.
input.origin = l1[3] input.origin = l1[3]
b, e = bq.NextBatch(context.Background(), safeHead) b, _, e = bq.NextBatch(context.Background(), safeHead)
require.Nil(t, e) require.Nil(t, e)
require.NotNil(t, b) require.NotNil(t, b)
require.Equal(t, safeHead.Time+2, b.Timestamp) require.Equal(t, safeHead.Time+2, b.Timestamp)
...@@ -400,13 +400,13 @@ func BatchQueueInvalidInternalAdvance(t *testing.T, batchType int) { ...@@ -400,13 +400,13 @@ func BatchQueueInvalidInternalAdvance(t *testing.T, batchType int) {
safeHead.Time += 2 safeHead.Time += 2
safeHead.Hash = mockHash(b.Timestamp, 2) safeHead.Hash = mockHash(b.Timestamp, 2)
safeHead.L1Origin = b.Epoch() safeHead.L1Origin = b.Epoch()
b, e = bq.NextBatch(context.Background(), safeHead) b, _, e = bq.NextBatch(context.Background(), safeHead)
require.ErrorIs(t, e, io.EOF) require.ErrorIs(t, e, io.EOF)
require.Nil(t, b) require.Nil(t, b)
// Advance to origin 4. Should generate one empty batch. // Advance to origin 4. Should generate one empty batch.
input.origin = l1[4] input.origin = l1[4]
b, e = bq.NextBatch(context.Background(), safeHead) b, _, e = bq.NextBatch(context.Background(), safeHead)
require.Nil(t, e) require.Nil(t, e)
require.NotNil(t, b) require.NotNil(t, b)
require.Equal(t, rollup.Epoch(2), b.EpochNum) require.Equal(t, rollup.Epoch(2), b.EpochNum)
...@@ -415,7 +415,7 @@ func BatchQueueInvalidInternalAdvance(t *testing.T, batchType int) { ...@@ -415,7 +415,7 @@ func BatchQueueInvalidInternalAdvance(t *testing.T, batchType int) {
safeHead.Time += 2 safeHead.Time += 2
safeHead.Hash = mockHash(b.Timestamp, 2) safeHead.Hash = mockHash(b.Timestamp, 2)
safeHead.L1Origin = b.Epoch() safeHead.L1Origin = b.Epoch()
b, e = bq.NextBatch(context.Background(), safeHead) b, _, e = bq.NextBatch(context.Background(), safeHead)
require.ErrorIs(t, e, io.EOF) require.ErrorIs(t, e, io.EOF)
require.Nil(t, b) require.Nil(t, b)
...@@ -477,7 +477,7 @@ func BatchQueueMissing(t *testing.T, batchType int) { ...@@ -477,7 +477,7 @@ func BatchQueueMissing(t *testing.T, batchType int) {
_ = bq.Reset(context.Background(), l1[0], eth.SystemConfig{}) _ = bq.Reset(context.Background(), l1[0], eth.SystemConfig{})
for i := 0; i < len(expectedOutputBatches); i++ { for i := 0; i < len(expectedOutputBatches); i++ {
b, e := bq.NextBatch(context.Background(), safeHead) b, _, e := bq.NextBatch(context.Background(), safeHead)
require.ErrorIs(t, e, NotEnoughData) require.ErrorIs(t, e, NotEnoughData)
require.Nil(t, b) require.Nil(t, b)
} }
...@@ -485,7 +485,7 @@ func BatchQueueMissing(t *testing.T, batchType int) { ...@@ -485,7 +485,7 @@ func BatchQueueMissing(t *testing.T, batchType int) {
// advance origin. Underlying stage still has no more inputBatches // advance origin. Underlying stage still has no more inputBatches
// This is not enough to auto advance yet // This is not enough to auto advance yet
input.origin = l1[1] input.origin = l1[1]
b, e := bq.NextBatch(context.Background(), safeHead) b, _, e := bq.NextBatch(context.Background(), safeHead)
require.ErrorIs(t, e, io.EOF) require.ErrorIs(t, e, io.EOF)
require.Nil(t, b) require.Nil(t, b)
...@@ -493,7 +493,7 @@ func BatchQueueMissing(t *testing.T, batchType int) { ...@@ -493,7 +493,7 @@ func BatchQueueMissing(t *testing.T, batchType int) {
input.origin = l1[2] input.origin = l1[2]
// Check for a generated batch at t = 12 // Check for a generated batch at t = 12
b, e = bq.NextBatch(context.Background(), safeHead) b, _, e = bq.NextBatch(context.Background(), safeHead)
require.Nil(t, e) require.Nil(t, e)
require.Equal(t, b.Timestamp, uint64(12)) require.Equal(t, b.Timestamp, uint64(12))
require.Empty(t, b.Transactions) require.Empty(t, b.Transactions)
...@@ -503,7 +503,7 @@ func BatchQueueMissing(t *testing.T, batchType int) { ...@@ -503,7 +503,7 @@ func BatchQueueMissing(t *testing.T, batchType int) {
safeHead.Hash = mockHash(b.Timestamp, 2) safeHead.Hash = mockHash(b.Timestamp, 2)
// Check for generated batch at t = 14 // Check for generated batch at t = 14
b, e = bq.NextBatch(context.Background(), safeHead) b, _, e = bq.NextBatch(context.Background(), safeHead)
require.Nil(t, e) require.Nil(t, e)
require.Equal(t, b.Timestamp, uint64(14)) require.Equal(t, b.Timestamp, uint64(14))
require.Empty(t, b.Transactions) require.Empty(t, b.Transactions)
...@@ -513,7 +513,7 @@ func BatchQueueMissing(t *testing.T, batchType int) { ...@@ -513,7 +513,7 @@ func BatchQueueMissing(t *testing.T, batchType int) {
safeHead.Hash = mockHash(b.Timestamp, 2) safeHead.Hash = mockHash(b.Timestamp, 2)
// Check for the inputted batch at t = 16 // Check for the inputted batch at t = 16
b, e = bq.NextBatch(context.Background(), safeHead) b, _, e = bq.NextBatch(context.Background(), safeHead)
require.Nil(t, e) require.Nil(t, e)
require.Equal(t, b, expectedOutputBatches[0]) require.Equal(t, b, expectedOutputBatches[0])
require.Equal(t, rollup.Epoch(0), b.EpochNum) require.Equal(t, rollup.Epoch(0), b.EpochNum)
...@@ -527,9 +527,9 @@ func BatchQueueMissing(t *testing.T, batchType int) { ...@@ -527,9 +527,9 @@ func BatchQueueMissing(t *testing.T, batchType int) {
// Check for the generated batch at t = 18. This batch advances the epoch // Check for the generated batch at t = 18. This batch advances the epoch
// Note: We need one io.EOF returned from the bq that advances the internal L1 Blocks view // Note: We need one io.EOF returned from the bq that advances the internal L1 Blocks view
// before the batch will be auto generated // before the batch will be auto generated
_, e = bq.NextBatch(context.Background(), safeHead) _, _, e = bq.NextBatch(context.Background(), safeHead)
require.Equal(t, e, io.EOF) require.Equal(t, e, io.EOF)
b, e = bq.NextBatch(context.Background(), safeHead) b, _, e = bq.NextBatch(context.Background(), safeHead)
require.Nil(t, e) require.Nil(t, e)
require.Equal(t, b.Timestamp, uint64(18)) require.Equal(t, b.Timestamp, uint64(18))
require.Empty(t, b.Transactions) require.Empty(t, b.Transactions)
...@@ -610,13 +610,12 @@ func BatchQueueAdvancedEpoch(t *testing.T, batchType int) { ...@@ -610,13 +610,12 @@ func BatchQueueAdvancedEpoch(t *testing.T, batchType int) {
inputOriginNumber += 1 inputOriginNumber += 1
input.origin = l1[inputOriginNumber] input.origin = l1[inputOriginNumber]
} }
b, e := bq.NextBatch(context.Background(), safeHead) b, _, e := bq.NextBatch(context.Background(), safeHead)
require.ErrorIs(t, e, expectedOutputErrors[i]) require.ErrorIs(t, e, expectedOutputErrors[i])
if b == nil { if b == nil {
require.Nil(t, expectedOutput) require.Nil(t, expectedOutput)
} else { } else {
require.Equal(t, expectedOutput, b) require.Equal(t, expectedOutput, b)
require.Equal(t, bq.l1Blocks[0].Number, uint64(b.EpochNum))
safeHead.Number += 1 safeHead.Number += 1
safeHead.Time += cfg.BlockTime safeHead.Time += cfg.BlockTime
safeHead.Hash = mockHash(b.Timestamp, 2) safeHead.Hash = mockHash(b.Timestamp, 2)
...@@ -706,7 +705,7 @@ func BatchQueueShuffle(t *testing.T, batchType int) { ...@@ -706,7 +705,7 @@ func BatchQueueShuffle(t *testing.T, batchType int) {
var e error var e error
for j := 0; j < len(expectedOutputBatches); j++ { for j := 0; j < len(expectedOutputBatches); j++ {
// Multiple NextBatch() executions may be required because the order of input is shuffled // Multiple NextBatch() executions may be required because the order of input is shuffled
b, e = bq.NextBatch(context.Background(), safeHead) b, _, e = bq.NextBatch(context.Background(), safeHead)
if !errors.Is(e, NotEnoughData) { if !errors.Is(e, NotEnoughData) {
break break
} }
...@@ -716,7 +715,6 @@ func BatchQueueShuffle(t *testing.T, batchType int) { ...@@ -716,7 +715,6 @@ func BatchQueueShuffle(t *testing.T, batchType int) {
require.Nil(t, expectedOutput) require.Nil(t, expectedOutput)
} else { } else {
require.Equal(t, expectedOutput, b) require.Equal(t, expectedOutput, b)
require.Equal(t, bq.l1Blocks[0].Number, uint64(b.EpochNum))
safeHead.Number += 1 safeHead.Number += 1
safeHead.Time += cfg.BlockTime safeHead.Time += cfg.BlockTime
safeHead.Hash = mockHash(b.Timestamp, 2) safeHead.Hash = mockHash(b.Timestamp, 2)
...@@ -814,7 +812,7 @@ func TestBatchQueueOverlappingSpanBatch(t *testing.T) { ...@@ -814,7 +812,7 @@ func TestBatchQueueOverlappingSpanBatch(t *testing.T) {
input.origin = l1[1] input.origin = l1[1]
for i := 0; i < len(expectedOutputBatches); i++ { for i := 0; i < len(expectedOutputBatches); i++ {
b, e := bq.NextBatch(context.Background(), safeHead) b, _, e := bq.NextBatch(context.Background(), safeHead)
require.ErrorIs(t, e, expectedOutputErrors[i]) require.ErrorIs(t, e, expectedOutputErrors[i])
if b == nil { if b == nil {
require.Nil(t, expectedOutputBatches[i]) require.Nil(t, expectedOutputBatches[i])
...@@ -928,7 +926,7 @@ func TestBatchQueueComplex(t *testing.T) { ...@@ -928,7 +926,7 @@ func TestBatchQueueComplex(t *testing.T) {
var e error var e error
for j := 0; j < len(expectedOutputBatches); j++ { for j := 0; j < len(expectedOutputBatches); j++ {
// Multiple NextBatch() executions may be required because the order of input is shuffled // Multiple NextBatch() executions may be required because the order of input is shuffled
b, e = bq.NextBatch(context.Background(), safeHead) b, _, e = bq.NextBatch(context.Background(), safeHead)
if !errors.Is(e, NotEnoughData) { if !errors.Is(e, NotEnoughData) {
break break
} }
...@@ -938,7 +936,6 @@ func TestBatchQueueComplex(t *testing.T) { ...@@ -938,7 +936,6 @@ func TestBatchQueueComplex(t *testing.T) {
require.Nil(t, expectedOutput) require.Nil(t, expectedOutput)
} else { } else {
require.Equal(t, expectedOutput, b) require.Equal(t, expectedOutput, b)
require.Equal(t, bq.l1Blocks[0].Number, uint64(b.EpochNum))
safeHead.Number += 1 safeHead.Number += 1
safeHead.Time += cfg.BlockTime safeHead.Time += cfg.BlockTime
safeHead.Hash = mockHash(b.Timestamp, 2) safeHead.Hash = mockHash(b.Timestamp, 2)
...@@ -948,3 +945,70 @@ func TestBatchQueueComplex(t *testing.T) { ...@@ -948,3 +945,70 @@ func TestBatchQueueComplex(t *testing.T) {
l2Client.Mock.AssertExpectations(t) l2Client.Mock.AssertExpectations(t)
} }
func TestBatchQueueResetSpan(t *testing.T) {
log := testlog.Logger(t, log.LvlCrit)
chainId := big.NewInt(1234)
l1 := L1Chain([]uint64{0, 4, 8})
safeHead := eth.L2BlockRef{
Hash: mockHash(0, 2),
Number: 0,
ParentHash: common.Hash{},
Time: 0,
L1Origin: l1[0].ID(),
SequenceNumber: 0,
}
cfg := &rollup.Config{
Genesis: rollup.Genesis{
L2Time: 10,
},
BlockTime: 2,
MaxSequencerDrift: 600,
SeqWindowSize: 30,
SpanBatchTime: getSpanBatchTime(SpanBatchType),
L2ChainID: chainId,
}
singularBatches := []*SingularBatch{
b(cfg.L2ChainID, 2, l1[0]),
b(cfg.L2ChainID, 4, l1[1]),
b(cfg.L2ChainID, 6, l1[1]),
b(cfg.L2ChainID, 8, l1[2]),
}
input := &fakeBatchQueueInput{
batches: []Batch{NewSpanBatch(singularBatches)},
errors: []error{nil},
origin: l1[2],
}
l2Client := testutils.MockL2Client{}
bq := NewBatchQueue(log, cfg, input, &l2Client)
bq.l1Blocks = l1 // Set enough l1 blocks to derive span batch
// This NextBatch() will derive the span batch, return the first singular batch and save rest of batches in span.
nextBatch, _, err := bq.NextBatch(context.Background(), safeHead)
require.NoError(t, err)
require.Equal(t, nextBatch, singularBatches[0])
require.Equal(t, len(bq.nextSpan), len(singularBatches)-1)
// batch queue's epoch should not be advanced until the entire span batch is returned
require.Equal(t, bq.l1Blocks[0], l1[0])
// This NextBatch() will return the second singular batch.
safeHead.Number += 1
safeHead.Time += cfg.BlockTime
safeHead.Hash = mockHash(nextBatch.Timestamp, 2)
safeHead.L1Origin = nextBatch.Epoch()
nextBatch, _, err = bq.NextBatch(context.Background(), safeHead)
require.NoError(t, err)
require.Equal(t, nextBatch, singularBatches[1])
require.Equal(t, len(bq.nextSpan), len(singularBatches)-2)
// batch queue's epoch should not be advanced until the entire span batch is returned
require.Equal(t, bq.l1Blocks[0], l1[0])
// Call NextBatch() with stale safeHead. It means the second batch failed to be processed.
// Batch queue should drop the entire span batch.
nextBatch, _, err = bq.NextBatch(context.Background(), safeHead)
require.Nil(t, nextBatch)
require.ErrorIs(t, err, io.EOF)
require.Equal(t, len(bq.nextSpan), 0)
}
...@@ -47,10 +47,6 @@ func CheckBatch(ctx context.Context, cfg *rollup.Config, log log.Logger, l1Block ...@@ -47,10 +47,6 @@ func CheckBatch(ctx context.Context, cfg *rollup.Config, log log.Logger, l1Block
log.Error("failed type assertion to SpanBatch") log.Error("failed type assertion to SpanBatch")
return BatchDrop return BatchDrop
} }
if !cfg.IsSpanBatch(batch.Batch.GetTimestamp()) {
log.Warn("received SpanBatch before SpanBatch hard fork")
return BatchDrop
}
return checkSpanBatch(ctx, cfg, log, l1Blocks, l2SafeHead, spanBatch, batch.L1InclusionBlock, l2Fetcher) return checkSpanBatch(ctx, cfg, log, l1Blocks, l2SafeHead, spanBatch, batch.L1InclusionBlock, l2Fetcher)
default: default:
log.Warn("Unrecognized batch type: %d", batch.Batch.GetBatchType()) log.Warn("Unrecognized batch type: %d", batch.Batch.GetBatchType())
...@@ -181,6 +177,20 @@ func checkSpanBatch(ctx context.Context, cfg *rollup.Config, log log.Logger, l1B ...@@ -181,6 +177,20 @@ func checkSpanBatch(ctx context.Context, cfg *rollup.Config, log log.Logger, l1B
} }
epoch := l1Blocks[0] epoch := l1Blocks[0]
startEpochNum := uint64(batch.GetStartEpochNum())
batchOrigin := epoch
if startEpochNum == batchOrigin.Number+1 {
if len(l1Blocks) < 2 {
log.Info("eager batch wants to advance epoch, but could not without more L1 blocks", "current_epoch", epoch.ID())
return BatchUndecided
}
batchOrigin = l1Blocks[1]
}
if !cfg.IsSpanBatch(batchOrigin.Time) {
log.Warn("received SpanBatch with L1 origin before SpanBatch hard fork")
return BatchDrop
}
nextTimestamp := l2SafeHead.Time + cfg.BlockTime nextTimestamp := l2SafeHead.Time + cfg.BlockTime
if batch.GetTimestamp() > nextTimestamp { if batch.GetTimestamp() > nextTimestamp {
...@@ -220,8 +230,6 @@ func checkSpanBatch(ctx context.Context, cfg *rollup.Config, log log.Logger, l1B ...@@ -220,8 +230,6 @@ func checkSpanBatch(ctx context.Context, cfg *rollup.Config, log log.Logger, l1B
return BatchDrop return BatchDrop
} }
startEpochNum := uint64(batch.GetStartEpochNum())
// Filter out batches that were included too late. // Filter out batches that were included too late.
if startEpochNum+cfg.SeqWindowSize < l1InclusionBlock.Number { if startEpochNum+cfg.SeqWindowSize < l1InclusionBlock.Number {
log.Warn("batch was included too late, sequence window expired") log.Warn("batch was included too late, sequence window expired")
......
...@@ -711,6 +711,33 @@ func TestValidBatch(t *testing.T) { ...@@ -711,6 +711,33 @@ func TestValidBatch(t *testing.T) {
}), }),
}, },
Expected: BatchUndecided, Expected: BatchUndecided,
ExpectedLog: "eager batch wants to advance epoch, but could not without more L1 blocks",
SpanBatchTime: &minTs,
},
{
Name: "insufficient L1 info for eager derivation - long span",
L1Blocks: []eth.L1BlockRef{l1A}, // don't know about l1B yet
L2SafeHead: l2A2,
Batch: BatchWithL1InclusionBlock{
L1InclusionBlock: l1C,
Batch: NewSpanBatch([]*SingularBatch{
{
ParentHash: l2A3.ParentHash,
EpochNum: rollup.Epoch(l2A3.L1Origin.Number),
EpochHash: l2A3.L1Origin.Hash,
Timestamp: l2A3.Time,
Transactions: nil,
},
{
ParentHash: l2B0.ParentHash,
EpochNum: rollup.Epoch(l2B0.L1Origin.Number),
EpochHash: l2B0.L1Origin.Hash,
Timestamp: l2B0.Time,
Transactions: nil,
},
}),
},
Expected: BatchUndecided,
ExpectedLog: "need more l1 blocks to check entire origins of span batch", ExpectedLog: "need more l1 blocks to check entire origins of span batch",
SpanBatchTime: &minTs, SpanBatchTime: &minTs,
}, },
...@@ -1413,7 +1440,7 @@ func TestValidBatch(t *testing.T) { ...@@ -1413,7 +1440,7 @@ func TestValidBatch(t *testing.T) {
Transactions: []hexutil.Bytes{randTxData}, Transactions: []hexutil.Bytes{randTxData},
}, },
}, },
SpanBatchTime: &l2A2.Time, SpanBatchTime: &l1B.Time,
Expected: BatchAccept, Expected: BatchAccept,
}, },
{ {
...@@ -1432,8 +1459,9 @@ func TestValidBatch(t *testing.T) { ...@@ -1432,8 +1459,9 @@ func TestValidBatch(t *testing.T) {
}, },
}), }),
}, },
SpanBatchTime: &l2A2.Time, SpanBatchTime: &l1B.Time,
Expected: BatchDrop, Expected: BatchDrop,
ExpectedLog: "received SpanBatch with L1 origin before SpanBatch hard fork",
}, },
{ {
Name: "singular batch after hard fork", Name: "singular batch after hard fork",
...@@ -1449,7 +1477,7 @@ func TestValidBatch(t *testing.T) { ...@@ -1449,7 +1477,7 @@ func TestValidBatch(t *testing.T) {
Transactions: []hexutil.Bytes{randTxData}, Transactions: []hexutil.Bytes{randTxData},
}, },
}, },
SpanBatchTime: &l2A0.Time, SpanBatchTime: &l1A.Time,
Expected: BatchAccept, Expected: BatchAccept,
}, },
{ {
...@@ -1468,7 +1496,7 @@ func TestValidBatch(t *testing.T) { ...@@ -1468,7 +1496,7 @@ func TestValidBatch(t *testing.T) {
}, },
}), }),
}, },
SpanBatchTime: &l2A0.Time, SpanBatchTime: &l1A.Time,
Expected: BatchAccept, Expected: BatchAccept,
}, },
} }
......
...@@ -99,6 +99,9 @@ func (cr *ChannelInReader) NextBatch(ctx context.Context) (Batch, error) { ...@@ -99,6 +99,9 @@ func (cr *ChannelInReader) NextBatch(ctx context.Context) (Batch, error) {
return singularBatch, nil return singularBatch, nil
case SpanBatchType: case SpanBatchType:
if origin := cr.Origin(); !cr.cfg.IsSpanBatch(origin.Time) { if origin := cr.Origin(); !cr.cfg.IsSpanBatch(origin.Time) {
// Check hard fork activation with the L1 inclusion block time instead of the L1 origin block time.
// Therefore, even if the batch passed this rule, it can be dropped in the batch queue.
// This is just for early dropping invalid batches as soon as possible.
return nil, NewTemporaryError(fmt.Errorf("cannot accept span batch in L1 block %s at time %d", origin, origin.Time)) return nil, NewTemporaryError(fmt.Errorf("cannot accept span batch in L1 block %s at time %d", origin, origin.Time))
} }
rawSpanBatch, ok := batchData.inner.(*RawSpanBatch) rawSpanBatch, ok := batchData.inner.(*RawSpanBatch)
......
...@@ -17,14 +17,15 @@ import ( ...@@ -17,14 +17,15 @@ import (
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
) )
type attributesWithParent struct { type AttributesWithParent struct {
attributes *eth.PayloadAttributes attributes *eth.PayloadAttributes
parent eth.L2BlockRef parent eth.L2BlockRef
isLastInSpan bool
} }
type NextAttributesProvider interface { type NextAttributesProvider interface {
Origin() eth.L1BlockRef Origin() eth.L1BlockRef
NextAttributes(context.Context, eth.L2BlockRef) (*eth.PayloadAttributes, error) NextAttributes(context.Context, eth.L2BlockRef) (*AttributesWithParent, error)
} }
type Engine interface { type Engine interface {
...@@ -103,6 +104,10 @@ type EngineQueue struct { ...@@ -103,6 +104,10 @@ type EngineQueue struct {
safeHead eth.L2BlockRef safeHead eth.L2BlockRef
unsafeHead eth.L2BlockRef unsafeHead eth.L2BlockRef
// L2 block processed from the batch, but not consolidated to the safe block yet.
// Consolidation will be pending until the entire batch is processed successfully, to guarantee the span batch atomicity.
pendingSafeHead eth.L2BlockRef
// Target L2 block the engine is currently syncing to. // Target L2 block the engine is currently syncing to.
// If the engine p2p sync is enabled, it can be different with unsafeHead. Otherwise, it must be same with unsafeHead. // If the engine p2p sync is enabled, it can be different with unsafeHead. Otherwise, it must be same with unsafeHead.
engineSyncTarget eth.L2BlockRef engineSyncTarget eth.L2BlockRef
...@@ -124,7 +129,7 @@ type EngineQueue struct { ...@@ -124,7 +129,7 @@ type EngineQueue struct {
triedFinalizeAt eth.L1BlockRef triedFinalizeAt eth.L1BlockRef
// The queued-up attributes // The queued-up attributes
safeAttributes *attributesWithParent safeAttributes *AttributesWithParent
unsafePayloads *PayloadsQueue // queue of unsafe payloads, ordered by ascending block number, may have gaps and duplicates unsafePayloads *PayloadsQueue // queue of unsafe payloads, ordered by ascending block number, may have gaps and duplicates
// Tracks which L2 blocks where last derived from which L1 block. At most finalityLookback large. // Tracks which L2 blocks where last derived from which L1 block. At most finalityLookback large.
...@@ -235,6 +240,10 @@ func (eq *EngineQueue) SafeL2Head() eth.L2BlockRef { ...@@ -235,6 +240,10 @@ func (eq *EngineQueue) SafeL2Head() eth.L2BlockRef {
return eq.safeHead return eq.safeHead
} }
func (eq *EngineQueue) PendingSafeL2Head() eth.L2BlockRef {
return eq.pendingSafeHead
}
func (eq *EngineQueue) EngineSyncTarget() eth.L2BlockRef { func (eq *EngineQueue) EngineSyncTarget() eth.L2BlockRef {
return eq.engineSyncTarget return eq.engineSyncTarget
} }
...@@ -275,16 +284,14 @@ func (eq *EngineQueue) Step(ctx context.Context) error { ...@@ -275,16 +284,14 @@ func (eq *EngineQueue) Step(ctx context.Context) error {
if err := eq.tryFinalizePastL2Blocks(ctx); err != nil { if err := eq.tryFinalizePastL2Blocks(ctx); err != nil {
return err return err
} }
if next, err := eq.prev.NextAttributes(ctx, eq.safeHead); err == io.EOF { if next, err := eq.prev.NextAttributes(ctx, eq.pendingSafeHead); err == io.EOF {
outOfData = true outOfData = true
} else if err != nil { } else if err != nil {
return err return err
} else { } else {
eq.safeAttributes = &attributesWithParent{ eq.safeAttributes = next
attributes: next, eq.log.Debug("Adding next safe attributes", "safe_head", eq.safeHead,
parent: eq.safeHead, "pending_safe_head", eq.pendingSafeHead, "next", next)
}
eq.log.Debug("Adding next safe attributes", "safe_head", eq.safeHead, "next", next)
return NotEnoughData return NotEnoughData
} }
...@@ -411,6 +418,7 @@ func (eq *EngineQueue) logSyncProgress(reason string) { ...@@ -411,6 +418,7 @@ func (eq *EngineQueue) logSyncProgress(reason string) {
"reason", reason, "reason", reason,
"l2_finalized", eq.finalized, "l2_finalized", eq.finalized,
"l2_safe", eq.safeHead, "l2_safe", eq.safeHead,
"l2_safe_pending", eq.pendingSafeHead,
"l2_unsafe", eq.unsafeHead, "l2_unsafe", eq.unsafeHead,
"l2_engineSyncTarget", eq.engineSyncTarget, "l2_engineSyncTarget", eq.engineSyncTarget,
"l2_time", eq.unsafeHead.Time, "l2_time", eq.unsafeHead.Time,
...@@ -552,29 +560,30 @@ func (eq *EngineQueue) tryNextSafeAttributes(ctx context.Context) error { ...@@ -552,29 +560,30 @@ func (eq *EngineQueue) tryNextSafeAttributes(ctx context.Context) error {
return nil return nil
} }
// validate the safe attributes before processing them. The engine may have completed processing them through other means. // validate the safe attributes before processing them. The engine may have completed processing them through other means.
if eq.safeHead != eq.safeAttributes.parent { if eq.pendingSafeHead != eq.safeAttributes.parent {
// Previously the attribute's parent was the safe head. If the safe head advances so safe head's parent is the same as the // Previously the attribute's parent was the pending safe head. If the pending safe head advances so pending safe head's parent is the same as the
// attribute's parent then we need to cancel the attributes. // attribute's parent then we need to cancel the attributes.
if eq.safeHead.ParentHash == eq.safeAttributes.parent.Hash { if eq.pendingSafeHead.ParentHash == eq.safeAttributes.parent.Hash {
eq.log.Warn("queued safe attributes are stale, safehead progressed", eq.log.Warn("queued safe attributes are stale, safehead progressed",
"safe_head", eq.safeHead, "safe_head_parent", eq.safeHead.ParentID(), "attributes_parent", eq.safeAttributes.parent) "pending_safe_head", eq.pendingSafeHead, "pending_safe_head_parent", eq.pendingSafeHead.ParentID(),
"attributes_parent", eq.safeAttributes.parent)
eq.safeAttributes = nil eq.safeAttributes = nil
return nil return nil
} }
// If something other than a simple advance occurred, perform a full reset // If something other than a simple advance occurred, perform a full reset
return NewResetError(fmt.Errorf("safe head changed to %s with parent %s, conflicting with queued safe attributes on top of %s", return NewResetError(fmt.Errorf("pending safe head changed to %s with parent %s, conflicting with queued safe attributes on top of %s",
eq.safeHead, eq.safeHead.ParentID(), eq.safeAttributes.parent)) eq.pendingSafeHead, eq.pendingSafeHead.ParentID(), eq.safeAttributes.parent))
} }
if eq.safeHead.Number < eq.unsafeHead.Number { if eq.pendingSafeHead.Number < eq.unsafeHead.Number {
return eq.consolidateNextSafeAttributes(ctx) return eq.consolidateNextSafeAttributes(ctx)
} else if eq.safeHead.Number == eq.unsafeHead.Number { } else if eq.pendingSafeHead.Number == eq.unsafeHead.Number {
return eq.forceNextSafeAttributes(ctx) return eq.forceNextSafeAttributes(ctx)
} else { } else {
// For some reason the unsafe head is behind the safe head. Log it, and correct it. // For some reason the unsafe head is behind the pending safe head. Log it, and correct it.
eq.log.Error("invalid sync state, unsafe head is behind safe head", "unsafe", eq.unsafeHead, "safe", eq.safeHead) eq.log.Error("invalid sync state, unsafe head is behind pending safe head", "unsafe", eq.unsafeHead, "pending_safe", eq.pendingSafeHead)
eq.unsafeHead = eq.safeHead eq.unsafeHead = eq.pendingSafeHead
eq.engineSyncTarget = eq.safeHead eq.engineSyncTarget = eq.pendingSafeHead
eq.metrics.RecordL2Ref("l2_unsafe", eq.unsafeHead) eq.metrics.RecordL2Ref("l2_unsafe", eq.unsafeHead)
eq.metrics.RecordL2Ref("l2_engineSyncTarget", eq.unsafeHead) eq.metrics.RecordL2Ref("l2_engineSyncTarget", eq.unsafeHead)
return nil return nil
...@@ -588,7 +597,7 @@ func (eq *EngineQueue) consolidateNextSafeAttributes(ctx context.Context) error ...@@ -588,7 +597,7 @@ func (eq *EngineQueue) consolidateNextSafeAttributes(ctx context.Context) error
ctx, cancel := context.WithTimeout(ctx, time.Second*10) ctx, cancel := context.WithTimeout(ctx, time.Second*10)
defer cancel() defer cancel()
payload, err := eq.engine.PayloadByNumber(ctx, eq.safeHead.Number+1) payload, err := eq.engine.PayloadByNumber(ctx, eq.pendingSafeHead.Number+1)
if err != nil { if err != nil {
if errors.Is(err, ethereum.NotFound) { if errors.Is(err, ethereum.NotFound) {
// engine may have restarted, or inconsistent safe head. We need to reset // engine may have restarted, or inconsistent safe head. We need to reset
...@@ -596,8 +605,8 @@ func (eq *EngineQueue) consolidateNextSafeAttributes(ctx context.Context) error ...@@ -596,8 +605,8 @@ func (eq *EngineQueue) consolidateNextSafeAttributes(ctx context.Context) error
} }
return NewTemporaryError(fmt.Errorf("failed to get existing unsafe payload to compare against derived attributes from L1: %w", err)) return NewTemporaryError(fmt.Errorf("failed to get existing unsafe payload to compare against derived attributes from L1: %w", err))
} }
if err := AttributesMatchBlock(eq.safeAttributes.attributes, eq.safeHead.Hash, payload, eq.log); err != nil { if err := AttributesMatchBlock(eq.safeAttributes.attributes, eq.pendingSafeHead.Hash, payload, eq.log); err != nil {
eq.log.Warn("L2 reorg: existing unsafe block does not match derived attributes from L1", "err", err, "unsafe", eq.unsafeHead, "safe", eq.safeHead) eq.log.Warn("L2 reorg: existing unsafe block does not match derived attributes from L1", "err", err, "unsafe", eq.unsafeHead, "pending_safe", eq.pendingSafeHead, "safe", eq.safeHead)
// geth cannot wind back a chain without reorging to a new, previously non-canonical, block // geth cannot wind back a chain without reorging to a new, previously non-canonical, block
return eq.forceNextSafeAttributes(ctx) return eq.forceNextSafeAttributes(ctx)
} }
...@@ -605,12 +614,15 @@ func (eq *EngineQueue) consolidateNextSafeAttributes(ctx context.Context) error ...@@ -605,12 +614,15 @@ func (eq *EngineQueue) consolidateNextSafeAttributes(ctx context.Context) error
if err != nil { if err != nil {
return NewResetError(fmt.Errorf("failed to decode L2 block ref from payload: %w", err)) return NewResetError(fmt.Errorf("failed to decode L2 block ref from payload: %w", err))
} }
eq.pendingSafeHead = ref
if eq.safeAttributes.isLastInSpan {
eq.safeHead = ref eq.safeHead = ref
eq.needForkchoiceUpdate = true eq.needForkchoiceUpdate = true
eq.metrics.RecordL2Ref("l2_safe", ref) eq.metrics.RecordL2Ref("l2_safe", ref)
eq.postProcessSafeL2()
}
// unsafe head stays the same, we did not reorg the chain. // unsafe head stays the same, we did not reorg the chain.
eq.safeAttributes = nil eq.safeAttributes = nil
eq.postProcessSafeL2()
eq.logSyncProgress("reconciled with L1") eq.logSyncProgress("reconciled with L1")
return nil return nil
...@@ -622,7 +634,7 @@ func (eq *EngineQueue) forceNextSafeAttributes(ctx context.Context) error { ...@@ -622,7 +634,7 @@ func (eq *EngineQueue) forceNextSafeAttributes(ctx context.Context) error {
return nil return nil
} }
attrs := eq.safeAttributes.attributes attrs := eq.safeAttributes.attributes
errType, err := eq.StartPayload(ctx, eq.safeHead, attrs, true) errType, err := eq.StartPayload(ctx, eq.pendingSafeHead, attrs, true)
if err == nil { if err == nil {
_, errType, err = eq.ConfirmPayload(ctx) _, errType, err = eq.ConfirmPayload(ctx)
} }
...@@ -648,11 +660,13 @@ func (eq *EngineQueue) forceNextSafeAttributes(ctx context.Context) error { ...@@ -648,11 +660,13 @@ func (eq *EngineQueue) forceNextSafeAttributes(ctx context.Context) error {
// block is somehow invalid, there is nothing we can do to recover & we should exit. // block is somehow invalid, there is nothing we can do to recover & we should exit.
// TODO: Can this be triggered by an empty batch with invalid data (like parent hash or gas limit?) // TODO: Can this be triggered by an empty batch with invalid data (like parent hash or gas limit?)
if len(attrs.Transactions) == depositCount { if len(attrs.Transactions) == depositCount {
eq.log.Error("deposit only block was invalid", "parent", eq.safeHead, "err", err) eq.log.Error("deposit only block was invalid", "parent", eq.safeAttributes.parent, "err", err)
return NewCriticalError(fmt.Errorf("failed to process block with only deposit transactions: %w", err)) return NewCriticalError(fmt.Errorf("failed to process block with only deposit transactions: %w", err))
} }
// drop the payload without inserting it // drop the payload without inserting it
eq.safeAttributes = nil eq.safeAttributes = nil
// Revert the pending safe head to the safe head.
eq.pendingSafeHead = eq.safeHead
// suppress the error b/c we want to retry with the next batch from the batch queue // suppress the error b/c we want to retry with the next batch from the batch queue
// If there is no valid batch the node will eventually force a deposit only block. If // If there is no valid batch the node will eventually force a deposit only block. If
// the deposit only block fails, this will return the critical error above. // the deposit only block fails, this will return the critical error above.
...@@ -703,7 +717,9 @@ func (eq *EngineQueue) ConfirmPayload(ctx context.Context) (out *eth.ExecutionPa ...@@ -703,7 +717,9 @@ func (eq *EngineQueue) ConfirmPayload(ctx context.Context) (out *eth.ExecutionPa
SafeBlockHash: eq.safeHead.Hash, SafeBlockHash: eq.safeHead.Hash,
FinalizedBlockHash: eq.finalized.Hash, FinalizedBlockHash: eq.finalized.Hash,
} }
payload, errTyp, err := ConfirmPayload(ctx, eq.log, eq.engine, fc, eq.buildingID, eq.buildingSafe) // Update the safe head if the payload is built with the last attributes in the batch.
updateSafe := eq.buildingSafe && eq.safeAttributes != nil && eq.safeAttributes.isLastInSpan
payload, errTyp, err := ConfirmPayload(ctx, eq.log, eq.engine, fc, eq.buildingID, updateSafe)
if err != nil { if err != nil {
return nil, errTyp, fmt.Errorf("failed to complete building on top of L2 chain %s, id: %s, error (%d): %w", eq.buildingOnto, eq.buildingID, errTyp, err) return nil, errTyp, fmt.Errorf("failed to complete building on top of L2 chain %s, id: %s, error (%d): %w", eq.buildingOnto, eq.buildingID, errTyp, err)
} }
...@@ -718,10 +734,13 @@ func (eq *EngineQueue) ConfirmPayload(ctx context.Context) (out *eth.ExecutionPa ...@@ -718,10 +734,13 @@ func (eq *EngineQueue) ConfirmPayload(ctx context.Context) (out *eth.ExecutionPa
eq.metrics.RecordL2Ref("l2_engineSyncTarget", ref) eq.metrics.RecordL2Ref("l2_engineSyncTarget", ref)
if eq.buildingSafe { if eq.buildingSafe {
eq.pendingSafeHead = ref
if updateSafe {
eq.safeHead = ref eq.safeHead = ref
eq.postProcessSafeL2() eq.postProcessSafeL2()
eq.metrics.RecordL2Ref("l2_safe", ref) eq.metrics.RecordL2Ref("l2_safe", ref)
} }
}
eq.resetBuildingState() eq.resetBuildingState()
return payload, BlockInsertOK, nil return payload, BlockInsertOK, nil
} }
...@@ -798,6 +817,7 @@ func (eq *EngineQueue) Reset(ctx context.Context, _ eth.L1BlockRef, _ eth.System ...@@ -798,6 +817,7 @@ func (eq *EngineQueue) Reset(ctx context.Context, _ eth.L1BlockRef, _ eth.System
eq.unsafeHead = unsafe eq.unsafeHead = unsafe
eq.engineSyncTarget = unsafe eq.engineSyncTarget = unsafe
eq.safeHead = safe eq.safeHead = safe
eq.pendingSafeHead = safe
eq.safeAttributes = nil eq.safeAttributes = nil
eq.finalized = finalized eq.finalized = finalized
eq.resetBuildingState() eq.resetBuildingState()
......
...@@ -25,17 +25,18 @@ import ( ...@@ -25,17 +25,18 @@ import (
type fakeAttributesQueue struct { type fakeAttributesQueue struct {
origin eth.L1BlockRef origin eth.L1BlockRef
attrs *eth.PayloadAttributes attrs *eth.PayloadAttributes
islastInSpan bool
} }
func (f *fakeAttributesQueue) Origin() eth.L1BlockRef { func (f *fakeAttributesQueue) Origin() eth.L1BlockRef {
return f.origin return f.origin
} }
func (f *fakeAttributesQueue) NextAttributes(_ context.Context, _ eth.L2BlockRef) (*eth.PayloadAttributes, error) { func (f *fakeAttributesQueue) NextAttributes(_ context.Context, safeHead eth.L2BlockRef) (*AttributesWithParent, error) {
if f.attrs == nil { if f.attrs == nil {
return nil, io.EOF return nil, io.EOF
} }
return f.attrs, nil return &AttributesWithParent{f.attrs, safeHead, f.islastInSpan}, nil
} }
var _ NextAttributesProvider = (*fakeAttributesQueue)(nil) var _ NextAttributesProvider = (*fakeAttributesQueue)(nil)
...@@ -909,7 +910,7 @@ func TestBlockBuildingRace(t *testing.T) { ...@@ -909,7 +910,7 @@ func TestBlockBuildingRace(t *testing.T) {
GasLimit: &gasLimit, GasLimit: &gasLimit,
} }
prev := &fakeAttributesQueue{origin: refA, attrs: attrs} prev := &fakeAttributesQueue{origin: refA, attrs: attrs, islastInSpan: true}
eq := NewEngineQueue(logger, cfg, eng, metrics, prev, l1F, &sync.Config{}) eq := NewEngineQueue(logger, cfg, eng, metrics, prev, l1F, &sync.Config{})
require.ErrorIs(t, eq.Reset(context.Background(), eth.L1BlockRef{}, eth.SystemConfig{}), io.EOF) require.ErrorIs(t, eq.Reset(context.Background(), eth.L1BlockRef{}, eth.SystemConfig{}), io.EOF)
...@@ -1078,7 +1079,7 @@ func TestResetLoop(t *testing.T) { ...@@ -1078,7 +1079,7 @@ func TestResetLoop(t *testing.T) {
l1F.ExpectL1BlockRefByHash(refA.Hash, refA, nil) l1F.ExpectL1BlockRefByHash(refA.Hash, refA, nil)
l1F.ExpectL1BlockRefByHash(refA.Hash, refA, nil) l1F.ExpectL1BlockRefByHash(refA.Hash, refA, nil)
prev := &fakeAttributesQueue{origin: refA, attrs: attrs} prev := &fakeAttributesQueue{origin: refA, attrs: attrs, islastInSpan: true}
eq := NewEngineQueue(logger, cfg, eng, metrics.NoopMetrics, prev, l1F, &sync.Config{}) eq := NewEngineQueue(logger, cfg, eng, metrics.NoopMetrics, prev, l1F, &sync.Config{})
eq.unsafeHead = refA2 eq.unsafeHead = refA2
......
...@@ -51,6 +51,7 @@ type EngineQueueStage interface { ...@@ -51,6 +51,7 @@ type EngineQueueStage interface {
Finalized() eth.L2BlockRef Finalized() eth.L2BlockRef
UnsafeL2Head() eth.L2BlockRef UnsafeL2Head() eth.L2BlockRef
SafeL2Head() eth.L2BlockRef SafeL2Head() eth.L2BlockRef
PendingSafeL2Head() eth.L2BlockRef
EngineSyncTarget() eth.L2BlockRef EngineSyncTarget() eth.L2BlockRef
Origin() eth.L1BlockRef Origin() eth.L1BlockRef
SystemConfig() eth.SystemConfig SystemConfig() eth.SystemConfig
...@@ -148,6 +149,10 @@ func (dp *DerivationPipeline) SafeL2Head() eth.L2BlockRef { ...@@ -148,6 +149,10 @@ func (dp *DerivationPipeline) SafeL2Head() eth.L2BlockRef {
return dp.eng.SafeL2Head() return dp.eng.SafeL2Head()
} }
func (dp *DerivationPipeline) PendingSafeL2Head() eth.L2BlockRef {
return dp.eng.PendingSafeL2Head()
}
// UnsafeL2Head returns the head of the L2 chain that we are deriving for, this may be past what we derived from L1 // UnsafeL2Head returns the head of the L2 chain that we are deriving for, this may be past what we derived from L1
func (dp *DerivationPipeline) UnsafeL2Head() eth.L2BlockRef { func (dp *DerivationPipeline) UnsafeL2Head() eth.L2BlockRef {
return dp.eng.UnsafeL2Head() return dp.eng.UnsafeL2Head()
......
...@@ -60,6 +60,7 @@ type DerivationPipeline interface { ...@@ -60,6 +60,7 @@ type DerivationPipeline interface {
Finalized() eth.L2BlockRef Finalized() eth.L2BlockRef
SafeL2Head() eth.L2BlockRef SafeL2Head() eth.L2BlockRef
UnsafeL2Head() eth.L2BlockRef UnsafeL2Head() eth.L2BlockRef
PendingSafeL2Head() eth.L2BlockRef
Origin() eth.L1BlockRef Origin() eth.L1BlockRef
EngineReady() bool EngineReady() bool
EngineSyncTarget() eth.L2BlockRef EngineSyncTarget() eth.L2BlockRef
......
...@@ -481,6 +481,7 @@ func (s *Driver) syncStatus() *eth.SyncStatus { ...@@ -481,6 +481,7 @@ func (s *Driver) syncStatus() *eth.SyncStatus {
UnsafeL2: s.derivation.UnsafeL2Head(), UnsafeL2: s.derivation.UnsafeL2Head(),
SafeL2: s.derivation.SafeL2Head(), SafeL2: s.derivation.SafeL2Head(),
FinalizedL2: s.derivation.Finalized(), FinalizedL2: s.derivation.Finalized(),
PendingSafeL2: s.derivation.PendingSafeL2Head(),
UnsafeL2SyncTarget: s.derivation.UnsafeL2SyncTarget(), UnsafeL2SyncTarget: s.derivation.UnsafeL2SyncTarget(),
EngineSyncTarget: s.derivation.EngineSyncTarget(), EngineSyncTarget: s.derivation.EngineSyncTarget(),
} }
......
...@@ -25,13 +25,13 @@ type Derivation interface { ...@@ -25,13 +25,13 @@ type Derivation interface {
type L2Source interface { type L2Source interface {
derive.Engine derive.Engine
L2OutputRoot() (eth.Bytes32, error) L2OutputRoot(uint64) (eth.Bytes32, error)
} }
type Driver struct { type Driver struct {
logger log.Logger logger log.Logger
pipeline Derivation pipeline Derivation
l2OutputRoot func() (eth.Bytes32, error) l2OutputRoot func(uint64) (eth.Bytes32, error)
targetBlockNum uint64 targetBlockNum uint64
} }
...@@ -77,8 +77,8 @@ func (d *Driver) SafeHead() eth.L2BlockRef { ...@@ -77,8 +77,8 @@ func (d *Driver) SafeHead() eth.L2BlockRef {
return d.pipeline.SafeL2Head() return d.pipeline.SafeL2Head()
} }
func (d *Driver) ValidateClaim(claimedOutputRoot eth.Bytes32) error { func (d *Driver) ValidateClaim(l2ClaimBlockNum uint64, claimedOutputRoot eth.Bytes32) error {
outputRoot, err := d.l2OutputRoot() outputRoot, err := d.l2OutputRoot(l2ClaimBlockNum)
if err != nil { if err != nil {
return fmt.Errorf("calculate L2 output root: %w", err) return fmt.Errorf("calculate L2 output root: %w", err)
} }
......
...@@ -73,29 +73,29 @@ func TestValidateClaim(t *testing.T) { ...@@ -73,29 +73,29 @@ func TestValidateClaim(t *testing.T) {
t.Run("Valid", func(t *testing.T) { t.Run("Valid", func(t *testing.T) {
driver := createDriver(t, io.EOF) driver := createDriver(t, io.EOF)
expected := eth.Bytes32{0x11} expected := eth.Bytes32{0x11}
driver.l2OutputRoot = func() (eth.Bytes32, error) { driver.l2OutputRoot = func(_ uint64) (eth.Bytes32, error) {
return expected, nil return expected, nil
} }
err := driver.ValidateClaim(expected) err := driver.ValidateClaim(uint64(0), expected)
require.NoError(t, err) require.NoError(t, err)
}) })
t.Run("Invalid", func(t *testing.T) { t.Run("Invalid", func(t *testing.T) {
driver := createDriver(t, io.EOF) driver := createDriver(t, io.EOF)
driver.l2OutputRoot = func() (eth.Bytes32, error) { driver.l2OutputRoot = func(_ uint64) (eth.Bytes32, error) {
return eth.Bytes32{0x22}, nil return eth.Bytes32{0x22}, nil
} }
err := driver.ValidateClaim(eth.Bytes32{0x11}) err := driver.ValidateClaim(uint64(0), eth.Bytes32{0x11})
require.ErrorIs(t, err, ErrClaimNotValid) require.ErrorIs(t, err, ErrClaimNotValid)
}) })
t.Run("Error", func(t *testing.T) { t.Run("Error", func(t *testing.T) {
driver := createDriver(t, io.EOF) driver := createDriver(t, io.EOF)
expectedErr := errors.New("boom") expectedErr := errors.New("boom")
driver.l2OutputRoot = func() (eth.Bytes32, error) { driver.l2OutputRoot = func(_ uint64) (eth.Bytes32, error) {
return eth.Bytes32{}, expectedErr return eth.Bytes32{}, expectedErr
} }
err := driver.ValidateClaim(eth.Bytes32{0x11}) err := driver.ValidateClaim(uint64(0), eth.Bytes32{0x11})
require.ErrorIs(t, err, expectedErr) require.ErrorIs(t, err, expectedErr)
}) })
} }
......
...@@ -34,8 +34,11 @@ func NewOracleEngine(rollupCfg *rollup.Config, logger log.Logger, backend engine ...@@ -34,8 +34,11 @@ func NewOracleEngine(rollupCfg *rollup.Config, logger log.Logger, backend engine
} }
} }
func (o *OracleEngine) L2OutputRoot() (eth.Bytes32, error) { func (o *OracleEngine) L2OutputRoot(l2ClaimBlockNum uint64) (eth.Bytes32, error) {
outBlock := o.backend.CurrentHeader() outBlock := o.backend.GetHeaderByNumber(l2ClaimBlockNum)
if outBlock == nil {
return eth.Bytes32{}, fmt.Errorf("failed to get L2 block at %d", l2ClaimBlockNum)
}
stateDB, err := o.backend.StateAt(outBlock.Root) stateDB, err := o.backend.StateAt(outBlock.Root)
if err != nil { if err != nil {
return eth.Bytes32{}, fmt.Errorf("failed to open L2 state db at block %s: %w", outBlock.Hash(), err) return eth.Bytes32{}, fmt.Errorf("failed to open L2 state db at block %s: %w", outBlock.Hash(), err)
......
...@@ -79,7 +79,7 @@ func runDerivation(logger log.Logger, cfg *rollup.Config, l2Cfg *params.ChainCon ...@@ -79,7 +79,7 @@ func runDerivation(logger log.Logger, cfg *rollup.Config, l2Cfg *params.ChainCon
return err return err
} }
} }
return d.ValidateClaim(eth.Bytes32(l2Claim)) return d.ValidateClaim(l2ClaimBlockNum, eth.Bytes32(l2Claim))
} }
func CreateHinterChannel() oppio.FileChannel { func CreateHinterChannel() oppio.FileChannel {
......
...@@ -4,7 +4,6 @@ import ( ...@@ -4,7 +4,6 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"os"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
...@@ -30,21 +29,22 @@ type Lifecycle interface { ...@@ -30,21 +29,22 @@ type Lifecycle interface {
// a shutdown when the Stop context is not expired. // a shutdown when the Stop context is not expired.
type LifecycleAction func(ctx *cli.Context, close context.CancelCauseFunc) (Lifecycle, error) type LifecycleAction func(ctx *cli.Context, close context.CancelCauseFunc) (Lifecycle, error)
var interruptErr = errors.New("interrupt signal")
// LifecycleCmd turns a LifecycleAction into an CLI action, // LifecycleCmd turns a LifecycleAction into an CLI action,
// by instrumenting it with CLI context and signal based termination. // by instrumenting it with CLI context and signal based termination.
// The signals are caught with the opio.BlockFn attached to the context, if any.
// If no block function is provided, it adds default interrupt handling.
// The app may continue to run post-processing until fully shutting down. // The app may continue to run post-processing until fully shutting down.
// The user can force an early shut-down during post-processing by sending a second interruption signal. // The user can force an early shut-down during post-processing by sending a second interruption signal.
func LifecycleCmd(fn LifecycleAction) cli.ActionFunc { func LifecycleCmd(fn LifecycleAction) cli.ActionFunc {
return lifecycleCmd(fn, opio.BlockOnInterruptsContext)
}
type waitSignalFn func(ctx context.Context, signals ...os.Signal)
var interruptErr = errors.New("interrupt signal")
func lifecycleCmd(fn LifecycleAction, blockOnInterrupt waitSignalFn) cli.ActionFunc {
return func(ctx *cli.Context) error { return func(ctx *cli.Context) error {
hostCtx := ctx.Context hostCtx := ctx.Context
blockOnInterrupt := opio.BlockerFromContext(hostCtx)
if blockOnInterrupt == nil { // add default interrupt blocker to context if none is set.
hostCtx = opio.WithInterruptBlocker(hostCtx)
blockOnInterrupt = opio.BlockerFromContext(hostCtx)
}
appCtx, appCancel := context.WithCancelCause(hostCtx) appCtx, appCancel := context.WithCancelCause(hostCtx)
ctx.Context = appCtx ctx.Context = appCtx
......
...@@ -3,12 +3,13 @@ package cliapp ...@@ -3,12 +3,13 @@ package cliapp
import ( import (
"context" "context"
"errors" "errors"
"os"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-service/opio"
) )
type fakeLifecycle struct { type fakeLifecycle struct {
...@@ -77,19 +78,19 @@ func TestLifecycleCmd(t *testing.T) { ...@@ -77,19 +78,19 @@ func TestLifecycleCmd(t *testing.T) {
return app, nil return app, nil
} }
// puppeteer a system signal waiter with a test signal channel
fakeSignalWaiter := func(ctx context.Context, signals ...os.Signal) {
select {
case <-ctx.Done():
case <-signalCh:
}
}
// turn our mock app and system signal into a lifecycle-managed command // turn our mock app and system signal into a lifecycle-managed command
actionFn := lifecycleCmd(mockAppFn, fakeSignalWaiter) actionFn := LifecycleCmd(mockAppFn)
// try to shut the test down after being locked more than a minute // try to shut the test down after being locked more than a minute
ctx, cancel := context.WithTimeout(context.Background(), time.Minute) ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
// puppeteer system signal interrupts by hooking up the test signal channel as "blocker" for the app to use.
ctx = opio.WithBlocker(ctx, func(ctx context.Context) {
select {
case <-ctx.Done():
case <-signalCh:
}
})
t.Cleanup(cancel) t.Cleanup(cancel)
// create a fake CLI context to run our command with // create a fake CLI context to run our command with
......
...@@ -32,6 +32,8 @@ type SyncStatus struct { ...@@ -32,6 +32,8 @@ type SyncStatus struct {
// FinalizedL2 points to the L2 block that was derived fully from // FinalizedL2 points to the L2 block that was derived fully from
// finalized L1 information, thus irreversible. // finalized L1 information, thus irreversible.
FinalizedL2 L2BlockRef `json:"finalized_l2"` FinalizedL2 L2BlockRef `json:"finalized_l2"`
// PendingSafeL2 points to the L2 block processed from the batch, but not consolidated to the safe block yet.
PendingSafeL2 L2BlockRef `json:"pending_safe_l2"`
// UnsafeL2SyncTarget points to the first unprocessed unsafe L2 block. // UnsafeL2SyncTarget points to the first unprocessed unsafe L2 block.
// It may be zeroed if there is no targeted block. // It may be zeroed if there is no targeted block.
UnsafeL2SyncTarget L2BlockRef `json:"queued_unsafe_l2"` UnsafeL2SyncTarget L2BlockRef `json:"queued_unsafe_l2"`
......
...@@ -41,3 +41,74 @@ func BlockOnInterruptsContext(ctx context.Context, signals ...os.Signal) { ...@@ -41,3 +41,74 @@ func BlockOnInterruptsContext(ctx context.Context, signals ...os.Signal) {
signal.Stop(interruptChannel) signal.Stop(interruptChannel)
} }
} }
type interruptContextKeyType struct{}
var blockerContextKey = interruptContextKeyType{}
type interruptCatcher struct {
incoming chan os.Signal
}
// Block blocks until either an interrupt signal is received, or the context is cancelled.
// No error is returned on interrupt.
func (c *interruptCatcher) Block(ctx context.Context) {
select {
case <-c.incoming:
case <-ctx.Done():
}
}
// WithInterruptBlocker attaches an interrupt handler to the context,
// which continues to receive signals after every block.
// This helps functions block on individual consecutive interrupts.
func WithInterruptBlocker(ctx context.Context) context.Context {
if ctx.Value(blockerContextKey) != nil { // already has an interrupt handler
return ctx
}
catcher := &interruptCatcher{
incoming: make(chan os.Signal, 10),
}
signal.Notify(catcher.incoming, DefaultInterruptSignals...)
return context.WithValue(ctx, blockerContextKey, BlockFn(catcher.Block))
}
// WithBlocker overrides the interrupt blocker value,
// e.g. to insert a block-function for testing CLI shutdown without actual process signals.
func WithBlocker(ctx context.Context, fn BlockFn) context.Context {
return context.WithValue(ctx, blockerContextKey, fn)
}
// BlockFn simply blocks until the implementation of the blocker interrupts it, or till the given context is cancelled.
type BlockFn func(ctx context.Context)
// BlockerFromContext returns a BlockFn that blocks on interrupts when called.
func BlockerFromContext(ctx context.Context) BlockFn {
v := ctx.Value(blockerContextKey)
if v == nil {
return nil
}
return v.(BlockFn)
}
// CancelOnInterrupt cancels the given context on interrupt.
// If a BlockFn is attached to the context, this is used as interrupt-blocking.
// If not, then the context blocks on a manually handled interrupt signal.
func CancelOnInterrupt(ctx context.Context) context.Context {
inner, cancel := context.WithCancel(ctx)
blockOnInterrupt := BlockerFromContext(ctx)
if blockOnInterrupt == nil {
blockOnInterrupt = func(ctx context.Context) {
BlockOnInterruptsContext(ctx) // default signals
}
}
go func() {
blockOnInterrupt(ctx)
cancel()
}()
return inner
}
...@@ -110,6 +110,10 @@ func (c *CallResult) GetHash(i int) common.Hash { ...@@ -110,6 +110,10 @@ func (c *CallResult) GetHash(i int) common.Hash {
return *abi.ConvertType(c.out[i], new([32]byte)).(*[32]byte) return *abi.ConvertType(c.out[i], new([32]byte)).(*[32]byte)
} }
func (c *CallResult) GetAddress(i int) common.Address {
return *abi.ConvertType(c.out[i], new([20]byte)).(*[20]byte)
}
func (c *CallResult) GetBigInt(i int) *big.Int { func (c *CallResult) GetBigInt(i int) *big.Int {
return *abi.ConvertType(c.out[i], new(*big.Int)).(**big.Int) return *abi.ConvertType(c.out[i], new(*big.Int)).(**big.Int)
} }
...@@ -118,6 +118,13 @@ func TestCallResult_GetValues(t *testing.T) { ...@@ -118,6 +118,13 @@ func TestCallResult_GetValues(t *testing.T) {
}, },
expected: true, expected: true,
}, },
{
name: "GetAddress",
getter: func(result *CallResult, i int) interface{} {
return result.GetAddress(i)
},
expected: ([20]byte)(common.Address{0xaa, 0xbb, 0xcc}),
},
{ {
name: "GetHash", name: "GetHash",
getter: func(result *CallResult, i int) interface{} { getter: func(result *CallResult, i int) interface{} {
......
...@@ -5,10 +5,13 @@ import ( ...@@ -5,10 +5,13 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
var DefaultBatchSize = 100
type EthRpc interface { type EthRpc interface {
CallContext(ctx context.Context, out interface{}, method string, args ...interface{}) error CallContext(ctx context.Context, out interface{}, method string, args ...interface{}) error
BatchCallContext(ctx context.Context, b []rpc.BatchElem) error BatchCallContext(ctx context.Context, b []rpc.BatchElem) error
...@@ -26,15 +29,15 @@ func NewMultiCaller(rpc EthRpc, batchSize int) *MultiCaller { ...@@ -26,15 +29,15 @@ func NewMultiCaller(rpc EthRpc, batchSize int) *MultiCaller {
} }
} }
func (m *MultiCaller) SingleCallLatest(ctx context.Context, call *ContractCall) (*CallResult, error) { func (m *MultiCaller) SingleCall(ctx context.Context, block Block, call *ContractCall) (*CallResult, error) {
results, err := m.CallLatest(ctx, call) results, err := m.Call(ctx, block, call)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return results[0], nil return results[0], nil
} }
func (m *MultiCaller) CallLatest(ctx context.Context, calls ...*ContractCall) ([]*CallResult, error) { func (m *MultiCaller) Call(ctx context.Context, block Block, calls ...*ContractCall) ([]*CallResult, error) {
keys := make([]interface{}, len(calls)) keys := make([]interface{}, len(calls))
for i := 0; i < len(calls); i++ { for i := 0; i < len(calls); i++ {
args, err := calls[i].ToCallArgs() args, err := calls[i].ToCallArgs()
...@@ -49,7 +52,7 @@ func (m *MultiCaller) CallLatest(ctx context.Context, calls ...*ContractCall) ([ ...@@ -49,7 +52,7 @@ func (m *MultiCaller) CallLatest(ctx context.Context, calls ...*ContractCall) ([
out := new(hexutil.Bytes) out := new(hexutil.Bytes)
return out, rpc.BatchElem{ return out, rpc.BatchElem{
Method: "eth_call", Method: "eth_call",
Args: []interface{}{args, "latest"}, Args: []interface{}{args, block.value},
Result: &out, Result: &out,
} }
}, },
...@@ -79,3 +82,30 @@ func (m *MultiCaller) CallLatest(ctx context.Context, calls ...*ContractCall) ([ ...@@ -79,3 +82,30 @@ func (m *MultiCaller) CallLatest(ctx context.Context, calls ...*ContractCall) ([
} }
return callResults, nil return callResults, nil
} }
// Block represents the block ref value in RPC calls.
// It can be either a label (e.g. latest), a block number or block hash.
type Block struct {
value any
}
func (b Block) ArgValue() any {
return b.value
}
var (
BlockPending = Block{"pending"}
BlockLatest = Block{"latest"}
BlockSafe = Block{"safe"}
BlockFinalized = Block{"finalized"}
)
// BlockByNumber references a canonical block by number.
func BlockByNumber(blockNum uint64) Block {
return Block{rpc.BlockNumber(blockNum)}
}
// BlockByHash references a block by hash. Canonical or non-canonical blocks may be referenced.
func BlockByHash(hash common.Hash) Block {
return Block{rpc.BlockNumberOrHashWithHash(hash, false)}
}
...@@ -7,22 +7,25 @@ import ( ...@@ -7,22 +7,25 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
) )
type expectedCall struct { type expectedCall struct {
block batching.Block
args []interface{} args []interface{}
packedArgs []byte packedArgs []byte
outputs []interface{} outputs []interface{}
} }
func (e *expectedCall) String() string { func (e *expectedCall) String() string {
return fmt.Sprintf("{args: %v, outputs: %v}", e.args, e.outputs) return fmt.Sprintf("{block: %v, args: %v, outputs: %v}", e.block, e.args, e.outputs)
} }
type AbiBasedRpc struct { type AbiBasedRpc struct {
...@@ -42,7 +45,7 @@ func NewAbiBasedRpc(t *testing.T, contractAbi *abi.ABI, addr common.Address) *Ab ...@@ -42,7 +45,7 @@ func NewAbiBasedRpc(t *testing.T, contractAbi *abi.ABI, addr common.Address) *Ab
} }
} }
func (l *AbiBasedRpc) SetResponse(method string, expected []interface{}, output []interface{}) { func (l *AbiBasedRpc) SetResponse(method string, block batching.Block, expected []interface{}, output []interface{}) {
if expected == nil { if expected == nil {
expected = []interface{}{} expected = []interface{}{}
} }
...@@ -54,6 +57,7 @@ func (l *AbiBasedRpc) SetResponse(method string, expected []interface{}, output ...@@ -54,6 +57,7 @@ func (l *AbiBasedRpc) SetResponse(method string, expected []interface{}, output
packedArgs, err := abiMethod.Inputs.Pack(expected...) packedArgs, err := abiMethod.Inputs.Pack(expected...)
require.NoErrorf(l.t, err, "Invalid expected arguments for method %v: %v", method, expected) require.NoErrorf(l.t, err, "Invalid expected arguments for method %v: %v", method, expected)
l.expectedCalls[method] = append(l.expectedCalls[method], &expectedCall{ l.expectedCalls[method] = append(l.expectedCalls[method], &expectedCall{
block: block,
args: expected, args: expected,
packedArgs: packedArgs, packedArgs: packedArgs,
outputs: output, outputs: output,
...@@ -72,7 +76,7 @@ func (l *AbiBasedRpc) BatchCallContext(ctx context.Context, b []rpc.BatchElem) e ...@@ -72,7 +76,7 @@ func (l *AbiBasedRpc) BatchCallContext(ctx context.Context, b []rpc.BatchElem) e
func (l *AbiBasedRpc) CallContext(_ context.Context, out interface{}, method string, args ...interface{}) error { func (l *AbiBasedRpc) CallContext(_ context.Context, out interface{}, method string, args ...interface{}) error {
require.Equal(l.t, "eth_call", method) require.Equal(l.t, "eth_call", method)
require.Len(l.t, args, 2) require.Len(l.t, args, 2)
require.Equal(l.t, "latest", args[1]) actualBlockRef := args[1]
callOpts, ok := args[0].(map[string]any) callOpts, ok := args[0].(map[string]any)
require.True(l.t, ok) require.True(l.t, ok)
require.Equal(l.t, &l.addr, callOpts["to"]) require.Equal(l.t, &l.addr, callOpts["to"])
...@@ -90,12 +94,12 @@ func (l *AbiBasedRpc) CallContext(_ context.Context, out interface{}, method str ...@@ -90,12 +94,12 @@ func (l *AbiBasedRpc) CallContext(_ context.Context, out interface{}, method str
require.Truef(l.t, ok, "Unexpected call to %v", abiMethod.Name) require.Truef(l.t, ok, "Unexpected call to %v", abiMethod.Name)
var call *expectedCall var call *expectedCall
for _, candidate := range expectedCalls { for _, candidate := range expectedCalls {
if slices.Equal(candidate.packedArgs, argData) { if slices.Equal(candidate.packedArgs, argData) && assert.ObjectsAreEqualValues(candidate.block.ArgValue(), actualBlockRef) {
call = candidate call = candidate
break break
} }
} }
require.NotNilf(l.t, call, "No expected calls to %v with arguments: %v\nExpected calls: %v", abiMethod.Name, args, expectedCalls) require.NotNilf(l.t, call, "No expected calls to %v at block %v with arguments: %v\nExpected calls: %v", abiMethod.Name, actualBlockRef, args, expectedCalls)
output, err := abiMethod.Outputs.Pack(call.outputs...) output, err := abiMethod.Outputs.Pack(call.outputs...)
require.NoErrorf(l.t, err, "Invalid outputs for method %v: %v", abiMethod.Name, call.outputs) require.NoErrorf(l.t, err, "Invalid outputs for method %v: %v", abiMethod.Name, call.outputs)
......
package tasks
import (
"fmt"
"runtime/debug"
"golang.org/x/sync/errgroup"
)
// Group is a tasks group, which can at any point be awaited to complete.
// Tasks in the group are run in separate go routines.
// If a task panics, the panic is recovered with HandleCrit.
type Group struct {
errGroup errgroup.Group
HandleCrit func(err error)
}
func (t *Group) Go(fn func() error) {
t.errGroup.Go(func() error {
defer func() {
if err := recover(); err != nil {
debug.PrintStack()
t.HandleCrit(fmt.Errorf("panic: %v", err))
}
}()
return fn()
})
}
func (t *Group) Wait() error {
return t.errGroup.Wait()
}
...@@ -324,6 +324,7 @@ func RandomOutputResponse(rng *rand.Rand) *eth.OutputResponse { ...@@ -324,6 +324,7 @@ func RandomOutputResponse(rng *rand.Rand) *eth.OutputResponse {
UnsafeL2: RandomL2BlockRef(rng), UnsafeL2: RandomL2BlockRef(rng),
SafeL2: RandomL2BlockRef(rng), SafeL2: RandomL2BlockRef(rng),
FinalizedL2: RandomL2BlockRef(rng), FinalizedL2: RandomL2BlockRef(rng),
PendingSafeL2: RandomL2BlockRef(rng),
EngineSyncTarget: RandomL2BlockRef(rng), EngineSyncTarget: RandomL2BlockRef(rng),
}, },
} }
......
...@@ -59,7 +59,7 @@ ...@@ -59,7 +59,7 @@
"@ethersproject/abstract-provider": "^5.7.0", "@ethersproject/abstract-provider": "^5.7.0",
"@nomiclabs/hardhat-ethers": "^2.2.3", "@nomiclabs/hardhat-ethers": "^2.2.3",
"@nomiclabs/hardhat-waffle": "^2.0.6", "@nomiclabs/hardhat-waffle": "^2.0.6",
"hardhat": "^2.18.3", "hardhat": "^2.19.0",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"tsx": "^3.14.0" "tsx": "^3.14.0"
} }
......
...@@ -308,8 +308,8 @@ LivenessGuard_CheckTx_TestFails:test_checkTransaction_callerIsNotSafe_revert() ( ...@@ -308,8 +308,8 @@ LivenessGuard_CheckTx_TestFails:test_checkTransaction_callerIsNotSafe_revert() (
LivenessGuard_Constructor_Test:test_constructor_works() (gas: 1198965) LivenessGuard_Constructor_Test:test_constructor_works() (gas: 1198965)
LivenessGuard_Getters_Test:test_getters_works() (gas: 10662) LivenessGuard_Getters_Test:test_getters_works() (gas: 10662)
LivenessGuard_OwnerManagement_Test:test_addOwner_succeeds() (gas: 274366) LivenessGuard_OwnerManagement_Test:test_addOwner_succeeds() (gas: 274366)
LivenessGuard_OwnerManagement_Test:test_removeOwner_succeeds() (gas: 246263) LivenessGuard_OwnerManagement_Test:test_removeOwner_succeeds() (gas: 243684)
LivenessGuard_OwnerManagement_Test:test_swapOwner_succeeds() (gas: 284880) LivenessGuard_OwnerManagement_Test:test_swapOwner_succeeds() (gas: 282299)
LivenessGuard_ShowLiveness_Test:test_showLiveness_succeeds() (gas: 28831) LivenessGuard_ShowLiveness_Test:test_showLiveness_succeeds() (gas: 28831)
LivenessGuard_ShowLiveness_TestFail:test_showLiveness_callIsNotSafeOwner_reverts() (gas: 18770) LivenessGuard_ShowLiveness_TestFail:test_showLiveness_callIsNotSafeOwner_reverts() (gas: 18770)
LivenessModule_CanRemove_Test:test_canRemove_works() (gas: 33026) LivenessModule_CanRemove_Test:test_canRemove_works() (gas: 33026)
...@@ -318,92 +318,92 @@ LivenessModule_Constructor_TestFail:test_constructor_minOwnersGreaterThanOwners_ ...@@ -318,92 +318,92 @@ LivenessModule_Constructor_TestFail:test_constructor_minOwnersGreaterThanOwners_
LivenessModule_Constructor_TestFail:test_constructor_wrongThreshold_reverts() (gas: 92925) LivenessModule_Constructor_TestFail:test_constructor_wrongThreshold_reverts() (gas: 92925)
LivenessModule_Get75PercentThreshold_Test:test_get75PercentThreshold_Works() (gas: 26339) LivenessModule_Get75PercentThreshold_Test:test_get75PercentThreshold_Works() (gas: 26339)
LivenessModule_Getters_Test:test_getters_works() (gas: 14853) LivenessModule_Getters_Test:test_getters_works() (gas: 14853)
LivenessModule_RemoveOwners_Test:test_removeOwners_allOwners_succeeds() (gas: 1326177) LivenessModule_RemoveOwners_Test:test_removeOwners_allOwners_succeeds() (gas: 1316393)
LivenessModule_RemoveOwners_Test:test_removeOwners_oneOwner_succeeds() (gas: 133975) LivenessModule_RemoveOwners_Test:test_removeOwners_oneOwner_succeeds() (gas: 130750)
LivenessModule_RemoveOwners_TestFail:test_removeOwners_belowEmptiedButNotShutDown_reverts() (gas: 1278643) LivenessModule_RemoveOwners_TestFail:test_removeOwners_belowEmptiedButNotShutDown_reverts() (gas: 1269598)
LivenessModule_RemoveOwners_TestFail:test_removeOwners_belowMinButNotEmptied_reverts() (gas: 1281685) LivenessModule_RemoveOwners_TestFail:test_removeOwners_belowMinButNotEmptied_reverts() (gas: 1273409)
LivenessModule_RemoveOwners_TestFail:test_removeOwners_differentArrayLengths_reverts() (gas: 10502) LivenessModule_RemoveOwners_TestFail:test_removeOwners_differentArrayLengths_reverts() (gas: 10502)
LivenessModule_RemoveOwners_TestFail:test_removeOwners_guardChanged_reverts() (gas: 2839358) LivenessModule_RemoveOwners_TestFail:test_removeOwners_guardChanged_reverts() (gas: 2836129)
LivenessModule_RemoveOwners_TestFail:test_removeOwners_invalidThreshold_reverts() (gas: 69358) LivenessModule_RemoveOwners_TestFail:test_removeOwners_invalidThreshold_reverts() (gas: 69358)
LivenessModule_RemoveOwners_TestFail:test_removeOwners_ownerHasShownLivenessRecently_reverts() (gas: 80971) LivenessModule_RemoveOwners_TestFail:test_removeOwners_ownerHasShownLivenessRecently_reverts() (gas: 77749)
LivenessModule_RemoveOwners_TestFail:test_removeOwners_ownerHasSignedRecently_reverts() (gas: 617629) LivenessModule_RemoveOwners_TestFail:test_removeOwners_ownerHasSignedRecently_reverts() (gas: 615047)
LivenessModule_RemoveOwners_TestFail:test_removeOwners_swapToFallbackOwner_reverts() (gas: 1288036) LivenessModule_RemoveOwners_TestFail:test_removeOwners_swapToFallbackOwner_reverts() (gas: 1278252)
LivenessModule_RemoveOwners_TestFail:test_removeOwners_wrongPreviousOwner_reverts() (gas: 73954) LivenessModule_RemoveOwners_TestFail:test_removeOwners_wrongPreviousOwner_reverts() (gas: 73954)
MIPS_Test:test_add_succeeds() (gas: 122932) MIPS_Test:test_add_succeeds() (gas: 122866)
MIPS_Test:test_addiSign_succeeds() (gas: 122923) MIPS_Test:test_addiSign_succeeds() (gas: 122857)
MIPS_Test:test_addi_succeeds() (gas: 123120) MIPS_Test:test_addi_succeeds() (gas: 123054)
MIPS_Test:test_addu_succeeds() (gas: 122974) MIPS_Test:test_addu_succeeds() (gas: 122908)
MIPS_Test:test_addui_succeeds() (gas: 123182) MIPS_Test:test_addui_succeeds() (gas: 123116)
MIPS_Test:test_and_succeeds() (gas: 122993) MIPS_Test:test_and_succeeds() (gas: 122927)
MIPS_Test:test_andi_succeeds() (gas: 122926) MIPS_Test:test_andi_succeeds() (gas: 122860)
MIPS_Test:test_beq_succeeds() (gas: 203359) MIPS_Test:test_beq_succeeds() (gas: 203293)
MIPS_Test:test_bgez_succeeds() (gas: 122219) MIPS_Test:test_bgez_succeeds() (gas: 122153)
MIPS_Test:test_bgtz_succeeds() (gas: 122140) MIPS_Test:test_bgtz_succeeds() (gas: 122074)
MIPS_Test:test_blez_succeeds() (gas: 122096) MIPS_Test:test_blez_succeeds() (gas: 122030)
MIPS_Test:test_bltz_succeeds() (gas: 122239) MIPS_Test:test_bltz_succeeds() (gas: 122173)
MIPS_Test:test_bne_succeeds() (gas: 122305) MIPS_Test:test_bne_succeeds() (gas: 122239)
MIPS_Test:test_branch_inDelaySlot_fails() (gas: 86514) MIPS_Test:test_branch_inDelaySlot_fails() (gas: 86448)
MIPS_Test:test_brk_succeeds() (gas: 122609) MIPS_Test:test_brk_succeeds() (gas: 122543)
MIPS_Test:test_clo_succeeds() (gas: 122661) MIPS_Test:test_clo_succeeds() (gas: 122595)
MIPS_Test:test_clone_succeeds() (gas: 122562) MIPS_Test:test_clone_succeeds() (gas: 122496)
MIPS_Test:test_clz_succeeds() (gas: 123132) MIPS_Test:test_clz_succeeds() (gas: 123066)
MIPS_Test:test_div_succeeds() (gas: 123111) MIPS_Test:test_div_succeeds() (gas: 123045)
MIPS_Test:test_divu_succeeds() (gas: 123096) MIPS_Test:test_divu_succeeds() (gas: 123030)
MIPS_Test:test_exit_succeeds() (gas: 122611) MIPS_Test:test_exit_succeeds() (gas: 122545)
MIPS_Test:test_fcntl_succeeds() (gas: 204841) MIPS_Test:test_fcntl_succeeds() (gas: 204775)
MIPS_Test:test_illegal_instruction_fails() (gas: 91977) MIPS_Test:test_illegal_instruction_fails() (gas: 91911)
MIPS_Test:test_invalid_root_fails() (gas: 436151) MIPS_Test:test_invalid_root_fails() (gas: 436085)
MIPS_Test:test_jal_nonzeroRegion_succeeds() (gas: 121249) MIPS_Test:test_jal_nonzeroRegion_succeeds() (gas: 121183)
MIPS_Test:test_jal_succeeds() (gas: 121238) MIPS_Test:test_jal_succeeds() (gas: 121172)
MIPS_Test:test_jalr_succeeds() (gas: 122357) MIPS_Test:test_jalr_succeeds() (gas: 122291)
MIPS_Test:test_jr_succeeds() (gas: 122051) MIPS_Test:test_jr_succeeds() (gas: 121985)
MIPS_Test:test_jump_inDelaySlot_fails() (gas: 85882) MIPS_Test:test_jump_inDelaySlot_fails() (gas: 85816)
MIPS_Test:test_jump_nonzeroRegion_succeeds() (gas: 120993) MIPS_Test:test_jump_nonzeroRegion_succeeds() (gas: 120927)
MIPS_Test:test_jump_succeeds() (gas: 120923) MIPS_Test:test_jump_succeeds() (gas: 120857)
MIPS_Test:test_lb_succeeds() (gas: 128164) MIPS_Test:test_lb_succeeds() (gas: 128098)
MIPS_Test:test_lbu_succeeds() (gas: 128062) MIPS_Test:test_lbu_succeeds() (gas: 127996)
MIPS_Test:test_lh_succeeds() (gas: 128185) MIPS_Test:test_lh_succeeds() (gas: 128119)
MIPS_Test:test_lhu_succeeds() (gas: 128102) MIPS_Test:test_lhu_succeeds() (gas: 128036)
MIPS_Test:test_ll_succeeds() (gas: 128324) MIPS_Test:test_ll_succeeds() (gas: 128258)
MIPS_Test:test_lui_succeeds() (gas: 122205) MIPS_Test:test_lui_succeeds() (gas: 122139)
MIPS_Test:test_lw_succeeds() (gas: 127953) MIPS_Test:test_lw_succeeds() (gas: 127887)
MIPS_Test:test_lwl_succeeds() (gas: 243070) MIPS_Test:test_lwl_succeeds() (gas: 242938)
MIPS_Test:test_lwr_succeeds() (gas: 243358) MIPS_Test:test_lwr_succeeds() (gas: 243226)
MIPS_Test:test_mfhi_succeeds() (gas: 122566) MIPS_Test:test_mfhi_succeeds() (gas: 122500)
MIPS_Test:test_mflo_succeeds() (gas: 122695) MIPS_Test:test_mflo_succeeds() (gas: 122629)
MIPS_Test:test_mmap_succeeds() (gas: 119529) MIPS_Test:test_mmap_succeeds() (gas: 119528)
MIPS_Test:test_movn_succeeds() (gas: 204031) MIPS_Test:test_movn_succeeds() (gas: 203965)
MIPS_Test:test_movz_succeeds() (gas: 203899) MIPS_Test:test_movz_succeeds() (gas: 203833)
MIPS_Test:test_mthi_succeeds() (gas: 122610) MIPS_Test:test_mthi_succeeds() (gas: 122544)
MIPS_Test:test_mtlo_succeeds() (gas: 122718) MIPS_Test:test_mtlo_succeeds() (gas: 122652)
MIPS_Test:test_mul_succeeds() (gas: 122210) MIPS_Test:test_mul_succeeds() (gas: 122144)
MIPS_Test:test_mult_succeeds() (gas: 122914) MIPS_Test:test_mult_succeeds() (gas: 122848)
MIPS_Test:test_multu_succeeds() (gas: 122951) MIPS_Test:test_multu_succeeds() (gas: 122885)
MIPS_Test:test_nor_succeeds() (gas: 123043) MIPS_Test:test_nor_succeeds() (gas: 122977)
MIPS_Test:test_or_succeeds() (gas: 123000) MIPS_Test:test_or_succeeds() (gas: 122934)
MIPS_Test:test_ori_succeeds() (gas: 123003) MIPS_Test:test_ori_succeeds() (gas: 122937)
MIPS_Test:test_preimage_read_succeeds() (gas: 235481) MIPS_Test:test_preimage_read_succeeds() (gas: 235349)
MIPS_Test:test_preimage_write_succeeds() (gas: 127551) MIPS_Test:test_preimage_write_succeeds() (gas: 127485)
MIPS_Test:test_prestate_exited_succeeds() (gas: 113792) MIPS_Test:test_prestate_exited_succeeds() (gas: 113726)
MIPS_Test:test_sb_succeeds() (gas: 161501) MIPS_Test:test_sb_succeeds() (gas: 161369)
MIPS_Test:test_sc_succeeds() (gas: 161684) MIPS_Test:test_sc_succeeds() (gas: 161552)
MIPS_Test:test_sh_succeeds() (gas: 161538) MIPS_Test:test_sh_succeeds() (gas: 161406)
MIPS_Test:test_sll_succeeds() (gas: 122171) MIPS_Test:test_sll_succeeds() (gas: 122105)
MIPS_Test:test_sllv_succeeds() (gas: 122400) MIPS_Test:test_sllv_succeeds() (gas: 122334)
MIPS_Test:test_slt_succeeds() (gas: 205226) MIPS_Test:test_slt_succeeds() (gas: 205160)
MIPS_Test:test_sltu_succeeds() (gas: 123217) MIPS_Test:test_sltu_succeeds() (gas: 123151)
MIPS_Test:test_sra_succeeds() (gas: 122422) MIPS_Test:test_sra_succeeds() (gas: 122356)
MIPS_Test:test_srav_succeeds() (gas: 122690) MIPS_Test:test_srav_succeeds() (gas: 122624)
MIPS_Test:test_srl_succeeds() (gas: 122253) MIPS_Test:test_srl_succeeds() (gas: 122187)
MIPS_Test:test_srlv_succeeds() (gas: 122418) MIPS_Test:test_srlv_succeeds() (gas: 122352)
MIPS_Test:test_step_abi_succeeds() (gas: 58467) MIPS_Test:test_step_abi_succeeds() (gas: 58467)
MIPS_Test:test_sub_succeeds() (gas: 123027) MIPS_Test:test_sub_succeeds() (gas: 122961)
MIPS_Test:test_subu_succeeds() (gas: 123024) MIPS_Test:test_subu_succeeds() (gas: 122958)
MIPS_Test:test_sw_succeeds() (gas: 161513) MIPS_Test:test_sw_succeeds() (gas: 161381)
MIPS_Test:test_swl_succeeds() (gas: 161574) MIPS_Test:test_swl_succeeds() (gas: 161442)
MIPS_Test:test_swr_succeeds() (gas: 161649) MIPS_Test:test_swr_succeeds() (gas: 161517)
MIPS_Test:test_xor_succeeds() (gas: 123028) MIPS_Test:test_xor_succeeds() (gas: 122962)
MIPS_Test:test_xori_succeeds() (gas: 123080) MIPS_Test:test_xori_succeeds() (gas: 123014)
MerkleTrie_get_Test:test_get_corruptedProof_reverts() (gas: 5733) MerkleTrie_get_Test:test_get_corruptedProof_reverts() (gas: 5733)
MerkleTrie_get_Test:test_get_extraProofElements_reverts() (gas: 58889) MerkleTrie_get_Test:test_get_extraProofElements_reverts() (gas: 58889)
MerkleTrie_get_Test:test_get_invalidDataRemainder_reverts() (gas: 35845) MerkleTrie_get_Test:test_get_invalidDataRemainder_reverts() (gas: 35845)
...@@ -469,9 +469,9 @@ OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifOutp ...@@ -469,9 +469,9 @@ OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifOutp
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifOutputTimestampIsNotFinalized_reverts() (gas: 182306) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifOutputTimestampIsNotFinalized_reverts() (gas: 182306)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifWithdrawalNotProven_reverts() (gas: 41780) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifWithdrawalNotProven_reverts() (gas: 41780)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifWithdrawalProofNotOldEnough_reverts() (gas: 173953) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_ifWithdrawalProofNotOldEnough_reverts() (gas: 173953)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onInsufficientGas_reverts() (gas: 181188) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onInsufficientGas_reverts() (gas: 181161)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onRecentWithdrawal_reverts() (gas: 154740) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onRecentWithdrawal_reverts() (gas: 154740)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onReentrancy_reverts() (gas: 219235) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onReentrancy_reverts() (gas: 219208)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onReplay_reverts() (gas: 220983) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_onReplay_reverts() (gas: 220983)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_paused_reverts() (gas: 38706) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_paused_reverts() (gas: 38706)
OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_provenWithdrawalHash_succeeds() (gas: 209679) OptimismPortal_FinalizeWithdrawal_Test:test_finalizeWithdrawalTransaction_provenWithdrawalHash_succeeds() (gas: 209679)
...@@ -486,23 +486,14 @@ OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_replayPro ...@@ -486,23 +486,14 @@ OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_replayPro
OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_replayProve_reverts() (gas: 166699) OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_replayProve_reverts() (gas: 166699)
OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_validWithdrawalProof_succeeds() (gas: 154430) OptimismPortal_FinalizeWithdrawal_Test:test_proveWithdrawalTransaction_validWithdrawalProof_succeeds() (gas: 154430)
OptimismPortal_Test:test_constructor_succeeds() (gas: 28164) OptimismPortal_Test:test_constructor_succeeds() (gas: 28164)
OptimismPortal_Test:test_depositTransaction_contractCreation_reverts() (gas: 14292) OptimismPortal_Test:test_depositTransaction_contractCreation_reverts() (gas: 14281)
OptimismPortal_Test:test_depositTransaction_createWithZeroValueForContract_succeeds() (gas: 76814) OptimismPortal_Test:test_depositTransaction_largeData_reverts() (gas: 512200)
OptimismPortal_Test:test_depositTransaction_createWithZeroValueForEOA_succeeds() (gas: 77115)
OptimismPortal_Test:test_depositTransaction_largeData_reverts() (gas: 512149)
OptimismPortal_Test:test_depositTransaction_noValueContract_succeeds() (gas: 76767)
OptimismPortal_Test:test_depositTransaction_noValueEOA_succeeds() (gas: 77112)
OptimismPortal_Test:test_depositTransaction_smallGasLimit_reverts() (gas: 14528) OptimismPortal_Test:test_depositTransaction_smallGasLimit_reverts() (gas: 14528)
OptimismPortal_Test:test_depositTransaction_withEthValueAndContractContractCreation_succeeds() (gas: 83773) OptimismPortal_Test:test_isOutputFinalized_succeeds() (gas: 127594)
OptimismPortal_Test:test_depositTransaction_withEthValueAndEOAContractCreation_succeeds() (gas: 75929) OptimismPortal_Test:test_minimumGasLimit_succeeds() (gas: 17597)
OptimismPortal_Test:test_depositTransaction_withEthValueFromContract_succeeds() (gas: 83476)
OptimismPortal_Test:test_depositTransaction_withEthValueFromEOA_succeeds() (gas: 84069)
OptimismPortal_Test:test_isOutputFinalized_succeeds() (gas: 127617)
OptimismPortal_Test:test_minimumGasLimit_succeeds() (gas: 17430)
OptimismPortal_Test:test_pause_onlyGuardian_reverts() (gas: 24487) OptimismPortal_Test:test_pause_onlyGuardian_reverts() (gas: 24487)
OptimismPortal_Test:test_pause_succeeds() (gas: 27344) OptimismPortal_Test:test_pause_succeeds() (gas: 27344)
OptimismPortal_Test:test_receive_succeeds() (gas: 127564) OptimismPortal_Test:test_receive_succeeds() (gas: 127564)
OptimismPortal_Test:test_simple_isOutputFinalized_succeeds() (gas: 35651)
OptimismPortal_Test:test_unpause_onlyGuardian_reverts() (gas: 31514) OptimismPortal_Test:test_unpause_onlyGuardian_reverts() (gas: 31514)
OptimismPortal_Test:test_unpause_succeeds() (gas: 27451) OptimismPortal_Test:test_unpause_succeeds() (gas: 27451)
OptimistAllowlistTest:test_constructor_succeeds() (gas: 16407) OptimistAllowlistTest:test_constructor_succeeds() (gas: 16407)
...@@ -596,44 +587,44 @@ Proxy_Test:test_upgradeToAndCall_functionDoesNotExist_reverts() (gas: 107980) ...@@ -596,44 +587,44 @@ Proxy_Test:test_upgradeToAndCall_functionDoesNotExist_reverts() (gas: 107980)
Proxy_Test:test_upgradeToAndCall_isPayable_succeeds() (gas: 53744) Proxy_Test:test_upgradeToAndCall_isPayable_succeeds() (gas: 53744)
Proxy_Test:test_upgradeToAndCall_succeeds() (gas: 125192) Proxy_Test:test_upgradeToAndCall_succeeds() (gas: 125192)
Proxy_Test:test_upgradeTo_clashingFunctionSignatures_succeeds() (gas: 101363) Proxy_Test:test_upgradeTo_clashingFunctionSignatures_succeeds() (gas: 101363)
RLPReader_readBytes_Test:test_readBytes_bytestring00_succeeds() (gas: 1863) RLPReader_readBytes_Test:test_readBytes_bytestring00_succeeds() (gas: 1841)
RLPReader_readBytes_Test:test_readBytes_bytestring01_succeeds() (gas: 1840) RLPReader_readBytes_Test:test_readBytes_bytestring01_succeeds() (gas: 1818)
RLPReader_readBytes_Test:test_readBytes_bytestring7f_succeeds() (gas: 1861) RLPReader_readBytes_Test:test_readBytes_bytestring7f_succeeds() (gas: 1861)
RLPReader_readBytes_Test:test_readBytes_invalidListLength_reverts() (gas: 3903) RLPReader_readBytes_Test:test_readBytes_invalidListLength_reverts() (gas: 3946)
RLPReader_readBytes_Test:test_readBytes_invalidPrefix_reverts() (gas: 3961) RLPReader_readBytes_Test:test_readBytes_invalidPrefix_reverts() (gas: 3961)
RLPReader_readBytes_Test:test_readBytes_invalidRemainder_reverts() (gas: 4155) RLPReader_readBytes_Test:test_readBytes_invalidRemainder_reverts() (gas: 4155)
RLPReader_readBytes_Test:test_readBytes_invalidStringLength_reverts() (gas: 3857) RLPReader_readBytes_Test:test_readBytes_invalidStringLength_reverts() (gas: 3857)
RLPReader_readBytes_Test:test_readBytes_revertListItem_reverts() (gas: 3998) RLPReader_readBytes_Test:test_readBytes_revertListItem_reverts() (gas: 3998)
RLPReader_readList_Test:test_readList_dictTest1_succeeds() (gas: 23140) RLPReader_readList_Test:test_readList_dictTest1_succeeds() (gas: 23140)
RLPReader_readList_Test:test_readList_empty_succeeds() (gas: 4610) RLPReader_readList_Test:test_readList_empty_succeeds() (gas: 4610)
RLPReader_readList_Test:test_readList_incorrectLengthInArray_reverts() (gas: 3976) RLPReader_readList_Test:test_readList_incorrectLengthInArray_reverts() (gas: 3954)
RLPReader_readList_Test:test_readList_int32Overflow2_reverts() (gas: 4139) RLPReader_readList_Test:test_readList_int32Overflow2_reverts() (gas: 4139)
RLPReader_readList_Test:test_readList_int32Overflow_reverts() (gas: 4138) RLPReader_readList_Test:test_readList_int32Overflow_reverts() (gas: 4116)
RLPReader_readList_Test:test_readList_invalidRemainder_reverts() (gas: 4114) RLPReader_readList_Test:test_readList_invalidRemainder_reverts() (gas: 4114)
RLPReader_readList_Test:test_readList_invalidShortList_reverts() (gas: 3967) RLPReader_readList_Test:test_readList_invalidShortList_reverts() (gas: 3967)
RLPReader_readList_Test:test_readList_invalidValue_reverts() (gas: 3878) RLPReader_readList_Test:test_readList_invalidValue_reverts() (gas: 3878)
RLPReader_readList_Test:test_readList_leadingZerosInLongLengthArray1_reverts() (gas: 3982) RLPReader_readList_Test:test_readList_leadingZerosInLongLengthArray1_reverts() (gas: 3982)
RLPReader_readList_Test:test_readList_leadingZerosInLongLengthArray2_reverts() (gas: 3945) RLPReader_readList_Test:test_readList_leadingZerosInLongLengthArray2_reverts() (gas: 3968)
RLPReader_readList_Test:test_readList_leadingZerosInLongLengthList1_reverts() (gas: 3984) RLPReader_readList_Test:test_readList_leadingZerosInLongLengthList1_reverts() (gas: 3984)
RLPReader_readList_Test:test_readList_listLongerThan32Elements_reverts() (gas: 38612) RLPReader_readList_Test:test_readList_listLongerThan32Elements_reverts() (gas: 38590)
RLPReader_readList_Test:test_readList_listOfLists2_succeeds() (gas: 12122) RLPReader_readList_Test:test_readList_listOfLists2_succeeds() (gas: 12122)
RLPReader_readList_Test:test_readList_listOfLists_succeeds() (gas: 9472) RLPReader_readList_Test:test_readList_listOfLists_succeeds() (gas: 9450)
RLPReader_readList_Test:test_readList_longList1_succeeds() (gas: 28354) RLPReader_readList_Test:test_readList_longList1_succeeds() (gas: 28332)
RLPReader_readList_Test:test_readList_longList2_succeeds() (gas: 196352) RLPReader_readList_Test:test_readList_longList2_succeeds() (gas: 196395)
RLPReader_readList_Test:test_readList_longListLessThan56Bytes_reverts() (gas: 4023) RLPReader_readList_Test:test_readList_longListLessThan56Bytes_reverts() (gas: 4046)
RLPReader_readList_Test:test_readList_longStringLength_reverts() (gas: 3946) RLPReader_readList_Test:test_readList_longStringLength_reverts() (gas: 3924)
RLPReader_readList_Test:test_readList_longStringLessThan56Bytes_reverts() (gas: 4009) RLPReader_readList_Test:test_readList_longStringLessThan56Bytes_reverts() (gas: 4009)
RLPReader_readList_Test:test_readList_multiList_succeeds() (gas: 11695) RLPReader_readList_Test:test_readList_multiList_succeeds() (gas: 11737)
RLPReader_readList_Test:test_readList_nonOptimalLongLengthArray1_reverts() (gas: 3999) RLPReader_readList_Test:test_readList_nonOptimalLongLengthArray1_reverts() (gas: 3999)
RLPReader_readList_Test:test_readList_nonOptimalLongLengthArray2_reverts() (gas: 4044) RLPReader_readList_Test:test_readList_nonOptimalLongLengthArray2_reverts() (gas: 4022)
RLPReader_readList_Test:test_readList_notEnoughContentForList1_reverts() (gas: 4115) RLPReader_readList_Test:test_readList_notEnoughContentForList1_reverts() (gas: 4115)
RLPReader_readList_Test:test_readList_notEnoughContentForList2_reverts() (gas: 4117) RLPReader_readList_Test:test_readList_notEnoughContentForList2_reverts() (gas: 4161)
RLPReader_readList_Test:test_readList_notEnoughContentForString1_reverts() (gas: 4072) RLPReader_readList_Test:test_readList_notEnoughContentForString1_reverts() (gas: 4072)
RLPReader_readList_Test:test_readList_notEnoughContentForString2_reverts() (gas: 4094) RLPReader_readList_Test:test_readList_notEnoughContentForString2_reverts() (gas: 4138)
RLPReader_readList_Test:test_readList_notLongEnough_reverts() (gas: 3955) RLPReader_readList_Test:test_readList_notLongEnough_reverts() (gas: 3955)
RLPReader_readList_Test:test_readList_shortListMax1_succeeds() (gas: 39602) RLPReader_readList_Test:test_readList_shortListMax1_succeeds() (gas: 39580)
RLPWriter_writeList_Test:test_writeList_dictTest1_succeeds() (gas: 36979) RLPWriter_writeList_Test:test_writeList_dictTest1_succeeds() (gas: 36979)
RLPWriter_writeList_Test:test_writeList_empty_succeeds() (gas: 1711) RLPWriter_writeList_Test:test_writeList_empty_succeeds() (gas: 1666)
RLPWriter_writeList_Test:test_writeList_listoflists2_succeeds() (gas: 16566) RLPWriter_writeList_Test:test_writeList_listoflists2_succeeds() (gas: 16566)
RLPWriter_writeList_Test:test_writeList_listoflists_succeeds() (gas: 10841) RLPWriter_writeList_Test:test_writeList_listoflists_succeeds() (gas: 10841)
RLPWriter_writeList_Test:test_writeList_longlist1_succeeds() (gas: 40383) RLPWriter_writeList_Test:test_writeList_longlist1_succeeds() (gas: 40383)
...@@ -641,22 +632,22 @@ RLPWriter_writeList_Test:test_writeList_longlist2_succeeds() (gas: 280754) ...@@ -641,22 +632,22 @@ RLPWriter_writeList_Test:test_writeList_longlist2_succeeds() (gas: 280754)
RLPWriter_writeList_Test:test_writeList_multiList_succeeds() (gas: 22446) RLPWriter_writeList_Test:test_writeList_multiList_succeeds() (gas: 22446)
RLPWriter_writeList_Test:test_writeList_shortListMax1_succeeds() (gas: 36793) RLPWriter_writeList_Test:test_writeList_shortListMax1_succeeds() (gas: 36793)
RLPWriter_writeList_Test:test_writeList_stringList_succeeds() (gas: 10697) RLPWriter_writeList_Test:test_writeList_stringList_succeeds() (gas: 10697)
RLPWriter_writeString_Test:test_writeString_bytestring00_succeeds() (gas: 951) RLPWriter_writeString_Test:test_writeString_bytestring00_succeeds() (gas: 929)
RLPWriter_writeString_Test:test_writeString_bytestring01_succeeds() (gas: 951) RLPWriter_writeString_Test:test_writeString_bytestring01_succeeds() (gas: 906)
RLPWriter_writeString_Test:test_writeString_bytestring7f_succeeds() (gas: 972) RLPWriter_writeString_Test:test_writeString_bytestring7f_succeeds() (gas: 972)
RLPWriter_writeString_Test:test_writeString_empty_succeeds() (gas: 1633) RLPWriter_writeString_Test:test_writeString_empty_succeeds() (gas: 1611)
RLPWriter_writeString_Test:test_writeString_longstring2_succeeds() (gas: 258768) RLPWriter_writeString_Test:test_writeString_longstring2_succeeds() (gas: 258768)
RLPWriter_writeString_Test:test_writeString_longstring_succeeds() (gas: 16961) RLPWriter_writeString_Test:test_writeString_longstring_succeeds() (gas: 16939)
RLPWriter_writeString_Test:test_writeString_shortstring2_succeeds() (gas: 15376) RLPWriter_writeString_Test:test_writeString_shortstring2_succeeds() (gas: 15376)
RLPWriter_writeString_Test:test_writeString_shortstring_succeeds() (gas: 2470) RLPWriter_writeString_Test:test_writeString_shortstring_succeeds() (gas: 2470)
RLPWriter_writeUint_Test:test_writeUint_mediumint2_succeeds() (gas: 8721) RLPWriter_writeUint_Test:test_writeUint_mediumint2_succeeds() (gas: 8699)
RLPWriter_writeUint_Test:test_writeUint_mediumint3_succeeds() (gas: 9098) RLPWriter_writeUint_Test:test_writeUint_mediumint3_succeeds() (gas: 9098)
RLPWriter_writeUint_Test:test_writeUint_mediumint_succeeds() (gas: 8357) RLPWriter_writeUint_Test:test_writeUint_mediumint_succeeds() (gas: 8380)
RLPWriter_writeUint_Test:test_writeUint_smallint2_succeeds() (gas: 7249) RLPWriter_writeUint_Test:test_writeUint_smallint2_succeeds() (gas: 7249)
RLPWriter_writeUint_Test:test_writeUint_smallint3_succeeds() (gas: 7271) RLPWriter_writeUint_Test:test_writeUint_smallint3_succeeds() (gas: 7271)
RLPWriter_writeUint_Test:test_writeUint_smallint4_succeeds() (gas: 7272) RLPWriter_writeUint_Test:test_writeUint_smallint4_succeeds() (gas: 7250)
RLPWriter_writeUint_Test:test_writeUint_smallint_succeeds() (gas: 7250) RLPWriter_writeUint_Test:test_writeUint_smallint_succeeds() (gas: 7294)
RLPWriter_writeUint_Test:test_writeUint_zero_succeeds() (gas: 7734) RLPWriter_writeUint_Test:test_writeUint_zero_succeeds() (gas: 7777)
ResolvedDelegateProxy_Test:test_fallback_addressManagerNotSet_reverts() (gas: 605906) ResolvedDelegateProxy_Test:test_fallback_addressManagerNotSet_reverts() (gas: 605906)
ResolvedDelegateProxy_Test:test_fallback_delegateCallBar_reverts() (gas: 24783) ResolvedDelegateProxy_Test:test_fallback_delegateCallBar_reverts() (gas: 24783)
ResourceMetering_Test:test_meter_denominatorEq1_reverts() (gas: 20024064) ResourceMetering_Test:test_meter_denominatorEq1_reverts() (gas: 20024064)
......
...@@ -52,3 +52,11 @@ ignore = ['src/vendor/WETH9.sol'] ...@@ -52,3 +52,11 @@ ignore = ['src/vendor/WETH9.sol']
[profile.ci.fuzz] [profile.ci.fuzz]
runs = 512 runs = 512
################################################################
# PROFILE: LITE #
################################################################
[profile.lite]
optimizer = false
...@@ -14,7 +14,7 @@ contract AdminFaucetAuthModule is IFaucetAuthModule, EIP712 { ...@@ -14,7 +14,7 @@ contract AdminFaucetAuthModule is IFaucetAuthModule, EIP712 {
address public immutable ADMIN; address public immutable ADMIN;
/// @notice EIP712 typehash for the Proof type. /// @notice EIP712 typehash for the Proof type.
bytes32 public constant PROOF_TYPEHASH = keccak256("Proof(address recipient,bytes32 nonce,bytes32 id)"); bytes32 public immutable PROOF_TYPEHASH = keccak256("Proof(address recipient,bytes32 nonce,bytes32 id)");
/// @notice Struct that represents a proof that verifies the admin. /// @notice Struct that represents a proof that verifies the admin.
/// @custom:field recipient Address that will be receiving the faucet funds. /// @custom:field recipient Address that will be receiving the faucet funds.
......
...@@ -36,6 +36,7 @@ import { LegacyMintableERC20 } from "src/legacy/LegacyMintableERC20.sol"; ...@@ -36,6 +36,7 @@ import { LegacyMintableERC20 } from "src/legacy/LegacyMintableERC20.sol";
import { SystemConfig } from "src/L1/SystemConfig.sol"; import { SystemConfig } from "src/L1/SystemConfig.sol";
import { ResourceMetering } from "src/L1/ResourceMetering.sol"; import { ResourceMetering } from "src/L1/ResourceMetering.sol";
import { Constants } from "src/libraries/Constants.sol"; import { Constants } from "src/libraries/Constants.sol";
import { FFIInterface } from "test/setup/FFIInterface.sol";
contract CommonTest is Test { contract CommonTest is Test {
address alice = address(128); address alice = address(128);
...@@ -497,237 +498,3 @@ contract FeeVault_Initializer is Bridge_Initializer { ...@@ -497,237 +498,3 @@ contract FeeVault_Initializer is Bridge_Initializer {
event Withdrawal(uint256 value, address to, address from, FeeVault.WithdrawalNetwork withdrawalNetwork); event Withdrawal(uint256 value, address to, address from, FeeVault.WithdrawalNetwork withdrawalNetwork);
} }
contract FFIInterface is Test {
function getProveWithdrawalTransactionInputs(Types.WithdrawalTransaction memory _tx)
external
returns (bytes32, bytes32, bytes32, bytes32, bytes[] memory)
{
string[] memory cmds = new string[](9);
cmds[0] = "scripts/go-ffi/go-ffi";
cmds[1] = "diff";
cmds[2] = "getProveWithdrawalTransactionInputs";
cmds[3] = vm.toString(_tx.nonce);
cmds[4] = vm.toString(_tx.sender);
cmds[5] = vm.toString(_tx.target);
cmds[6] = vm.toString(_tx.value);
cmds[7] = vm.toString(_tx.gasLimit);
cmds[8] = vm.toString(_tx.data);
bytes memory result = vm.ffi(cmds);
(
bytes32 stateRoot,
bytes32 storageRoot,
bytes32 outputRoot,
bytes32 withdrawalHash,
bytes[] memory withdrawalProof
) = abi.decode(result, (bytes32, bytes32, bytes32, bytes32, bytes[]));
return (stateRoot, storageRoot, outputRoot, withdrawalHash, withdrawalProof);
}
function hashCrossDomainMessage(
uint256 _nonce,
address _sender,
address _target,
uint256 _value,
uint256 _gasLimit,
bytes memory _data
)
external
returns (bytes32)
{
string[] memory cmds = new string[](9);
cmds[0] = "scripts/go-ffi/go-ffi";
cmds[1] = "diff";
cmds[2] = "hashCrossDomainMessage";
cmds[3] = vm.toString(_nonce);
cmds[4] = vm.toString(_sender);
cmds[5] = vm.toString(_target);
cmds[6] = vm.toString(_value);
cmds[7] = vm.toString(_gasLimit);
cmds[8] = vm.toString(_data);
bytes memory result = vm.ffi(cmds);
return abi.decode(result, (bytes32));
}
function hashWithdrawal(
uint256 _nonce,
address _sender,
address _target,
uint256 _value,
uint256 _gasLimit,
bytes memory _data
)
external
returns (bytes32)
{
string[] memory cmds = new string[](9);
cmds[0] = "scripts/go-ffi/go-ffi";
cmds[1] = "diff";
cmds[2] = "hashWithdrawal";
cmds[3] = vm.toString(_nonce);
cmds[4] = vm.toString(_sender);
cmds[5] = vm.toString(_target);
cmds[6] = vm.toString(_value);
cmds[7] = vm.toString(_gasLimit);
cmds[8] = vm.toString(_data);
bytes memory result = vm.ffi(cmds);
return abi.decode(result, (bytes32));
}
function hashOutputRootProof(
bytes32 _version,
bytes32 _stateRoot,
bytes32 _messagePasserStorageRoot,
bytes32 _latestBlockhash
)
external
returns (bytes32)
{
string[] memory cmds = new string[](7);
cmds[0] = "scripts/go-ffi/go-ffi";
cmds[1] = "diff";
cmds[2] = "hashOutputRootProof";
cmds[3] = Strings.toHexString(uint256(_version));
cmds[4] = Strings.toHexString(uint256(_stateRoot));
cmds[5] = Strings.toHexString(uint256(_messagePasserStorageRoot));
cmds[6] = Strings.toHexString(uint256(_latestBlockhash));
bytes memory result = vm.ffi(cmds);
return abi.decode(result, (bytes32));
}
function hashDepositTransaction(
address _from,
address _to,
uint256 _mint,
uint256 _value,
uint64 _gas,
bytes memory _data,
uint64 _logIndex
)
external
returns (bytes32)
{
string[] memory cmds = new string[](11);
cmds[0] = "scripts/go-ffi/go-ffi";
cmds[1] = "diff";
cmds[2] = "hashDepositTransaction";
cmds[3] = "0x0000000000000000000000000000000000000000000000000000000000000000";
cmds[4] = vm.toString(_logIndex);
cmds[5] = vm.toString(_from);
cmds[6] = vm.toString(_to);
cmds[7] = vm.toString(_mint);
cmds[8] = vm.toString(_value);
cmds[9] = vm.toString(_gas);
cmds[10] = vm.toString(_data);
bytes memory result = vm.ffi(cmds);
return abi.decode(result, (bytes32));
}
function encodeDepositTransaction(Types.UserDepositTransaction calldata txn) external returns (bytes memory) {
string[] memory cmds = new string[](12);
cmds[0] = "scripts/go-ffi/go-ffi";
cmds[1] = "diff";
cmds[2] = "encodeDepositTransaction";
cmds[3] = vm.toString(txn.from);
cmds[4] = vm.toString(txn.to);
cmds[5] = vm.toString(txn.value);
cmds[6] = vm.toString(txn.mint);
cmds[7] = vm.toString(txn.gasLimit);
cmds[8] = vm.toString(txn.isCreation);
cmds[9] = vm.toString(txn.data);
cmds[10] = vm.toString(txn.l1BlockHash);
cmds[11] = vm.toString(txn.logIndex);
bytes memory result = vm.ffi(cmds);
return abi.decode(result, (bytes));
}
function encodeCrossDomainMessage(
uint256 _nonce,
address _sender,
address _target,
uint256 _value,
uint256 _gasLimit,
bytes memory _data
)
external
returns (bytes memory)
{
string[] memory cmds = new string[](9);
cmds[0] = "scripts/go-ffi/go-ffi";
cmds[1] = "diff";
cmds[2] = "encodeCrossDomainMessage";
cmds[3] = vm.toString(_nonce);
cmds[4] = vm.toString(_sender);
cmds[5] = vm.toString(_target);
cmds[6] = vm.toString(_value);
cmds[7] = vm.toString(_gasLimit);
cmds[8] = vm.toString(_data);
bytes memory result = vm.ffi(cmds);
return abi.decode(result, (bytes));
}
function decodeVersionedNonce(uint256 nonce) external returns (uint256, uint256) {
string[] memory cmds = new string[](4);
cmds[0] = "scripts/go-ffi/go-ffi";
cmds[1] = "diff";
cmds[2] = "decodeVersionedNonce";
cmds[3] = vm.toString(nonce);
bytes memory result = vm.ffi(cmds);
return abi.decode(result, (uint256, uint256));
}
function getMerkleTrieFuzzCase(string memory variant)
external
returns (bytes32, bytes memory, bytes memory, bytes[] memory)
{
string[] memory cmds = new string[](6);
cmds[0] = "./scripts/go-ffi/go-ffi";
cmds[1] = "trie";
cmds[2] = variant;
return abi.decode(vm.ffi(cmds), (bytes32, bytes, bytes, bytes[]));
}
function getCannonMemoryProof(uint32 pc, uint32 insn) external returns (bytes32, bytes memory) {
string[] memory cmds = new string[](5);
cmds[0] = "scripts/go-ffi/go-ffi";
cmds[1] = "diff";
cmds[2] = "cannonMemoryProof";
cmds[3] = vm.toString(pc);
cmds[4] = vm.toString(insn);
bytes memory result = vm.ffi(cmds);
(bytes32 memRoot, bytes memory proof) = abi.decode(result, (bytes32, bytes));
return (memRoot, proof);
}
function getCannonMemoryProof(
uint32 pc,
uint32 insn,
uint32 memAddr,
uint32 memVal
)
external
returns (bytes32, bytes memory)
{
string[] memory cmds = new string[](7);
cmds[0] = "scripts/go-ffi/go-ffi";
cmds[1] = "diff";
cmds[2] = "cannonMemoryProof";
cmds[3] = vm.toString(pc);
cmds[4] = vm.toString(insn);
cmds[5] = vm.toString(memAddr);
cmds[6] = vm.toString(memVal);
bytes memory result = vm.ffi(cmds);
(bytes32 memRoot, bytes memory proof) = abi.decode(result, (bytes32, bytes));
return (memRoot, proof);
}
}
...@@ -2,17 +2,18 @@ ...@@ -2,17 +2,18 @@
pragma solidity 0.8.15; pragma solidity 0.8.15;
// Testing utilities // Testing utilities
import { Vm, VmSafe } from "forge-std/Vm.sol"; import { VmSafe } from "forge-std/Vm.sol";
import { CommonTest, Portal_Initializer } from "test/CommonTest.t.sol"; import { Test } from "forge-std/Test.sol";
import { Portal_Initializer } from "test/CommonTest.t.sol";
// Libraries // Libraries
import { Bytes32AddressLib } from "@rari-capital/solmate/src/utils/Bytes32AddressLib.sol"; import { Bytes32AddressLib } from "@rari-capital/solmate/src/utils/Bytes32AddressLib.sol";
// Target contract dependencies // Target contract dependencies
import { AddressAliasHelper } from "../src/vendor/AddressAliasHelper.sol"; import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol";
// Target contract // Target contract
import { CrossDomainOwnable } from "../src/L2/CrossDomainOwnable.sol"; import { CrossDomainOwnable } from "src/L2/CrossDomainOwnable.sol";
contract XDomainSetter is CrossDomainOwnable { contract XDomainSetter is CrossDomainOwnable {
uint256 public value; uint256 public value;
...@@ -22,11 +23,10 @@ contract XDomainSetter is CrossDomainOwnable { ...@@ -22,11 +23,10 @@ contract XDomainSetter is CrossDomainOwnable {
} }
} }
contract CrossDomainOwnable_Test is CommonTest { contract CrossDomainOwnable_Test is Test {
XDomainSetter setter; XDomainSetter setter;
function setUp() public override { function setUp() public {
super.setUp();
setter = new XDomainSetter(); setter = new XDomainSetter();
} }
......
...@@ -2,16 +2,16 @@ ...@@ -2,16 +2,16 @@
pragma solidity 0.8.15; pragma solidity 0.8.15;
// Testing utilities // Testing utilities
import { CommonTest } from "test/CommonTest.t.sol"; import { Test } from "forge-std/Test.sol";
// Target contract // Target contract
import { DeployerWhitelist } from "src/legacy/DeployerWhitelist.sol"; import { DeployerWhitelist } from "src/legacy/DeployerWhitelist.sol";
contract DeployerWhitelist_Test is CommonTest { contract DeployerWhitelist_Test is Test {
DeployerWhitelist list; DeployerWhitelist list;
/// @dev Sets up the test suite. /// @dev Sets up the test suite.
function setUp() public virtual override { function setUp() public {
list = new DeployerWhitelist(); list = new DeployerWhitelist();
} }
......
...@@ -47,7 +47,7 @@ contract LivenessGuard_Constructor_Test is LivenessGuard_TestInit { ...@@ -47,7 +47,7 @@ contract LivenessGuard_Constructor_Test is LivenessGuard_TestInit {
function test_constructor_works() external { function test_constructor_works() external {
address[] memory owners = safeInstance.owners; address[] memory owners = safeInstance.owners;
livenessGuard = new WrappedGuard(safeInstance.safe); livenessGuard = new WrappedGuard(safeInstance.safe);
for (uint256 i = 0; i < owners.length; i++) { for (uint256 i; i < owners.length; i++) {
assertEq(livenessGuard.lastLive(owners[i]), initTime); assertEq(livenessGuard.lastLive(owners[i]), initTime);
} }
} }
...@@ -242,7 +242,7 @@ contract LivenessGuard_FuzzOwnerManagement_Test is StdCheats, StdUtils, Liveness ...@@ -242,7 +242,7 @@ contract LivenessGuard_FuzzOwnerManagement_Test is StdCheats, StdUtils, Liveness
(address[] memory ownerAddrs, uint256[] memory ownerkeys) = (address[] memory ownerAddrs, uint256[] memory ownerkeys) =
SafeTestLib.makeAddrsAndKeys("safeTest", initialOwners); SafeTestLib.makeAddrsAndKeys("safeTest", initialOwners);
// record the private keys for later use // record the private keys for later use
for (uint256 i = 0; i < ownerAddrs.length; i++) { for (uint256 i; i < ownerAddrs.length; i++) {
privateKeys[ownerAddrs[i]] = ownerkeys[i]; privateKeys[ownerAddrs[i]] = ownerkeys[i];
} }
...@@ -251,7 +251,7 @@ contract LivenessGuard_FuzzOwnerManagement_Test is StdCheats, StdUtils, Liveness ...@@ -251,7 +251,7 @@ contract LivenessGuard_FuzzOwnerManagement_Test is StdCheats, StdUtils, Liveness
livenessGuard = new WrappedGuard(safeInstance.safe); livenessGuard = new WrappedGuard(safeInstance.safe);
safeInstance.setGuard(address(livenessGuard)); safeInstance.setGuard(address(livenessGuard));
for (uint256 i = 0; i < changes.length; i++) { for (uint256 i; i < changes.length; i++) {
vm.warp(block.timestamp + changes[i].timeDelta); vm.warp(block.timestamp + changes[i].timeDelta);
OwnerChange memory change = changes[i]; OwnerChange memory change = changes[i];
address[] memory currentOwners = safeInstance.safe.getOwners(); address[] memory currentOwners = safeInstance.safe.getOwners();
...@@ -312,16 +312,16 @@ contract LivenessGuard_FuzzOwnerManagement_Test is StdCheats, StdUtils, Liveness ...@@ -312,16 +312,16 @@ contract LivenessGuard_FuzzOwnerManagement_Test is StdCheats, StdUtils, Liveness
// Looks up the private key for each owner // Looks up the private key for each owner
uint256[] memory unsortedOwnerPKs = new uint256[](instance.owners.length); uint256[] memory unsortedOwnerPKs = new uint256[](instance.owners.length);
for (uint256 j = 0; j < instance.owners.length; j++) { for (uint256 i; i < instance.owners.length; i++) {
unsortedOwnerPKs[j] = privateKeys[instance.owners[j]]; unsortedOwnerPKs[i] = privateKeys[instance.owners[i]];
} }
// Sort the keys by address and store them in the SafeInstance // Sort the keys by address and store them in the SafeInstance
instance.ownerPKs = SafeTestLib.sortPKsByComputedAddress(unsortedOwnerPKs); instance.ownerPKs = SafeTestLib.sortPKsByComputedAddress(unsortedOwnerPKs);
// Overwrite the SafeInstances owners array with the computed addresses from the ownerPKs array // Overwrite the SafeInstances owners array with the computed addresses from the ownerPKs array
for (uint256 k; k < instance.owners.length; k++) { for (uint256 i; i < instance.owners.length; i++) {
instance.owners[k] = SafeTestLib.getAddr(instance.ownerPKs[k]); instance.owners[i] = SafeTestLib.getAddr(instance.ownerPKs[i]);
} }
} }
} }
...@@ -199,7 +199,7 @@ contract LivenessModule_RemoveOwners_TestFail is LivenessModule_TestInit { ...@@ -199,7 +199,7 @@ contract LivenessModule_RemoveOwners_TestFail is LivenessModule_TestInit {
uint256 numOwners = safeInstance.owners.length; uint256 numOwners = safeInstance.owners.length;
address[] memory ownersToRemove = new address[](numOwners); address[] memory ownersToRemove = new address[](numOwners);
for (uint256 i = 0; i < numOwners; i++) { for (uint256 i; i < numOwners; i++) {
ownersToRemove[i] = safeInstance.owners[i]; ownersToRemove[i] = safeInstance.owners[i];
} }
address[] memory prevOwners = safeInstance.getPrevOwners(ownersToRemove); address[] memory prevOwners = safeInstance.getPrevOwners(ownersToRemove);
...@@ -218,7 +218,7 @@ contract LivenessModule_RemoveOwners_TestFail is LivenessModule_TestInit { ...@@ -218,7 +218,7 @@ contract LivenessModule_RemoveOwners_TestFail is LivenessModule_TestInit {
uint256 numOwners = safeInstance.owners.length - 2; uint256 numOwners = safeInstance.owners.length - 2;
address[] memory ownersToRemove = new address[](numOwners); address[] memory ownersToRemove = new address[](numOwners);
for (uint256 i = 0; i < numOwners; i++) { for (uint256 i; i < numOwners; i++) {
ownersToRemove[i] = safeInstance.owners[i]; ownersToRemove[i] = safeInstance.owners[i];
} }
address[] memory prevOwners = safeInstance.getPrevOwners(ownersToRemove); address[] memory prevOwners = safeInstance.getPrevOwners(ownersToRemove);
...@@ -236,7 +236,7 @@ contract LivenessModule_RemoveOwners_TestFail is LivenessModule_TestInit { ...@@ -236,7 +236,7 @@ contract LivenessModule_RemoveOwners_TestFail is LivenessModule_TestInit {
uint256 numOwners = safeInstance.owners.length - 1; uint256 numOwners = safeInstance.owners.length - 1;
address[] memory ownersToRemove = new address[](numOwners); address[] memory ownersToRemove = new address[](numOwners);
for (uint256 i = 0; i < numOwners; i++) { for (uint256 i; i < numOwners; i++) {
ownersToRemove[i] = safeInstance.owners[i]; ownersToRemove[i] = safeInstance.owners[i];
} }
address[] memory prevOwners = safeInstance.getPrevOwners(ownersToRemove); address[] memory prevOwners = safeInstance.getPrevOwners(ownersToRemove);
...@@ -297,7 +297,7 @@ contract LivenessModule_RemoveOwners_Test is LivenessModule_TestInit { ...@@ -297,7 +297,7 @@ contract LivenessModule_RemoveOwners_Test is LivenessModule_TestInit {
uint256 numOwners = safeInstance.owners.length; uint256 numOwners = safeInstance.owners.length;
address[] memory ownersToRemove = new address[](numOwners); address[] memory ownersToRemove = new address[](numOwners);
for (uint256 i = 0; i < numOwners; i++) { for (uint256 i; i < numOwners; i++) {
ownersToRemove[i] = safeInstance.owners[i]; ownersToRemove[i] = safeInstance.owners[i];
} }
address[] memory prevOwners = safeInstance.getPrevOwners(ownersToRemove); address[] memory prevOwners = safeInstance.getPrevOwners(ownersToRemove);
...@@ -348,15 +348,12 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit { ...@@ -348,15 +348,12 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit {
// //
// _numOwners must be at least 4, so that _minOwners can be set to at least 3 by the following bound() call. // _numOwners must be at least 4, so that _minOwners can be set to at least 3 by the following bound() call.
// Limiting the owner set to 20 helps to keep the runtime of the test reasonable. // Limiting the owner set to 20 helps to keep the runtime of the test reasonable.
console.log("bounding numOwners");
numOwners_ = bound(_numOwners, 4, 20); numOwners_ = bound(_numOwners, 4, 20);
// _minOwners must be at least 3, otherwise we don't have any range below _minOwners in which to test all of the // _minOwners must be at least 3, otherwise we don't have any range below _minOwners in which to test all of the
// ShutDownBehavior options. // ShutDownBehavior options.
console.log("bounding minOwners");
minOwners_ = bound(_minOwners, 3, numOwners_ - 1); minOwners_ = bound(_minOwners, 3, numOwners_ - 1);
// Ensure that _numLiveOwners is less than _numOwners so that we can remove at least one owner. // Ensure that _numLiveOwners is less than _numOwners so that we can remove at least one owner.
console.log("bounding numLiveOwners");
numLiveOwners_ = bound(_numLiveOwners, 0, numOwners_ - 1); numLiveOwners_ = bound(_numLiveOwners, 0, numOwners_ - 1);
// The above bounds are a bit tricky, so we assert that the resulting parameters enable us to test all possible // The above bounds are a bit tricky, so we assert that the resulting parameters enable us to test all possible
...@@ -402,20 +399,19 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit { ...@@ -402,20 +399,19 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit {
// Create an array of live owners, and call showLiveness for each of them // Create an array of live owners, and call showLiveness for each of them
address[] memory liveOwners = new address[](numLiveOwners); address[] memory liveOwners = new address[](numLiveOwners);
for (uint256 i = 0; i < numLiveOwners; i++) { for (uint256 i; i < numLiveOwners; i++) {
liveOwners[i] = safeInstance.owners[i]; liveOwners[i] = safeInstance.owners[i];
vm.prank(safeInstance.owners[i]); vm.prank(safeInstance.owners[i]);
livenessGuard.showLiveness(); livenessGuard.showLiveness();
} }
address[] memory nonLiveOwners = new address[](numOwners - numLiveOwners); address[] memory nonLiveOwners = new address[](numOwners - numLiveOwners);
for (uint256 i = 0; i < numOwners - numLiveOwners; i++) { for (uint256 i; i < numOwners - numLiveOwners; i++) {
nonLiveOwners[i] = safeInstance.owners[i + numLiveOwners]; nonLiveOwners[i] = safeInstance.owners[i + numLiveOwners];
} }
address[] memory prevOwners; address[] memory prevOwners;
if (numLiveOwners >= minOwners) { if (numLiveOwners >= minOwners) {
console.log("No shutdown");
// The safe will remain above the minimum number of owners, so we can remove only those owners which are not // The safe will remain above the minimum number of owners, so we can remove only those owners which are not
// live. // live.
prevOwners = safeInstance.getPrevOwners(nonLiveOwners); prevOwners = safeInstance.getPrevOwners(nonLiveOwners);
...@@ -424,10 +420,10 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit { ...@@ -424,10 +420,10 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit {
// Validate the resulting state of the Safe // Validate the resulting state of the Safe
assertEq(safeInstance.safe.getOwners().length, numLiveOwners); assertEq(safeInstance.safe.getOwners().length, numLiveOwners);
assertEq(safeInstance.safe.getThreshold(), get75PercentThreshold(numLiveOwners)); assertEq(safeInstance.safe.getThreshold(), get75PercentThreshold(numLiveOwners));
for (uint256 i = 0; i < numLiveOwners; i++) { for (uint256 i; i < numLiveOwners; i++) {
assertTrue(safeInstance.safe.isOwner(liveOwners[i])); assertTrue(safeInstance.safe.isOwner(liveOwners[i]));
} }
for (uint256 i = 0; i < nonLiveOwners.length; i++) { for (uint256 i; i < nonLiveOwners.length; i++) {
assertFalse(safeInstance.safe.isOwner(nonLiveOwners[i])); assertFalse(safeInstance.safe.isOwner(nonLiveOwners[i]));
} }
} else { } else {
...@@ -439,14 +435,10 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit { ...@@ -439,14 +435,10 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit {
// The safe is below the minimum number of owners. // The safe is below the minimum number of owners.
// The ShutDownBehavior enum determines how we handle this case. // The ShutDownBehavior enum determines how we handle this case.
if (shutDownBehavior == ShutDownBehavior.Correct) { if (shutDownBehavior == ShutDownBehavior.Correct) {
console.log("Correct Shutdown");
// We remove all owners, and transfer ownership to the shutDown owner. // We remove all owners, and transfer ownership to the shutDown owner.
// but we need to do remove the non-live owners first, so we reverse the owners array, since // but we need to do remove the non-live owners first, so we reverse the owners array, since
// the first owners in the array were the ones to call showLiveness. // the first owners in the array were the ones to call showLiveness.
// ownersToRemove = new address[](numOwners); for (uint256 i; i < numOwners; i++) {
for (uint256 i = 0; i < numOwners; i++) {
// ownersToRemove[numOwners - i - 1] = safeInstance.owners[i];
// ownersToRemove[i] = safeInstance.owners[numOwners - i - 1];
ownersToRemove.push(safeInstance.owners[numOwners - i - 1]); ownersToRemove.push(safeInstance.owners[numOwners - i - 1]);
} }
prevOwners = safeInstance.getPrevOwners(ownersToRemove); prevOwners = safeInstance.getPrevOwners(ownersToRemove);
...@@ -461,14 +453,11 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit { ...@@ -461,14 +453,11 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit {
// trigger that behavior. We initialize that value here then set it in the if statements below. // trigger that behavior. We initialize that value here then set it in the if statements below.
uint256 numOwnersToRemoveinShutDown; uint256 numOwnersToRemoveinShutDown;
if (shutDownBehavior == ShutDownBehavior.DoesNotRemoveAllOwners) { if (shutDownBehavior == ShutDownBehavior.DoesNotRemoveAllOwners) {
console.log("Shutdown DoesNotRemoveAllOwners");
// In the DoesNotRemoveAllOwners case, we should have more than 1 of the pre-existing owners // In the DoesNotRemoveAllOwners case, we should have more than 1 of the pre-existing owners
// remaining // remaining
console.log("bounding numOwnersToRemoveinShutDown");
numOwnersToRemoveinShutDown = numOwnersToRemoveinShutDown =
bound(_numOwnersToRemoveinShutDown, numOwners - minOwners + 1, numOwners - 2); bound(_numOwnersToRemoveinShutDown, numOwners - minOwners + 1, numOwners - 2);
uint256 i = 0; for (uint256 i; i < numOwnersToRemoveinShutDown; i++) {
for (i; i < numOwnersToRemoveinShutDown; i++) {
// Add non-live owners to remove first // Add non-live owners to remove first
if (i < nonLiveOwners.length) { if (i < nonLiveOwners.length) {
ownersToRemove.push(nonLiveOwners[i]); ownersToRemove.push(nonLiveOwners[i]);
...@@ -483,11 +472,9 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit { ...@@ -483,11 +472,9 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit {
); );
livenessModule.removeOwners(prevOwners, ownersToRemove); livenessModule.removeOwners(prevOwners, ownersToRemove);
} else if (shutDownBehavior == ShutDownBehavior.DoesNotTransferToFallbackOwner) { } else if (shutDownBehavior == ShutDownBehavior.DoesNotTransferToFallbackOwner) {
console.log("Shutdown DoesNotTransferToFallbackOwner");
// In the DoesNotRemoveAllOwners case, we should have exactly 1 pre-existing owners remaining // In the DoesNotRemoveAllOwners case, we should have exactly 1 pre-existing owners remaining
numOwnersToRemoveinShutDown = numOwners - 1; numOwnersToRemoveinShutDown = numOwners - 1;
uint256 i = 0; for (uint256 i; i < numOwnersToRemoveinShutDown; i++) {
for (i; i < numOwnersToRemoveinShutDown; i++) {
// Add non-live owners to remove first // Add non-live owners to remove first
if (i < nonLiveOwners.length) { if (i < nonLiveOwners.length) {
ownersToRemove.push(nonLiveOwners[i]); ownersToRemove.push(nonLiveOwners[i]);
...@@ -503,8 +490,8 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit { ...@@ -503,8 +490,8 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit {
// For both of the incorrect behaviors, verify no change to the Safe state // For both of the incorrect behaviors, verify no change to the Safe state
assertEq(safeInstance.safe.getOwners().length, numOwners); assertEq(safeInstance.safe.getOwners().length, numOwners);
assertEq(safeInstance.safe.getThreshold(), get75PercentThreshold(numOwners)); assertEq(safeInstance.safe.getThreshold(), get75PercentThreshold(numOwners));
for (uint256 j = 0; j < numOwners; j++) { for (uint256 i; i < numOwners; i++) {
assertTrue(safeInstance.safe.isOwner(safeInstance.owners[j])); assertTrue(safeInstance.safe.isOwner(safeInstance.owners[i]));
} }
} }
} }
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { CommonTest } from "test/CommonTest.t.sol"; import { Test } from "forge-std/Test.sol";
import { MerkleTrie } from "src/libraries/trie/MerkleTrie.sol"; import { MerkleTrie } from "src/libraries/trie/MerkleTrie.sol";
import { FFIInterface } from "test/setup/FFIInterface.sol";
contract MerkleTrie_get_Test is Test {
FFIInterface ffi;
function setUp() public {
ffi = new FFIInterface();
}
contract MerkleTrie_get_Test is CommonTest {
function test_get_validProof1_succeeds() external { function test_get_validProof1_succeeds() external {
bytes32 root = 0xd582f99275e227a1cf4284899e5ff06ee56da8859be71b553397c69151bc942f; bytes32 root = 0xd582f99275e227a1cf4284899e5ff06ee56da8859be71b553397c69151bc942f;
bytes memory key = hex"6b6579326262"; bytes memory key = hex"6b6579326262";
......
...@@ -25,6 +25,13 @@ contract OptimismPortal_Test is Portal_Initializer { ...@@ -25,6 +25,13 @@ contract OptimismPortal_Test is Portal_Initializer {
event Paused(address); event Paused(address);
event Unpaused(address); event Unpaused(address);
address depositor;
function setUp() public override {
super.setUp();
depositor = makeAddr("depositor");
}
/// @dev Tests that the constructor sets the correct values. /// @dev Tests that the constructor sets the correct values.
function test_constructor_succeeds() external { function test_constructor_succeeds() external {
assertEq(address(op.L2_ORACLE()), address(oracle)); assertEq(address(op.L2_ORACLE()), address(oracle));
...@@ -162,146 +169,83 @@ contract OptimismPortal_Test is Portal_Initializer { ...@@ -162,146 +169,83 @@ contract OptimismPortal_Test is Portal_Initializer {
assertTrue(op.minimumGasLimit(3) > op.minimumGasLimit(2)); assertTrue(op.minimumGasLimit(3) > op.minimumGasLimit(2));
} }
/// @dev Tests that `depositTransaction` succeeds for an EOA depositing a tx with 0 value. /// @dev Tests that `depositTransaction` succeeds for an EOA.
function test_depositTransaction_noValueEOA_succeeds() external { function testFuzz_depositTransaction_eoa_succeeds(
// EOA emulation address _to,
vm.prank(address(this), address(this)); uint64 _gasLimit,
vm.expectEmit(true, true, false, true); uint256 _value,
emitTransactionDeposited( uint256 _mint,
address(this), NON_ZERO_ADDRESS, ZERO_VALUE, ZERO_VALUE, NON_ZERO_GASLIMIT, false, NON_ZERO_DATA bool _isCreation,
); bytes memory _data
)
op.depositTransaction(NON_ZERO_ADDRESS, ZERO_VALUE, NON_ZERO_GASLIMIT, false, NON_ZERO_DATA); external
} {
_gasLimit = uint64(
/// @dev Tests that `depositTransaction` succeeds for a contract depositing a tx with 0 value. bound(_gasLimit, op.minimumGasLimit(uint64(_data.length)), systemConfig.resourceConfig().maxResourceLimit)
function test_depositTransaction_noValueContract_succeeds() external {
vm.expectEmit(true, true, false, true);
emitTransactionDeposited(
AddressAliasHelper.applyL1ToL2Alias(address(this)),
NON_ZERO_ADDRESS,
ZERO_VALUE,
ZERO_VALUE,
NON_ZERO_GASLIMIT,
false,
NON_ZERO_DATA
);
op.depositTransaction(NON_ZERO_ADDRESS, ZERO_VALUE, NON_ZERO_GASLIMIT, false, NON_ZERO_DATA);
}
/// @dev Tests that `depositTransaction` succeeds for an EOA
/// depositing a contract creation with 0 value.
function test_depositTransaction_createWithZeroValueForEOA_succeeds() external {
// EOA emulation
vm.prank(address(this), address(this));
vm.expectEmit(true, true, false, true);
emitTransactionDeposited(
address(this), ZERO_ADDRESS, ZERO_VALUE, ZERO_VALUE, NON_ZERO_GASLIMIT, true, NON_ZERO_DATA
);
op.depositTransaction(ZERO_ADDRESS, ZERO_VALUE, NON_ZERO_GASLIMIT, true, NON_ZERO_DATA);
}
/// @dev Tests that `depositTransaction` succeeds for a contract
/// depositing a contract creation with 0 value.
function test_depositTransaction_createWithZeroValueForContract_succeeds() external {
vm.expectEmit(true, true, false, true);
emitTransactionDeposited(
AddressAliasHelper.applyL1ToL2Alias(address(this)),
ZERO_ADDRESS,
ZERO_VALUE,
ZERO_VALUE,
NON_ZERO_GASLIMIT,
true,
NON_ZERO_DATA
);
op.depositTransaction(ZERO_ADDRESS, ZERO_VALUE, NON_ZERO_GASLIMIT, true, NON_ZERO_DATA);
}
/// @dev Tests that `depositTransaction` succeeds for an EOA depositing a tx with ETH.
function test_depositTransaction_withEthValueFromEOA_succeeds() external {
// EOA emulation
vm.prank(address(this), address(this));
vm.expectEmit(true, true, false, true);
emitTransactionDeposited(
address(this), NON_ZERO_ADDRESS, NON_ZERO_VALUE, ZERO_VALUE, NON_ZERO_GASLIMIT, false, NON_ZERO_DATA
);
op.depositTransaction{ value: NON_ZERO_VALUE }(
NON_ZERO_ADDRESS, ZERO_VALUE, NON_ZERO_GASLIMIT, false, NON_ZERO_DATA
);
assertEq(address(op).balance, NON_ZERO_VALUE);
}
/// @dev Tests that `depositTransaction` succeeds for a contract depositing a tx with ETH.
function test_depositTransaction_withEthValueFromContract_succeeds() external {
vm.expectEmit(true, true, false, true);
emitTransactionDeposited(
AddressAliasHelper.applyL1ToL2Alias(address(this)),
NON_ZERO_ADDRESS,
NON_ZERO_VALUE,
ZERO_VALUE,
NON_ZERO_GASLIMIT,
false,
NON_ZERO_DATA
);
op.depositTransaction{ value: NON_ZERO_VALUE }(
NON_ZERO_ADDRESS, ZERO_VALUE, NON_ZERO_GASLIMIT, false, NON_ZERO_DATA
); );
} if (_isCreation) _to = address(0);
/// @dev Tests that `depositTransaction` succeeds for an EOA depositing a contract creation with ETH.
function test_depositTransaction_withEthValueAndEOAContractCreation_succeeds() external {
// EOA emulation // EOA emulation
vm.prank(address(this), address(this)); vm.expectEmit(address(op));
emitTransactionDeposited({
vm.expectEmit(true, true, false, true); _from: depositor,
emitTransactionDeposited( _to: _to,
address(this), ZERO_ADDRESS, NON_ZERO_VALUE, ZERO_VALUE, NON_ZERO_GASLIMIT, true, hex"" _value: _value,
); _mint: _mint,
_gasLimit: _gasLimit,
op.depositTransaction{ value: NON_ZERO_VALUE }(ZERO_ADDRESS, ZERO_VALUE, NON_ZERO_GASLIMIT, true, hex""); _isCreation: _isCreation,
assertEq(address(op).balance, NON_ZERO_VALUE); _data: _data
} });
/// @dev Tests that `depositTransaction` succeeds for a contract depositing a contract creation with ETH.
function test_depositTransaction_withEthValueAndContractContractCreation_succeeds() external {
vm.expectEmit(true, true, false, true);
emitTransactionDeposited(
AddressAliasHelper.applyL1ToL2Alias(address(this)),
ZERO_ADDRESS,
NON_ZERO_VALUE,
ZERO_VALUE,
NON_ZERO_GASLIMIT,
true,
NON_ZERO_DATA
);
op.depositTransaction{ value: NON_ZERO_VALUE }(ZERO_ADDRESS, ZERO_VALUE, NON_ZERO_GASLIMIT, true, NON_ZERO_DATA); vm.deal(depositor, _mint);
assertEq(address(op).balance, NON_ZERO_VALUE); vm.prank(depositor, depositor);
op.depositTransaction{ value: _mint }({
_to: _to,
_value: _value,
_gasLimit: _gasLimit,
_isCreation: _isCreation,
_data: _data
});
assertEq(address(op).balance, _mint);
} }
/// @dev Tests that `isOutputFinalized` succeeds for an EOA depositing a tx with ETH and data. /// @dev Tests that `depositTransaction` succeeds for a contract.
function test_simple_isOutputFinalized_succeeds() external { function testFuzz_depositTransaction_contract_succeeds(
uint256 ts = block.timestamp; address _to,
vm.mockCall( uint64 _gasLimit,
address(op.L2_ORACLE()), uint256 _value,
abi.encodeWithSelector(L2OutputOracle.getL2Output.selector), uint256 _mint,
abi.encode(Types.OutputProposal(bytes32(uint256(1)), uint128(ts), uint128(startingBlockNumber))) bool _isCreation,
bytes memory _data
)
external
{
_gasLimit = uint64(
bound(_gasLimit, op.minimumGasLimit(uint64(_data.length)), systemConfig.resourceConfig().maxResourceLimit)
); );
if (_isCreation) _to = address(0);
vm.expectEmit(address(op));
emitTransactionDeposited({
_from: AddressAliasHelper.applyL1ToL2Alias(address(this)),
_to: _to,
_value: _value,
_mint: _mint,
_gasLimit: _gasLimit,
_isCreation: _isCreation,
_data: _data
});
// warp to the finalization period vm.deal(address(this), _mint);
vm.warp(ts + oracle.FINALIZATION_PERIOD_SECONDS()); vm.prank(address(this));
assertEq(op.isOutputFinalized(0), false); op.depositTransaction{ value: _mint }({
_to: _to,
// warp past the finalization period _value: _value,
vm.warp(ts + oracle.FINALIZATION_PERIOD_SECONDS() + 1); _gasLimit: _gasLimit,
assertEq(op.isOutputFinalized(0), true); _isCreation: _isCreation,
_data: _data
});
assertEq(address(op).balance, _mint);
} }
/// @dev Tests `isOutputFinalized` for a finalized output. /// @dev Tests `isOutputFinalized` for a finalized output.
......
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { stdError } from "forge-std/Test.sol"; import { stdError } from "forge-std/Test.sol";
import { CommonTest } from "test/CommonTest.t.sol"; import { Test } from "forge-std/Test.sol";
import { RLPReader } from "src/libraries/rlp/RLPReader.sol"; import { RLPReader } from "src/libraries/rlp/RLPReader.sol";
contract RLPReader_readBytes_Test is CommonTest { contract RLPReader_readBytes_Test is Test {
function test_readBytes_bytestring00_succeeds() external { function test_readBytes_bytestring00_succeeds() external {
assertEq(RLPReader.readBytes(hex"00"), hex"00"); assertEq(RLPReader.readBytes(hex"00"), hex"00");
} }
...@@ -44,7 +44,7 @@ contract RLPReader_readBytes_Test is CommonTest { ...@@ -44,7 +44,7 @@ contract RLPReader_readBytes_Test is CommonTest {
} }
} }
contract RLPReader_readList_Test is CommonTest { contract RLPReader_readList_Test is Test {
function test_readList_empty_succeeds() external { function test_readList_empty_succeeds() external {
RLPReader.RLPItem[] memory list = RLPReader.readList(hex"c0"); RLPReader.RLPItem[] memory list = RLPReader.readList(hex"c0");
assertEq(list.length, 0); assertEq(list.length, 0);
......
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { RLPWriter } from "src/libraries/rlp/RLPWriter.sol"; import { RLPWriter } from "src/libraries/rlp/RLPWriter.sol";
import { CommonTest } from "test/CommonTest.t.sol"; import { Test } from "forge-std/Test.sol";
contract RLPWriter_writeString_Test is CommonTest { contract RLPWriter_writeString_Test is Test {
function test_writeString_empty_succeeds() external { function test_writeString_empty_succeeds() external {
assertEq(RLPWriter.writeString(""), hex"80"); assertEq(RLPWriter.writeString(""), hex"80");
} }
...@@ -49,7 +49,7 @@ contract RLPWriter_writeString_Test is CommonTest { ...@@ -49,7 +49,7 @@ contract RLPWriter_writeString_Test is CommonTest {
} }
} }
contract RLPWriter_writeUint_Test is CommonTest { contract RLPWriter_writeUint_Test is Test {
function test_writeUint_zero_succeeds() external { function test_writeUint_zero_succeeds() external {
assertEq(RLPWriter.writeUint(0x0), hex"80"); assertEq(RLPWriter.writeUint(0x0), hex"80");
} }
...@@ -83,7 +83,7 @@ contract RLPWriter_writeUint_Test is CommonTest { ...@@ -83,7 +83,7 @@ contract RLPWriter_writeUint_Test is CommonTest {
} }
} }
contract RLPWriter_writeList_Test is CommonTest { contract RLPWriter_writeList_Test is Test {
function test_writeList_empty_succeeds() external { function test_writeList_empty_succeeds() external {
assertEq(RLPWriter.writeList(new bytes[](0)), hex"c0"); assertEq(RLPWriter.writeList(new bytes[](0)), hex"c0");
} }
......
...@@ -40,7 +40,7 @@ contract SafeSigners_Test is Test, SafeTestTools { ...@@ -40,7 +40,7 @@ contract SafeSigners_Test is Test, SafeTestTools {
uint256 numSigs = bound(_numSigs, 1, 25); uint256 numSigs = bound(_numSigs, 1, 25);
(, uint256[] memory keys) = SafeTestLib.makeAddrsAndKeys("getSigsTest", numSigs); (, uint256[] memory keys) = SafeTestLib.makeAddrsAndKeys("getSigsTest", numSigs);
for (uint256 i = 0; i < keys.length; i++) { for (uint256 i; i < keys.length; i++) {
if (sigType(keys[i]) == SigTypes.Contract) { if (sigType(keys[i]) == SigTypes.Contract) {
keys[i] = keys[i] =
SafeTestLib.encodeSmartContractWalletAsPK(SafeTestLib.decodeSmartContractWalletAsAddress(keys[i])); SafeTestLib.encodeSmartContractWalletAsPK(SafeTestLib.decodeSmartContractWalletAsAddress(keys[i]));
...@@ -91,7 +91,7 @@ contract SafeSigners_Test is Test, SafeTestTools { ...@@ -91,7 +91,7 @@ contract SafeSigners_Test is Test, SafeTestTools {
// the validation checks that the Safe contract performs on the value of s on contract // the validation checks that the Safe contract performs on the value of s on contract
// signatures. The Safe contract checks that s correctly points to additional data appended // signatures. The Safe contract checks that s correctly points to additional data appended
// after the signatures, and that the length of the data is within bounds. // after the signatures, and that the length of the data is within bounds.
for (uint256 i = 0; i < contractSigs; i++) { for (uint256 i; i < contractSigs; i++) {
signatures = bytes.concat(signatures, abi.encode(32, 1)); signatures = bytes.concat(signatures, abi.encode(32, 1));
} }
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity ^0.8.0;
import { Vm } from "forge-std/Vm.sol"; import { Vm } from "forge-std/Vm.sol";
import { Constants } from "src/libraries/Constants.sol"; import { Constants } from "src/libraries/Constants.sol";
......
...@@ -212,17 +212,10 @@ library SafeTestLib { ...@@ -212,17 +212,10 @@ library SafeTestLib {
address[] memory _ownersList address[] memory _ownersList
) )
internal internal
view pure
returns ( returns (address prevOwner_)
// pure
address prevOwner_
)
{ {
// console.log("getPrevOwnerFromList"); for (uint256 i; i < _ownersList.length; i++) {
for (uint256 i = 0; i < _ownersList.length; i++) {
// console.log(i);
// console.log(_owner);
// console.log("_ownersList[i]:", _ownersList[i]);
if (_ownersList[i] != _owner) continue; if (_ownersList[i] != _owner) continue;
if (i == 0) { if (i == 0) {
prevOwner_ = SENTINEL_OWNERS; prevOwner_ = SENTINEL_OWNERS;
...@@ -230,8 +223,6 @@ library SafeTestLib { ...@@ -230,8 +223,6 @@ library SafeTestLib {
} }
prevOwner_ = _ownersList[i - 1]; prevOwner_ = _ownersList[i - 1];
} }
console.log("prevOwner_:", prevOwner_);
} }
/// @dev Given an array of owners to remove, this function will return an array of the previous owners /// @dev Given an array of owners to remove, this function will return an array of the previous owners
...@@ -250,7 +241,7 @@ library SafeTestLib { ...@@ -250,7 +241,7 @@ library SafeTestLib {
OwnerSimulator ownerSimulator = new OwnerSimulator(instance.owners, 1); OwnerSimulator ownerSimulator = new OwnerSimulator(instance.owners, 1);
prevOwners_ = new address[](_ownersToRemove.length); prevOwners_ = new address[](_ownersToRemove.length);
address[] memory currentOwners; address[] memory currentOwners;
for (uint256 i = 0; i < _ownersToRemove.length; i++) { for (uint256 i; i < _ownersToRemove.length; i++) {
currentOwners = ownerSimulator.getOwners(); currentOwners = ownerSimulator.getOwners();
prevOwners_[i] = SafeTestLib.getPrevOwnerFromList(_ownersToRemove[i], currentOwners); prevOwners_[i] = SafeTestLib.getPrevOwnerFromList(_ownersToRemove[i], currentOwners);
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { Types } from "src/libraries/Types.sol";
import { Vm } from "forge-std/Vm.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
/// @title FFIInterface
contract FFIInterface {
Vm internal constant vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
function getProveWithdrawalTransactionInputs(Types.WithdrawalTransaction memory _tx)
external
returns (bytes32, bytes32, bytes32, bytes32, bytes[] memory)
{
string[] memory cmds = new string[](9);
cmds[0] = "scripts/go-ffi/go-ffi";
cmds[1] = "diff";
cmds[2] = "getProveWithdrawalTransactionInputs";
cmds[3] = vm.toString(_tx.nonce);
cmds[4] = vm.toString(_tx.sender);
cmds[5] = vm.toString(_tx.target);
cmds[6] = vm.toString(_tx.value);
cmds[7] = vm.toString(_tx.gasLimit);
cmds[8] = vm.toString(_tx.data);
bytes memory result = vm.ffi(cmds);
(
bytes32 stateRoot,
bytes32 storageRoot,
bytes32 outputRoot,
bytes32 withdrawalHash,
bytes[] memory withdrawalProof
) = abi.decode(result, (bytes32, bytes32, bytes32, bytes32, bytes[]));
return (stateRoot, storageRoot, outputRoot, withdrawalHash, withdrawalProof);
}
function hashCrossDomainMessage(
uint256 _nonce,
address _sender,
address _target,
uint256 _value,
uint256 _gasLimit,
bytes memory _data
)
external
returns (bytes32)
{
string[] memory cmds = new string[](9);
cmds[0] = "scripts/go-ffi/go-ffi";
cmds[1] = "diff";
cmds[2] = "hashCrossDomainMessage";
cmds[3] = vm.toString(_nonce);
cmds[4] = vm.toString(_sender);
cmds[5] = vm.toString(_target);
cmds[6] = vm.toString(_value);
cmds[7] = vm.toString(_gasLimit);
cmds[8] = vm.toString(_data);
bytes memory result = vm.ffi(cmds);
return abi.decode(result, (bytes32));
}
function hashWithdrawal(
uint256 _nonce,
address _sender,
address _target,
uint256 _value,
uint256 _gasLimit,
bytes memory _data
)
external
returns (bytes32)
{
string[] memory cmds = new string[](9);
cmds[0] = "scripts/go-ffi/go-ffi";
cmds[1] = "diff";
cmds[2] = "hashWithdrawal";
cmds[3] = vm.toString(_nonce);
cmds[4] = vm.toString(_sender);
cmds[5] = vm.toString(_target);
cmds[6] = vm.toString(_value);
cmds[7] = vm.toString(_gasLimit);
cmds[8] = vm.toString(_data);
bytes memory result = vm.ffi(cmds);
return abi.decode(result, (bytes32));
}
function hashOutputRootProof(
bytes32 _version,
bytes32 _stateRoot,
bytes32 _messagePasserStorageRoot,
bytes32 _latestBlockhash
)
external
returns (bytes32)
{
string[] memory cmds = new string[](7);
cmds[0] = "scripts/go-ffi/go-ffi";
cmds[1] = "diff";
cmds[2] = "hashOutputRootProof";
cmds[3] = Strings.toHexString(uint256(_version));
cmds[4] = Strings.toHexString(uint256(_stateRoot));
cmds[5] = Strings.toHexString(uint256(_messagePasserStorageRoot));
cmds[6] = Strings.toHexString(uint256(_latestBlockhash));
bytes memory result = vm.ffi(cmds);
return abi.decode(result, (bytes32));
}
function hashDepositTransaction(
address _from,
address _to,
uint256 _mint,
uint256 _value,
uint64 _gas,
bytes memory _data,
uint64 _logIndex
)
external
returns (bytes32)
{
string[] memory cmds = new string[](11);
cmds[0] = "scripts/go-ffi/go-ffi";
cmds[1] = "diff";
cmds[2] = "hashDepositTransaction";
cmds[3] = "0x0000000000000000000000000000000000000000000000000000000000000000";
cmds[4] = vm.toString(_logIndex);
cmds[5] = vm.toString(_from);
cmds[6] = vm.toString(_to);
cmds[7] = vm.toString(_mint);
cmds[8] = vm.toString(_value);
cmds[9] = vm.toString(_gas);
cmds[10] = vm.toString(_data);
bytes memory result = vm.ffi(cmds);
return abi.decode(result, (bytes32));
}
function encodeDepositTransaction(Types.UserDepositTransaction calldata txn) external returns (bytes memory) {
string[] memory cmds = new string[](12);
cmds[0] = "scripts/go-ffi/go-ffi";
cmds[1] = "diff";
cmds[2] = "encodeDepositTransaction";
cmds[3] = vm.toString(txn.from);
cmds[4] = vm.toString(txn.to);
cmds[5] = vm.toString(txn.value);
cmds[6] = vm.toString(txn.mint);
cmds[7] = vm.toString(txn.gasLimit);
cmds[8] = vm.toString(txn.isCreation);
cmds[9] = vm.toString(txn.data);
cmds[10] = vm.toString(txn.l1BlockHash);
cmds[11] = vm.toString(txn.logIndex);
bytes memory result = vm.ffi(cmds);
return abi.decode(result, (bytes));
}
function encodeCrossDomainMessage(
uint256 _nonce,
address _sender,
address _target,
uint256 _value,
uint256 _gasLimit,
bytes memory _data
)
external
returns (bytes memory)
{
string[] memory cmds = new string[](9);
cmds[0] = "scripts/go-ffi/go-ffi";
cmds[1] = "diff";
cmds[2] = "encodeCrossDomainMessage";
cmds[3] = vm.toString(_nonce);
cmds[4] = vm.toString(_sender);
cmds[5] = vm.toString(_target);
cmds[6] = vm.toString(_value);
cmds[7] = vm.toString(_gasLimit);
cmds[8] = vm.toString(_data);
bytes memory result = vm.ffi(cmds);
return abi.decode(result, (bytes));
}
function decodeVersionedNonce(uint256 nonce) external returns (uint256, uint256) {
string[] memory cmds = new string[](4);
cmds[0] = "scripts/go-ffi/go-ffi";
cmds[1] = "diff";
cmds[2] = "decodeVersionedNonce";
cmds[3] = vm.toString(nonce);
bytes memory result = vm.ffi(cmds);
return abi.decode(result, (uint256, uint256));
}
function getMerkleTrieFuzzCase(string memory variant)
external
returns (bytes32, bytes memory, bytes memory, bytes[] memory)
{
string[] memory cmds = new string[](6);
cmds[0] = "./scripts/go-ffi/go-ffi";
cmds[1] = "trie";
cmds[2] = variant;
return abi.decode(vm.ffi(cmds), (bytes32, bytes, bytes, bytes[]));
}
function getCannonMemoryProof(uint32 pc, uint32 insn) external returns (bytes32, bytes memory) {
string[] memory cmds = new string[](5);
cmds[0] = "scripts/go-ffi/go-ffi";
cmds[1] = "diff";
cmds[2] = "cannonMemoryProof";
cmds[3] = vm.toString(pc);
cmds[4] = vm.toString(insn);
bytes memory result = vm.ffi(cmds);
(bytes32 memRoot, bytes memory proof) = abi.decode(result, (bytes32, bytes));
return (memRoot, proof);
}
function getCannonMemoryProof(
uint32 pc,
uint32 insn,
uint32 memAddr,
uint32 memVal
)
external
returns (bytes32, bytes memory)
{
string[] memory cmds = new string[](7);
cmds[0] = "scripts/go-ffi/go-ffi";
cmds[1] = "diff";
cmds[2] = "cannonMemoryProof";
cmds[3] = vm.toString(pc);
cmds[4] = vm.toString(insn);
cmds[5] = vm.toString(memAddr);
cmds[6] = vm.toString(memVal);
bytes memory result = vm.ffi(cmds);
(bytes32 memRoot, bytes memory proof) = abi.decode(result, (bytes32, bytes));
return (memRoot, proof);
}
}
...@@ -82,6 +82,6 @@ ...@@ -82,6 +82,6 @@
"change-case": "4.1.2", "change-case": "4.1.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"viem": "^1.18.1" "viem": "^1.18.4"
} }
} }
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
"jsdom": "^22.1.0", "jsdom": "^22.1.0",
"tsup": "^7.2.0", "tsup": "^7.2.0",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"viem": "^1.18.1", "viem": "^1.18.4",
"vite": "^4.5.0", "vite": "^4.5.0",
"vitest": "^0.34.2" "vitest": "^0.34.2"
}, },
......
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
"ethereum-waffle": "^4.0.10", "ethereum-waffle": "^4.0.10",
"ethers": "^5.7.2", "ethers": "^5.7.2",
"hardhat": "^2.18.3", "hardhat": "^2.19.0",
"hardhat-deploy": "^0.11.43", "hardhat-deploy": "^0.11.43",
"isomorphic-fetch": "^3.0.0", "isomorphic-fetch": "^3.0.0",
"mocha": "^10.2.0", "mocha": "^10.2.0",
...@@ -56,7 +56,7 @@ ...@@ -56,7 +56,7 @@
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typedoc": "^0.25.3", "typedoc": "^0.25.3",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"viem": "^1.18.1", "viem": "^1.18.4",
"vitest": "^0.34.2", "vitest": "^0.34.2",
"zod": "^3.22.4" "zod": "^3.22.4"
}, },
......
...@@ -37,13 +37,13 @@ ...@@ -37,13 +37,13 @@
"@vitest/coverage-istanbul": "^0.34.6", "@vitest/coverage-istanbul": "^0.34.6",
"tsup": "^7.2.0", "tsup": "^7.2.0",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"viem": "^1.18.1", "viem": "^1.18.4",
"vite": "^4.5.0", "vite": "^4.5.0",
"vitest": "^0.34.1", "vitest": "^0.34.1",
"zod": "^3.22.4" "zod": "^3.22.4"
}, },
"dependencies": { "dependencies": {
"@ethereumjs/rlp": "^5.0.0", "@ethereumjs/rlp": "^5.0.1",
"web3-eth": "^4.0.3", "web3-eth": "^4.0.3",
"web3-eth-accounts": "^4.0.3" "web3-eth-accounts": "^4.0.3"
}, },
......
...@@ -175,13 +175,13 @@ importers: ...@@ -175,13 +175,13 @@ importers:
version: 5.7.0 version: 5.7.0
'@nomiclabs/hardhat-ethers': '@nomiclabs/hardhat-ethers':
specifier: ^2.2.3 specifier: ^2.2.3
version: 2.2.3(ethers@5.7.2)(hardhat@2.18.3) version: 2.2.3(ethers@5.7.2)(hardhat@2.19.0)
'@nomiclabs/hardhat-waffle': '@nomiclabs/hardhat-waffle':
specifier: ^2.0.6 specifier: ^2.0.6
version: 2.0.6(@nomiclabs/hardhat-ethers@2.2.3)(@types/sinon-chai@3.2.5)(ethereum-waffle@4.0.10)(ethers@5.7.2)(hardhat@2.18.3) version: 2.0.6(@nomiclabs/hardhat-ethers@2.2.3)(@types/sinon-chai@3.2.5)(ethereum-waffle@4.0.10)(ethers@5.7.2)(hardhat@2.19.0)
hardhat: hardhat:
specifier: ^2.18.3 specifier: ^2.19.0
version: 2.18.3(ts-node@10.9.1)(typescript@5.2.2) version: 2.19.0(ts-node@10.9.1)(typescript@5.2.2)
ts-node: ts-node:
specifier: ^10.9.1 specifier: ^10.9.1
version: 10.9.1(@types/node@20.8.9)(typescript@5.2.2) version: 10.9.1(@types/node@20.8.9)(typescript@5.2.2)
...@@ -298,11 +298,11 @@ importers: ...@@ -298,11 +298,11 @@ importers:
specifier: ^18.2.0 specifier: ^18.2.0
version: 18.2.0(react@18.2.0) version: 18.2.0(react@18.2.0)
viem: viem:
specifier: ^1.18.1 specifier: ^1.18.4
version: 1.18.1(typescript@5.2.2)(zod@3.22.4) version: 1.18.4(typescript@5.2.2)(zod@3.22.4)
wagmi: wagmi:
specifier: '>1.0.0' specifier: '>1.0.0'
version: 1.0.1(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.18.1) version: 1.0.1(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.18.4)
devDependencies: devDependencies:
'@eth-optimism/contracts-bedrock': '@eth-optimism/contracts-bedrock':
specifier: workspace:* specifier: workspace:*
...@@ -324,7 +324,7 @@ importers: ...@@ -324,7 +324,7 @@ importers:
version: 1.5.2(@wagmi/core@1.4.5)(typescript@5.2.2)(wagmi@1.0.1) version: 1.5.2(@wagmi/core@1.4.5)(typescript@5.2.2)(wagmi@1.0.1)
'@wagmi/core': '@wagmi/core':
specifier: ^1.4.5 specifier: ^1.4.5
version: 1.4.5(react@18.2.0)(typescript@5.2.2)(viem@1.18.1) version: 1.4.5(react@18.2.0)(typescript@5.2.2)(viem@1.18.4)
abitype: abitype:
specifier: ^0.10.2 specifier: ^0.10.2
version: 0.10.2(typescript@5.2.2) version: 0.10.2(typescript@5.2.2)
...@@ -438,8 +438,8 @@ importers: ...@@ -438,8 +438,8 @@ importers:
specifier: ^5.2.2 specifier: ^5.2.2
version: 5.2.2 version: 5.2.2
viem: viem:
specifier: ^1.18.1 specifier: ^1.18.4
version: 1.18.1(typescript@5.2.2)(zod@3.22.4) version: 1.18.4(typescript@5.2.2)(zod@3.22.4)
vite: vite:
specifier: ^4.5.0 specifier: ^4.5.0
version: 4.5.0(@types/node@20.8.9) version: 4.5.0(@types/node@20.8.9)
...@@ -479,10 +479,10 @@ importers: ...@@ -479,10 +479,10 @@ importers:
version: 5.7.0 version: 5.7.0
'@nomiclabs/hardhat-ethers': '@nomiclabs/hardhat-ethers':
specifier: ^2.2.3 specifier: ^2.2.3
version: 2.2.3(ethers@5.7.2)(hardhat@2.18.3) version: 2.2.3(ethers@5.7.2)(hardhat@2.19.0)
'@nomiclabs/hardhat-waffle': '@nomiclabs/hardhat-waffle':
specifier: ^2.0.1 specifier: ^2.0.1
version: 2.0.1(@nomiclabs/hardhat-ethers@2.2.3)(ethereum-waffle@4.0.10)(ethers@5.7.2)(hardhat@2.18.3) version: 2.0.1(@nomiclabs/hardhat-ethers@2.2.3)(ethereum-waffle@4.0.10)(ethers@5.7.2)(hardhat@2.19.0)
'@types/chai': '@types/chai':
specifier: ^4.3.8 specifier: ^4.3.8
version: 4.3.8 version: 4.3.8
...@@ -505,8 +505,8 @@ importers: ...@@ -505,8 +505,8 @@ importers:
specifier: ^5.7.2 specifier: ^5.7.2
version: 5.7.2 version: 5.7.2
hardhat: hardhat:
specifier: ^2.18.3 specifier: ^2.19.0
version: 2.18.3(ts-node@10.9.1)(typescript@5.2.2) version: 2.19.0(ts-node@10.9.1)(typescript@5.2.2)
hardhat-deploy: hardhat-deploy:
specifier: ^0.11.43 specifier: ^0.11.43
version: 0.11.43 version: 0.11.43
...@@ -529,8 +529,8 @@ importers: ...@@ -529,8 +529,8 @@ importers:
specifier: ^5.2.2 specifier: ^5.2.2
version: 5.2.2 version: 5.2.2
viem: viem:
specifier: ^1.18.1 specifier: ^1.18.4
version: 1.18.1(typescript@5.2.2)(zod@3.22.4) version: 1.18.4(typescript@5.2.2)(zod@3.22.4)
vitest: vitest:
specifier: ^0.34.2 specifier: ^0.34.2
version: 0.34.2 version: 0.34.2
...@@ -541,8 +541,8 @@ importers: ...@@ -541,8 +541,8 @@ importers:
packages/web3js-plugin: packages/web3js-plugin:
dependencies: dependencies:
'@ethereumjs/rlp': '@ethereumjs/rlp':
specifier: ^5.0.0 specifier: ^5.0.1
version: 5.0.0 version: 5.0.1
web3: web3:
specifier: '>= 4.0.3 < 5.x' specifier: '>= 4.0.3 < 5.x'
version: 4.0.3 version: 4.0.3
...@@ -569,8 +569,8 @@ importers: ...@@ -569,8 +569,8 @@ importers:
specifier: ^5.2.2 specifier: ^5.2.2
version: 5.2.2 version: 5.2.2
viem: viem:
specifier: ^1.18.1 specifier: ^1.18.4
version: 1.18.1(typescript@5.2.2)(zod@3.22.4) version: 1.18.4(typescript@5.2.2)(zod@3.22.4)
vite: vite:
specifier: ^4.5.0 specifier: ^4.5.0
version: 4.5.0(@types/node@20.8.9) version: 4.5.0(@types/node@20.8.9)
...@@ -2014,8 +2014,8 @@ packages: ...@@ -2014,8 +2014,8 @@ packages:
engines: {node: '>=14'} engines: {node: '>=14'}
hasBin: true hasBin: true
/@ethereumjs/rlp@5.0.0: /@ethereumjs/rlp@5.0.1:
resolution: {integrity: sha512-WuS1l7GJmB0n0HsXLozCoEFc9IwYgf3l0gCkKVYgR67puVF1O4OpEaN0hWmm1c+iHUHFCKt1hJrvy5toLg+6ag==} resolution: {integrity: sha512-Ab/Hfzz+T9Zl+65Nkg+9xAmwKPLicsnQ4NW49pgvJp9ovefuic95cgOS9CbPc9izIEgsqm1UitV0uNveCvud9w==}
engines: {node: '>=18'} engines: {node: '>=18'}
hasBin: true hasBin: true
dev: false dev: false
...@@ -2947,17 +2947,17 @@ packages: ...@@ -2947,17 +2947,17 @@ packages:
'@nomicfoundation/solidity-analyzer-win32-x64-msvc': 0.1.1 '@nomicfoundation/solidity-analyzer-win32-x64-msvc': 0.1.1
dev: true dev: true
/@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.18.3): /@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.19.0):
resolution: {integrity: sha512-YhzPdzb612X591FOe68q+qXVXGG2ANZRvDo0RRUtimev85rCrAlv/TLMEZw5c+kq9AbzocLTVX/h2jVIFPL9Xg==} resolution: {integrity: sha512-YhzPdzb612X591FOe68q+qXVXGG2ANZRvDo0RRUtimev85rCrAlv/TLMEZw5c+kq9AbzocLTVX/h2jVIFPL9Xg==}
peerDependencies: peerDependencies:
ethers: ^5.0.0 ethers: ^5.0.0
hardhat: ^2.0.0 hardhat: ^2.0.0
dependencies: dependencies:
ethers: 5.7.2 ethers: 5.7.2
hardhat: 2.18.3(ts-node@10.9.1)(typescript@5.2.2) hardhat: 2.19.0(ts-node@10.9.1)(typescript@5.2.2)
dev: true dev: true
/@nomiclabs/hardhat-waffle@2.0.1(@nomiclabs/hardhat-ethers@2.2.3)(ethereum-waffle@4.0.10)(ethers@5.7.2)(hardhat@2.18.3): /@nomiclabs/hardhat-waffle@2.0.1(@nomiclabs/hardhat-ethers@2.2.3)(ethereum-waffle@4.0.10)(ethers@5.7.2)(hardhat@2.19.0):
resolution: {integrity: sha512-2YR2V5zTiztSH9n8BYWgtv3Q+EL0N5Ltm1PAr5z20uAY4SkkfylJ98CIqt18XFvxTD5x4K2wKBzddjV9ViDAZQ==} resolution: {integrity: sha512-2YR2V5zTiztSH9n8BYWgtv3Q+EL0N5Ltm1PAr5z20uAY4SkkfylJ98CIqt18XFvxTD5x4K2wKBzddjV9ViDAZQ==}
peerDependencies: peerDependencies:
'@nomiclabs/hardhat-ethers': ^2.0.0 '@nomiclabs/hardhat-ethers': ^2.0.0
...@@ -2965,15 +2965,15 @@ packages: ...@@ -2965,15 +2965,15 @@ packages:
ethers: ^5.0.0 ethers: ^5.0.0
hardhat: ^2.0.0 hardhat: ^2.0.0
dependencies: dependencies:
'@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.18.3) '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.19.0)
'@types/sinon-chai': 3.2.5 '@types/sinon-chai': 3.2.5
'@types/web3': 1.0.19 '@types/web3': 1.0.19
ethereum-waffle: 4.0.10(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typescript@5.2.2) ethereum-waffle: 4.0.10(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typescript@5.2.2)
ethers: 5.7.2 ethers: 5.7.2
hardhat: 2.18.3(ts-node@10.9.1)(typescript@5.2.2) hardhat: 2.19.0(ts-node@10.9.1)(typescript@5.2.2)
dev: true dev: true
/@nomiclabs/hardhat-waffle@2.0.6(@nomiclabs/hardhat-ethers@2.2.3)(@types/sinon-chai@3.2.5)(ethereum-waffle@4.0.10)(ethers@5.7.2)(hardhat@2.18.3): /@nomiclabs/hardhat-waffle@2.0.6(@nomiclabs/hardhat-ethers@2.2.3)(@types/sinon-chai@3.2.5)(ethereum-waffle@4.0.10)(ethers@5.7.2)(hardhat@2.19.0):
resolution: {integrity: sha512-+Wz0hwmJGSI17B+BhU/qFRZ1l6/xMW82QGXE/Gi+WTmwgJrQefuBs1lIf7hzQ1hLk6hpkvb/zwcNkpVKRYTQYg==} resolution: {integrity: sha512-+Wz0hwmJGSI17B+BhU/qFRZ1l6/xMW82QGXE/Gi+WTmwgJrQefuBs1lIf7hzQ1hLk6hpkvb/zwcNkpVKRYTQYg==}
peerDependencies: peerDependencies:
'@nomiclabs/hardhat-ethers': ^2.0.0 '@nomiclabs/hardhat-ethers': ^2.0.0
...@@ -2982,11 +2982,11 @@ packages: ...@@ -2982,11 +2982,11 @@ packages:
ethers: ^5.0.0 ethers: ^5.0.0
hardhat: ^2.0.0 hardhat: ^2.0.0
dependencies: dependencies:
'@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.18.3) '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.19.0)
'@types/sinon-chai': 3.2.5 '@types/sinon-chai': 3.2.5
ethereum-waffle: 4.0.10(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typescript@5.2.2) ethereum-waffle: 4.0.10(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typescript@5.2.2)
ethers: 5.7.2 ethers: 5.7.2
hardhat: 2.18.3(ts-node@10.9.1)(typescript@5.2.2) hardhat: 2.19.0(ts-node@10.9.1)(typescript@5.2.2)
dev: true dev: true
/@nrwl/nx-cloud@16.5.2: /@nrwl/nx-cloud@16.5.2:
...@@ -3197,7 +3197,7 @@ packages: ...@@ -3197,7 +3197,7 @@ packages:
resolution: {integrity: sha512-gYw0ki/EAuV1oSyMxpqandHjnthZjYYy+YWpTAzf8BqfXM3ItcZLpjxfg+3+mXW8HIO+3jw6T9iiqEXsqHaMMw==} resolution: {integrity: sha512-gYw0ki/EAuV1oSyMxpqandHjnthZjYYy+YWpTAzf8BqfXM3ItcZLpjxfg+3+mXW8HIO+3jw6T9iiqEXsqHaMMw==}
dependencies: dependencies:
'@safe-global/safe-gateway-typescript-sdk': 3.7.3 '@safe-global/safe-gateway-typescript-sdk': 3.7.3
viem: 1.18.1(typescript@5.2.2)(zod@3.22.4) viem: 1.18.4(typescript@5.2.2)(zod@3.22.4)
transitivePeerDependencies: transitivePeerDependencies:
- bufferutil - bufferutil
- encoding - encoding
...@@ -4562,7 +4562,7 @@ packages: ...@@ -4562,7 +4562,7 @@ packages:
wagmi: wagmi:
optional: true optional: true
dependencies: dependencies:
'@wagmi/core': 1.4.5(react@18.2.0)(typescript@5.2.2)(viem@1.18.1) '@wagmi/core': 1.4.5(react@18.2.0)(typescript@5.2.2)(viem@1.18.4)
abitype: 0.8.7(typescript@5.2.2)(zod@3.22.3) abitype: 0.8.7(typescript@5.2.2)(zod@3.22.3)
abort-controller: 3.0.0 abort-controller: 3.0.0
bundle-require: 3.1.2(esbuild@0.16.17) bundle-require: 3.1.2(esbuild@0.16.17)
...@@ -4584,15 +4584,15 @@ packages: ...@@ -4584,15 +4584,15 @@ packages:
picocolors: 1.0.0 picocolors: 1.0.0
prettier: 2.8.8 prettier: 2.8.8
typescript: 5.2.2 typescript: 5.2.2
viem: 1.18.1(typescript@5.2.2)(zod@3.22.3) viem: 1.18.4(typescript@5.2.2)(zod@3.22.3)
wagmi: 1.0.1(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.18.1) wagmi: 1.0.1(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.18.4)
zod: 3.22.3 zod: 3.22.3
transitivePeerDependencies: transitivePeerDependencies:
- bufferutil - bufferutil
- utf-8-validate - utf-8-validate
dev: true dev: true
/@wagmi/connectors@1.0.1(@wagmi/chains@0.2.22)(react@18.2.0)(typescript@5.2.2)(viem@1.18.1): /@wagmi/connectors@1.0.1(@wagmi/chains@0.2.22)(react@18.2.0)(typescript@5.2.2)(viem@1.18.4):
resolution: {integrity: sha512-fl01vym19DE1uoE+MlASw5zo3Orr/YXlJRjOKLaKYtV+Q7jOLY4TwHgq7sEMs+JYOvFICFBEAlWNNxidr51AqQ==} resolution: {integrity: sha512-fl01vym19DE1uoE+MlASw5zo3Orr/YXlJRjOKLaKYtV+Q7jOLY4TwHgq7sEMs+JYOvFICFBEAlWNNxidr51AqQ==}
peerDependencies: peerDependencies:
'@wagmi/chains': '>=0.2.0' '@wagmi/chains': '>=0.2.0'
...@@ -4615,7 +4615,7 @@ packages: ...@@ -4615,7 +4615,7 @@ packages:
abitype: 0.8.1(typescript@5.2.2) abitype: 0.8.1(typescript@5.2.2)
eventemitter3: 4.0.7 eventemitter3: 4.0.7
typescript: 5.2.2 typescript: 5.2.2
viem: 1.18.1(typescript@5.2.2)(zod@3.22.4) viem: 1.18.4(typescript@5.2.2)(zod@3.22.4)
transitivePeerDependencies: transitivePeerDependencies:
- '@react-native-async-storage/async-storage' - '@react-native-async-storage/async-storage'
- bufferutil - bufferutil
...@@ -4627,7 +4627,7 @@ packages: ...@@ -4627,7 +4627,7 @@ packages:
- utf-8-validate - utf-8-validate
- zod - zod
/@wagmi/connectors@3.1.3(react@18.2.0)(typescript@5.2.2)(viem@1.18.1): /@wagmi/connectors@3.1.3(react@18.2.0)(typescript@5.2.2)(viem@1.18.4):
resolution: {integrity: sha512-UgwsQKQDFObJVJMf9pDfFoXTv710o4zrTHyhIWKBTMMkLpCMsMxN5+ZaDhBYt/BgoRinfRYQo8uwuwLhxE6Log==} resolution: {integrity: sha512-UgwsQKQDFObJVJMf9pDfFoXTv710o4zrTHyhIWKBTMMkLpCMsMxN5+ZaDhBYt/BgoRinfRYQo8uwuwLhxE6Log==}
peerDependencies: peerDependencies:
typescript: '>=5.0.4' typescript: '>=5.0.4'
...@@ -4647,7 +4647,7 @@ packages: ...@@ -4647,7 +4647,7 @@ packages:
abitype: 0.8.7(typescript@5.2.2)(zod@3.22.3) abitype: 0.8.7(typescript@5.2.2)(zod@3.22.3)
eventemitter3: 4.0.7 eventemitter3: 4.0.7
typescript: 5.2.2 typescript: 5.2.2
viem: 1.18.1(typescript@5.2.2)(zod@3.22.4) viem: 1.18.4(typescript@5.2.2)(zod@3.22.4)
transitivePeerDependencies: transitivePeerDependencies:
- '@react-native-async-storage/async-storage' - '@react-native-async-storage/async-storage'
- '@types/react' - '@types/react'
...@@ -4660,7 +4660,7 @@ packages: ...@@ -4660,7 +4660,7 @@ packages:
- zod - zod
dev: true dev: true
/@wagmi/core@1.0.1(react@18.2.0)(typescript@5.2.2)(viem@1.18.1): /@wagmi/core@1.0.1(react@18.2.0)(typescript@5.2.2)(viem@1.18.4):
resolution: {integrity: sha512-Zzg4Ob92QMF9NsC+z5/8JZjMn3NCCnwVWGJlv79qRX9mp5Ku40OzJNvqDnjcSGjshe6H0L/KtFZAqTlmu8lT7w==} resolution: {integrity: sha512-Zzg4Ob92QMF9NsC+z5/8JZjMn3NCCnwVWGJlv79qRX9mp5Ku40OzJNvqDnjcSGjshe6H0L/KtFZAqTlmu8lT7w==}
peerDependencies: peerDependencies:
typescript: '>=4.9.4' typescript: '>=4.9.4'
...@@ -4670,11 +4670,11 @@ packages: ...@@ -4670,11 +4670,11 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@wagmi/chains': 0.2.22(typescript@5.2.2) '@wagmi/chains': 0.2.22(typescript@5.2.2)
'@wagmi/connectors': 1.0.1(@wagmi/chains@0.2.22)(react@18.2.0)(typescript@5.2.2)(viem@1.18.1) '@wagmi/connectors': 1.0.1(@wagmi/chains@0.2.22)(react@18.2.0)(typescript@5.2.2)(viem@1.18.4)
abitype: 0.8.1(typescript@5.2.2) abitype: 0.8.1(typescript@5.2.2)
eventemitter3: 4.0.7 eventemitter3: 4.0.7
typescript: 5.2.2 typescript: 5.2.2
viem: 1.18.1(typescript@5.2.2)(zod@3.22.4) viem: 1.18.4(typescript@5.2.2)(zod@3.22.4)
zustand: 4.3.9(react@18.2.0) zustand: 4.3.9(react@18.2.0)
transitivePeerDependencies: transitivePeerDependencies:
- '@react-native-async-storage/async-storage' - '@react-native-async-storage/async-storage'
...@@ -4688,7 +4688,7 @@ packages: ...@@ -4688,7 +4688,7 @@ packages:
- utf-8-validate - utf-8-validate
- zod - zod
/@wagmi/core@1.4.5(react@18.2.0)(typescript@5.2.2)(viem@1.18.1): /@wagmi/core@1.4.5(react@18.2.0)(typescript@5.2.2)(viem@1.18.4):
resolution: {integrity: sha512-N9luRb1Uk4tBN9kaYcQSWKE9AsRt/rvZaFt5IZech4JPzNN2sQlfhKd9GEjOXYRDqEPHdDvos7qyBKiDNTz4GA==} resolution: {integrity: sha512-N9luRb1Uk4tBN9kaYcQSWKE9AsRt/rvZaFt5IZech4JPzNN2sQlfhKd9GEjOXYRDqEPHdDvos7qyBKiDNTz4GA==}
peerDependencies: peerDependencies:
typescript: '>=5.0.4' typescript: '>=5.0.4'
...@@ -4697,11 +4697,11 @@ packages: ...@@ -4697,11 +4697,11 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@wagmi/connectors': 3.1.3(react@18.2.0)(typescript@5.2.2)(viem@1.18.1) '@wagmi/connectors': 3.1.3(react@18.2.0)(typescript@5.2.2)(viem@1.18.4)
abitype: 0.8.7(typescript@5.2.2)(zod@3.22.3) abitype: 0.8.7(typescript@5.2.2)(zod@3.22.3)
eventemitter3: 4.0.7 eventemitter3: 4.0.7
typescript: 5.2.2 typescript: 5.2.2
viem: 1.18.1(typescript@5.2.2)(zod@3.22.4) viem: 1.18.4(typescript@5.2.2)(zod@3.22.4)
zustand: 4.3.9(react@18.2.0) zustand: 4.3.9(react@18.2.0)
transitivePeerDependencies: transitivePeerDependencies:
- '@react-native-async-storage/async-storage' - '@react-native-async-storage/async-storage'
...@@ -8903,8 +8903,8 @@ packages: ...@@ -8903,8 +8903,8 @@ packages:
- utf-8-validate - utf-8-validate
dev: true dev: true
/hardhat@2.18.3(ts-node@10.9.1)(typescript@5.2.2): /hardhat@2.19.0(ts-node@10.9.1)(typescript@5.2.2):
resolution: {integrity: sha512-JuYaTG+4ZHVjEHCW5Hn6jCHH3LpO75dtgznZpM/dLv12RcSlw/xHbeQh3FAsGahQr1epKryZcZEMHvztVZHe0g==} resolution: {integrity: sha512-kMpwovOEfrFRQXEopCP+JTcKVwSYVj8rnXE0LynxDqnh06yvyKCQknmXL6IVYTHQL6Csysc/yNbCHQbjSeJGpA==}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
ts-node: '*' ts-node: '*'
...@@ -14292,8 +14292,8 @@ packages: ...@@ -14292,8 +14292,8 @@ packages:
vfile-message: 2.0.4 vfile-message: 2.0.4
dev: true dev: true
/viem@1.18.1(typescript@5.2.2)(zod@3.22.3): /viem@1.18.4(typescript@5.2.2)(zod@3.22.3):
resolution: {integrity: sha512-dkZG1jI8iL7G0+KZ8ZKHCXbzZxzu8Iib7OLCxkdaqdrlNrWTEMIZSp/2AHpbjpPeAg3VFD1CUayKPTJv2ZMXCg==} resolution: {integrity: sha512-im+y30k+IGT6VtfD/q1V0RX5PaiHPsFTHkKqvTjTqV+ZT8RgJXzOGPXr5E0uPIm2cbJAJp6A9nR9BCHY7BKR2Q==}
peerDependencies: peerDependencies:
typescript: '>=5.0.4' typescript: '>=5.0.4'
peerDependenciesMeta: peerDependenciesMeta:
...@@ -14315,8 +14315,8 @@ packages: ...@@ -14315,8 +14315,8 @@ packages:
- zod - zod
dev: true dev: true
/viem@1.18.1(typescript@5.2.2)(zod@3.22.4): /viem@1.18.4(typescript@5.2.2)(zod@3.22.4):
resolution: {integrity: sha512-dkZG1jI8iL7G0+KZ8ZKHCXbzZxzu8Iib7OLCxkdaqdrlNrWTEMIZSp/2AHpbjpPeAg3VFD1CUayKPTJv2ZMXCg==} resolution: {integrity: sha512-im+y30k+IGT6VtfD/q1V0RX5PaiHPsFTHkKqvTjTqV+ZT8RgJXzOGPXr5E0uPIm2cbJAJp6A9nR9BCHY7BKR2Q==}
peerDependencies: peerDependencies:
typescript: '>=5.0.4' typescript: '>=5.0.4'
peerDependenciesMeta: peerDependenciesMeta:
...@@ -14809,7 +14809,7 @@ packages: ...@@ -14809,7 +14809,7 @@ packages:
xml-name-validator: 4.0.0 xml-name-validator: 4.0.0
dev: true dev: true
/wagmi@1.0.1(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.18.1): /wagmi@1.0.1(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.18.4):
resolution: {integrity: sha512-+2UkZG9eA3tKqXj1wvlvI8mL0Bcff7Tf5CKfUOyQsdKcY+J5rfwYYya25G+jja57umpHFtfxRaL7xDkNjehrRg==} resolution: {integrity: sha512-+2UkZG9eA3tKqXj1wvlvI8mL0Bcff7Tf5CKfUOyQsdKcY+J5rfwYYya25G+jja57umpHFtfxRaL7xDkNjehrRg==}
peerDependencies: peerDependencies:
react: '>=17.0.0' react: '>=17.0.0'
...@@ -14822,12 +14822,12 @@ packages: ...@@ -14822,12 +14822,12 @@ packages:
'@tanstack/query-sync-storage-persister': 4.29.25 '@tanstack/query-sync-storage-persister': 4.29.25
'@tanstack/react-query': 4.29.25(react-dom@18.2.0)(react@18.2.0) '@tanstack/react-query': 4.29.25(react-dom@18.2.0)(react@18.2.0)
'@tanstack/react-query-persist-client': 4.29.25(@tanstack/react-query@4.29.25) '@tanstack/react-query-persist-client': 4.29.25(@tanstack/react-query@4.29.25)
'@wagmi/core': 1.0.1(react@18.2.0)(typescript@5.2.2)(viem@1.18.1) '@wagmi/core': 1.0.1(react@18.2.0)(typescript@5.2.2)(viem@1.18.4)
abitype: 0.8.1(typescript@5.2.2) abitype: 0.8.1(typescript@5.2.2)
react: 18.2.0 react: 18.2.0
typescript: 5.2.2 typescript: 5.2.2
use-sync-external-store: 1.2.0(react@18.2.0) use-sync-external-store: 1.2.0(react@18.2.0)
viem: 1.18.1(typescript@5.2.2)(zod@3.22.4) viem: 1.18.4(typescript@5.2.2)(zod@3.22.4)
transitivePeerDependencies: transitivePeerDependencies:
- '@react-native-async-storage/async-storage' - '@react-native-async-storage/async-storage'
- bufferutil - bufferutil
......
...@@ -51,6 +51,7 @@ type RateLimitConfig struct { ...@@ -51,6 +51,7 @@ type RateLimitConfig struct {
ExemptUserAgents []string `toml:"exempt_user_agents"` ExemptUserAgents []string `toml:"exempt_user_agents"`
ErrorMessage string `toml:"error_message"` ErrorMessage string `toml:"error_message"`
MethodOverrides map[string]*RateLimitMethodOverride `toml:"method_overrides"` MethodOverrides map[string]*RateLimitMethodOverride `toml:"method_overrides"`
IPHeaderOverride string `toml:"ip_header_override"`
} }
type RateLimitMethodOverride struct { type RateLimitMethodOverride struct {
......
...@@ -138,7 +138,6 @@ github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2 ...@@ -138,7 +138,6 @@ github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY=
......
...@@ -44,6 +44,7 @@ const ( ...@@ -44,6 +44,7 @@ const (
defaultWSWriteTimeout = 10 * time.Second defaultWSWriteTimeout = 10 * time.Second
maxRequestBodyLogLen = 2000 maxRequestBodyLogLen = 2000
defaultMaxUpstreamBatchSize = 10 defaultMaxUpstreamBatchSize = 10
defaultRateLimitHeader = "X-Forwarded-For"
) )
var emptyArrayResponse = json.RawMessage("[]") var emptyArrayResponse = json.RawMessage("[]")
...@@ -73,6 +74,7 @@ type Server struct { ...@@ -73,6 +74,7 @@ type Server struct {
wsServer *http.Server wsServer *http.Server
cache RPCCache cache RPCCache
srvMu sync.Mutex srvMu sync.Mutex
rateLimitHeader string
} }
type limiterFunc func(method string) bool type limiterFunc func(method string) bool
...@@ -168,6 +170,11 @@ func NewServer( ...@@ -168,6 +170,11 @@ func NewServer(
senderLim = limiterFactory(time.Duration(senderRateLimitConfig.Interval), senderRateLimitConfig.Limit, "senders") senderLim = limiterFactory(time.Duration(senderRateLimitConfig.Interval), senderRateLimitConfig.Limit, "senders")
} }
rateLimitHeader := defaultRateLimitHeader
if rateLimitConfig.IPHeaderOverride != "" {
rateLimitHeader = rateLimitConfig.IPHeaderOverride
}
return &Server{ return &Server{
BackendGroups: backendGroups, BackendGroups: backendGroups,
wsBackendGroup: wsBackendGroup, wsBackendGroup: wsBackendGroup,
...@@ -192,6 +199,7 @@ func NewServer( ...@@ -192,6 +199,7 @@ func NewServer(
allowedChainIds: senderRateLimitConfig.AllowedChainIds, allowedChainIds: senderRateLimitConfig.AllowedChainIds,
limExemptOrigins: limExemptOrigins, limExemptOrigins: limExemptOrigins,
limExemptUserAgents: limExemptUserAgents, limExemptUserAgents: limExemptUserAgents,
rateLimitHeader: rateLimitHeader,
}, nil }, nil
} }
...@@ -608,7 +616,7 @@ func (s *Server) HandleWS(w http.ResponseWriter, r *http.Request) { ...@@ -608,7 +616,7 @@ func (s *Server) HandleWS(w http.ResponseWriter, r *http.Request) {
func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context.Context { func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context.Context {
vars := mux.Vars(r) vars := mux.Vars(r)
authorization := vars["authorization"] authorization := vars["authorization"]
xff := r.Header.Get("X-Forwarded-For") xff := r.Header.Get(s.rateLimitHeader)
if xff == "" { if xff == "" {
ipPort := strings.Split(r.RemoteAddr, ":") ipPort := strings.Split(r.RemoteAddr, ":")
if len(ipPort) == 2 { if len(ipPort) == 2 {
......
...@@ -11,7 +11,11 @@ ...@@ -11,7 +11,11 @@
- [Owner removal call flow](#owner-removal-call-flow) - [Owner removal call flow](#owner-removal-call-flow)
- [Shutdown](#shutdown) - [Shutdown](#shutdown)
- [Security Properties](#security-properties) - [Security Properties](#security-properties)
- [In the guard](#in-the-guard)
- [In the module](#in-the-module)
- [Interdependency between the guard and module](#interdependency-between-the-guard-and-module) - [Interdependency between the guard and module](#interdependency-between-the-guard-and-module)
- [Operational considerations](#operational-considerations)
- [Manual validation of new owner liveness](#manual-validation-of-new-owner-liveness)
- [Deploying the liveness checking system](#deploying-the-liveness-checking-system) - [Deploying the liveness checking system](#deploying-the-liveness-checking-system)
- [Modify the liveness checking system](#modify-the-liveness-checking-system) - [Modify the liveness checking system](#modify-the-liveness-checking-system)
- [Replacing the module](#replacing-the-module) - [Replacing the module](#replacing-the-module)
...@@ -45,7 +49,18 @@ For implementing liveness checks a `LivenessGuard` is created which receives the ...@@ -45,7 +49,18 @@ For implementing liveness checks a `LivenessGuard` is created which receives the
each executed transaction, and tracks the latest time at which a transaction was signed by each each executed transaction, and tracks the latest time at which a transaction was signed by each
signer. This time is made publicly available by calling a `lastLive(address)(Timestamp)` method. signer. This time is made publicly available by calling a `lastLive(address)(Timestamp)` method.
Signers may also call the contract's `showLiveness()()` method directly in order to prove liveness. Owners are recorded in this mapping in one of 4 ways:
1. Upon deployment, the guard reads the current set of owners from the Safe contract.
1. When a new owner is added to the safe. Similarly, when an owner is removed from the Safe, it's
entry is deleted from the mapping.
1. When a transaction is executed, the signatures on that transaction are passed to the guard and
used to identify the signers. If more than the required number of signatures is provided, they
are ignored.
1. An owner may call the contract's `showLiveness()()` method directly in order to prove liveness.
Note that the first two methods do not require the owner to actually sign anything. However these mechanisms
are necessary to prevent new owners from being removed before they have had a chance to show liveness.
### The liveness module ### The liveness module
...@@ -85,25 +100,34 @@ sequenceDiagram ...@@ -85,25 +100,34 @@ sequenceDiagram
### Shutdown ### Shutdown
In the unlikely event that the signer set (`N`) is reduced below the allowed threshold, then (and only then) is a In the unlikely event that the signer set (`N`) is reduced below the allowed minimum number of
shutdown mechanism activated which removes the existing signers, and hands control of the owners, then (and only then) is a shutdown mechanism activated which removes the existing
multisig over to a predetermined entity. signers, and hands control of the multisig over to a predetermined entity.
### Security Properties ### Security Properties
The following security properties must be upheld: The following security properties must be upheld:
#### In the guard
1. Signatures are assigned to the correct signer. 1. Signatures are assigned to the correct signer.
1. Non-signers are unable to create a record of having signed. 1. Non-signers are unable to create a record of having signed.
1. A signer cannot be censored or griefed such that their signing is not recorded. 1. An owner cannot be censored or griefed such that their signing is not recorded.
1. Signers may demonstrate liveness either by signing a transaction or by calling directly to the 1. Owners may demonstrate liveness either by signing a transaction or by calling directly to the
guard. guard.
1. The module only removes a signer if they have demonstrated liveness during the interval, or 1. It must be impossible for the guard's `checkTransaction` or `checkAfterExecution` method to
if necessary to convert the safe to a 1 of 1. permanently revert given any calldata and the current state.
1. The module sets the correct 75% threshold upon removing a signer. 1. The guard correctly handles updates to the owners list, such that new owners are recorded, and
removed owners are deleted.
1. An `ownersBefore` enumerable set variable is used to accomplish this, it must be emptied at
the end of the `checkAfterExecution` call.
#### In the module
1. During a shutdown the module correctly removes all signers, and converts the safe to a 1 of 1. 1. During a shutdown the module correctly removes all signers, and converts the safe to a 1 of 1.
1. It must be impossible for the guard's checkTransaction or checkAfterExecution to permanently 1. The module only removes an owner if they have not demonstrated liveness during the interval, or
revert given any calldata and the current state. if enough other owners have been removed to activate the shutdown mechanism.
1. The module correctly sets the Safe's threshold upon removing a signer.
Note: neither the module nor guard attempt to prevent a quorum of owners from removing either the liveness Note: neither the module nor guard attempt to prevent a quorum of owners from removing either the liveness
module or guard. There are legitimate reasons they might wish to do so. Moreover, if such a quorum module or guard. There are legitimate reasons they might wish to do so. Moreover, if such a quorum
...@@ -119,6 +143,14 @@ This means that the module can be removed or replaced without any affect on the ...@@ -119,6 +143,14 @@ This means that the module can be removed or replaced without any affect on the
The module however does have a dependency on the guard; if the guard is removed from the Safe, then The module however does have a dependency on the guard; if the guard is removed from the Safe, then
the module will no longer be functional and calls to its `removeOwners` function will revert. the module will no longer be functional and calls to its `removeOwners` function will revert.
## Operational considerations
### Manual validation of new owner liveness
As [noted above](#the-liveness-guard) newly added owners are recorded in the guard without
necessarily having signed a transaction. Off-chain validation of the liveness of an address must
therefore be done prior to adding a new owner.
### Deploying the liveness checking system ### Deploying the liveness checking system
[deploying]: #deploying-the-liveness-checking-system [deploying]: #deploying-the-liveness-checking-system
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
- [Introduction](#introduction) - [Introduction](#introduction)
- [Span batch format](#span-batch-format) - [Span batch format](#span-batch-format)
- [Span batch Activation Rule](#span-batch-activation-rule)
- [Optimization Strategies](#optimization-strategies) - [Optimization Strategies](#optimization-strategies)
- [Truncating information and storing only necessary data](#truncating-information-and-storing-only-necessary-data) - [Truncating information and storing only necessary data](#truncating-information-and-storing-only-necessary-data)
- [`tx_data_headers` removal from initial specs](#tx_data_headers-removal-from-initial-specs) - [`tx_data_headers` removal from initial specs](#tx_data_headers-removal-from-initial-specs)
...@@ -153,6 +154,16 @@ decoding. For example, lets say bad batcher wrote span batch which `block_count ...@@ -153,6 +154,16 @@ decoding. For example, lets say bad batcher wrote span batch which `block_count
the explicit limit, not trying to consume data until EOF is reached. We can also safely preallocate memory for decoding the explicit limit, not trying to consume data until EOF is reached. We can also safely preallocate memory for decoding
because we know the upper limit of memory usage. because we know the upper limit of memory usage.
## Span batch Activation Rule
The span batch upgrade is activated based on timestamp.
Activation Rule: `upgradeTime != null && span_start.l1_origin.timestamp >= upgradeTime`
`span_start.l1_origin.timestamp` is the L1 origin block timestamp of the first block in the span batch.
This rule ensures that every chain activity regarding this span batch is done after the hard fork.
i.e. Every block in the span is created, submitted to the L1, and derived from the L1 after the hard fork.
## Optimization Strategies ## Optimization Strategies
### Truncating information and storing only necessary data ### Truncating information and storing only necessary data
...@@ -261,6 +272,13 @@ Rules are enforced with the [contextual definitions](./derivation.md#batch-queue ...@@ -261,6 +272,13 @@ Rules are enforced with the [contextual definitions](./derivation.md#batch-queue
Span-batch rules, in validation order: Span-batch rules, in validation order:
- `batch_origin` is determined like with singular batches:
- `batch.epoch_num == epoch.number+1`:
- If `next_epoch` is not known -> `undecided`:
i.e. a batch that changes the L1 origin cannot be processed until we have the L1 origin data.
- If known, then define `batch_origin` as `next_epoch`
- `batch_origin.timestamp < span_batch_upgrade_timestamp` -> `drop`:
i.e. enforce the [span batch upgrade activation rule](#span-batch-activation-rule).
- `batch.start_timestamp > next_timestamp` -> `future`: i.e. the batch must be ready to process. - `batch.start_timestamp > next_timestamp` -> `future`: i.e. the batch must be ready to process.
- `batch.start_timestamp < next_timestamp` -> `drop`: i.e. the batch must not be too old. - `batch.start_timestamp < next_timestamp` -> `drop`: i.e. the batch must not be too old.
- `batch.parent_check != safe_l2_head.hash[:20]` -> `drop`: i.e. the checked part of the parent hash must be equal - `batch.parent_check != safe_l2_head.hash[:20]` -> `drop`: i.e. the checked part of the parent hash must be equal
......
...@@ -199,7 +199,10 @@ and are then retrieved from the superchain target configuration. ...@@ -199,7 +199,10 @@ and are then retrieved from the superchain target configuration.
### L2 Block-number based activation (deprecated) ### L2 Block-number based activation (deprecated)
Activation rule: `x != null && x >= upgradeNumber` Activation rule: `upgradeNumber != null && block.number >= upgradeNumber`
Starting at, and including, the L2 `block` with `block.number >= upgradeNumber`, the upgrade rules apply.
If the upgrade block-number `upgradeNumber` is not specified in the configuration, the upgrade is ignored.
This block number based method has commonly been used in L1 up until the Bellatrix/Paris upgrade, a.k.a. The Merge, This block number based method has commonly been used in L1 up until the Bellatrix/Paris upgrade, a.k.a. The Merge,
which was upgraded through special rules. which was upgraded through special rules.
...@@ -207,22 +210,19 @@ which was upgraded through special rules. ...@@ -207,22 +210,19 @@ which was upgraded through special rules.
This method is not superchain-compatible, as the activation-parameter is chain-specific This method is not superchain-compatible, as the activation-parameter is chain-specific
(different chains may have different block-heights at the same moment in time). (different chains may have different block-heights at the same moment in time).
Starting at, and including, the L2 `block` with `block.number == x`, the upgrade rules apply.
If the upgrade block-number `x` is not specified in the configuration, the upgrade is ignored.
This applies to the L2 block number, not to the L1-origin block number. This applies to the L2 block number, not to the L1-origin block number.
This means that an L2 upgrade may be inactive, and then active, without changing the L1-origin. This means that an L2 upgrade may be inactive, and then active, without changing the L1-origin.
### L2 Block-timestamp based activation ### L2 Block-timestamp based activation
Activation rule: `x != null && x >= upgradeTime` Activation rule: `upgradeTime != null && block.timestamp >= upgradeTime`
Starting at, and including, the L2 `block` with `block.timestamp >= upgradeTime`, the upgrade rules apply.
If the upgrade block-timestamp `upgradeTime` is not specified in the configuration, the upgrade is ignored.
This is the preferred superchain upgrade activation-parameter type: This is the preferred superchain upgrade activation-parameter type:
it is synchronous between all L2 chains and compatible with post-Merge timestamp-based chain upgrades in L1. it is synchronous between all L2 chains and compatible with post-Merge timestamp-based chain upgrades in L1.
Starting at, and including, the L2 `block` with `block.timestamp == x`, the upgrade rules apply.
If the upgrade block-timestamp `x` is not specified in the configuration, the upgrade is ignored.
This applies to the L2 block timestamp, not to the L1-origin block timestamp. This applies to the L2 block timestamp, not to the L1-origin block timestamp.
This means that an L2 upgrade may be inactive, and then active, without changing the L1-origin. This means that an L2 upgrade may be inactive, and then active, without changing the L1-origin.
......
...@@ -7,6 +7,8 @@ WORKDIR /app ...@@ -7,6 +7,8 @@ WORKDIR /app
# Update PATH # Update PATH
ENV PATH /app/node_modules/.bin:$PATH ENV PATH /app/node_modules/.bin:$PATH
RUN npm i -g pnpm
RUN if [ "$METAMASK_PLAYWRIGHT_RUN_HEADLESS" != "false" ]; then \ RUN if [ "$METAMASK_PLAYWRIGHT_RUN_HEADLESS" != "false" ]; then \
apt-get update && \ apt-get update && \
apt-get install -y xvfb && \ apt-get install -y xvfb && \
...@@ -14,8 +16,8 @@ RUN if [ "$METAMASK_PLAYWRIGHT_RUN_HEADLESS" != "false" ]; then \ ...@@ -14,8 +16,8 @@ RUN if [ "$METAMASK_PLAYWRIGHT_RUN_HEADLESS" != "false" ]; then \
fi fi
# Copy necessary files and directories # Copy necessary files and directories
COPY package.json /app/ COPY package.json pnpm-lock.yaml pnpm-workspace.yaml /app/
RUN npm install RUN pnpm install --frozen-lockfile
COPY tests /app/tests/ COPY tests /app/tests/
COPY playwright.config.ts /app/ COPY playwright.config.ts /app/
COPY start.sh /app/ COPY start.sh /app/
......
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