Commit 7444b92f authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into aj/default-score-bands

parents 757253bf 2d04a15e
# indexer/cmd/indexer-refresh
Entrypoint for the new WIP indexer. After project is deployed and stable we will delete the [indexer/main.go](../indexer/main.go) file and move [indexer-refresh/main.go](./main.go) in it's place.
package main
import (
"fmt"
"os"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/urfave/cli"
"github.com/ethereum-optimism/optimism/indexer"
"github.com/ethereum-optimism/optimism/indexer/flags"
)
var (
GitVersion = ""
GitCommit = ""
GitDate = ""
)
func main() {
// Set up logger with a default INFO level in case we fail to parse flags.
// Otherwise the final crtiical log won't show what the parsing error was.
log.Root().SetHandler(
log.LvlFilterHandler(
log.LvlInfo,
log.StreamHandler(os.Stdout, log.TerminalFormat(true)),
),
)
app := cli.NewApp()
app.Flags = flags.Flags
app.Version = fmt.Sprintf("%s-%s", GitVersion, params.VersionWithCommit(GitCommit, GitDate))
app.Name = "indexer"
app.Usage = "Indexer Service"
app.Description = "Service for indexing deposits and withdrawals " +
"by account on L1 and L2"
app.Action = indexer.Main(GitVersion)
err := app.Run(os.Args)
if err != nil {
log.Crit("Application failed", "message", err)
}
}
......@@ -8,8 +8,8 @@ import (
"github.com/ethereum/go-ethereum/params"
"github.com/urfave/cli"
"github.com/ethereum-optimism/optimism/indexer"
"github.com/ethereum-optimism/optimism/indexer/flags"
"github.com/ethereum-optimism/optimism/indexer/legacy"
)
var (
......@@ -36,7 +36,7 @@ func main() {
app.Description = "Service for indexing deposits and withdrawals " +
"by account on L1 and L2"
app.Action = indexer.Main(GitVersion)
app.Action = legacy.Main(GitVersion)
err := app.Run(os.Args)
if err != nil {
log.Crit("Application failed", "message", err)
......
......@@ -38,13 +38,27 @@ services:
- INDEXER_REST_HOSTNAME=0.0.0.0
- INDEXER_REST_PORT=8080
- INDEXER_BEDROCK_L1_STANDARD_BRIDGE=0
- INDEXER_BEDROCK_L1_STANDARD_BRIDGE=0x636Af16bf2f682dD3109e60102b8E1A089FedAa8
- INDEXER_BEDROCK_OPTIMISM_PORTAL=0xB7040fd32359688346A3D1395a42114cf8E3b9b2
- INDEXER_BEDROCK_L1_STANDARD_BRIDGE=${INDEXER_BEDROCK_L1_STANDARD_BRIDGE:-0x636Af16bf2f682dD3109e60102b8E1A089FedAa8}
- INDEXER_BEDROCK_OPTIMISM_PORTAL=${INDEXER_BEDROCK_OPTIMISM_PORTAL:-0xB7040fd32359688346A3D1395a42114cf8E3b9b2}
- INDEXER_L1_ADDRESS_MANAGER_ADDRESS=${INDEXER_L1_ADDRESS_MANAGER_ADDRESS:-0xdE1FCfB0851916CA5101820A69b13a4E276bd81F}
ports:
- 8080:8080
depends_on:
postgres:
condition: service_healthy
ui:
build:
context: ..
dockerfile: indexer/ui/Dockerfile
environment:
- DATABASE_URL=${DATABASE_URL:-postgresql://db_username:db_password@postgres:5432/db_name}
ports:
- 5555:5555
healthcheck:
test: wget localhost:5555 -q -O - > /dev/null 2>&1
depends_on:
postgres:
condition: service_healthy
volumes:
postgres_data:
......@@ -2,3 +2,5 @@
INDEXER_L1_ETH_RPC=FILL_ME_IN
INDEXER_L2_ETH_RPC=FILL_ME_IN
# Fill in to use prisma studio ui with a db other than the default
# DATABASE_URL=FILL_ME_IN
......@@ -2,28 +2,12 @@ package indexer
import (
"context"
"fmt"
"math/big"
"net"
"net/http"
"os"
"strconv"
"time"
"github.com/ethereum-optimism/optimism/indexer/services"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/indexer/metrics"
"github.com/ethereum-optimism/optimism/indexer/server"
"github.com/rs/cors"
database "github.com/ethereum-optimism/optimism/indexer/db"
"github.com/ethereum-optimism/optimism/indexer/services/l1"
"github.com/ethereum-optimism/optimism/indexer/services/l2"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
"github.com/gorilla/mux"
"github.com/urfave/cli"
)
......@@ -39,30 +23,7 @@ const (
// e.g. GitVersion, to be captured and used once the function is executed.
func Main(gitVersion string) func(ctx *cli.Context) error {
return func(ctx *cli.Context) error {
cfg, err := NewConfig(ctx)
if err != nil {
return err
}
log.Info("Initializing indexer")
indexer, err := NewIndexer(cfg)
if err != nil {
log.Error("Unable to create indexer", "error", err)
return err
}
log.Info("Starting indexer")
if err := indexer.Start(); err != nil {
return err
}
defer indexer.Stop()
log.Info("Indexer started")
<-(chan struct{})(nil)
return nil
}
}
......@@ -70,35 +31,23 @@ func Main(gitVersion string) func(ctx *cli.Context) error {
// Indexer is a service that configures the necessary resources for
// running the Sync and BlockHandler sub-services.
type Indexer struct {
ctx context.Context
cfg Config
l1Client *ethclient.Client
l2Client *ethclient.Client
l1IndexingService *l1.Service
l2IndexingService *l2.Service
airdropService *services.Airdrop
router *mux.Router
metrics *metrics.Metrics
db *database.Database
server *http.Server
}
// NewIndexer initializes the Indexer, gathering any resources
// that will be needed by the TxIndexer and StateIndexer
// sub-services.
func NewIndexer(cfg Config) (*Indexer, error) {
func NewIndexer() (*Indexer, error) {
ctx := context.Background()
var logHandler log.Handler
if cfg.LogTerminal {
logHandler = log.StreamHandler(os.Stdout, log.TerminalFormat(true))
} else {
logHandler = log.StreamHandler(os.Stdout, log.JSONFormat())
}
logLevel, err := log.LvlFromString(cfg.LogLevel)
var logHandler log.Handler = log.StreamHandler(os.Stdout, log.TerminalFormat(true))
// TODO https://linear.app/optimism/issue/DX-55/api-implement-rest-api-with-mocked-data
// do json format too
// TODO https://linear.app/optimism/issue/DX-55/api-implement-rest-api-with-mocked-data
// pass in loglevel from config
// logHandler = log.StreamHandler(os.Stdout, log.JSONFormat())
logLevel, err := log.LvlFromString("info")
if err != nil {
return nil, err
}
......@@ -107,182 +56,43 @@ func NewIndexer(cfg Config) (*Indexer, error) {
// Connect to L1 and L2 providers. Perform these last since they are the
// most expensive.
l1Client, rawl1Client, err := dialEthClientWithTimeout(ctx, cfg.L1EthRpc)
if err != nil {
return nil, err
}
l2Client, l2RPC, err := dialEthClientWithTimeout(ctx, cfg.L2EthRpc)
if err != nil {
return nil, err
}
m := metrics.NewMetrics(nil)
if cfg.MetricsServerEnable {
go func() {
_, err := m.Serve(cfg.MetricsHostname, cfg.MetricsPort)
if err != nil {
log.Error("metrics server failed to start", "err", err)
}
}()
log.Info("metrics server enabled", "host", cfg.MetricsHostname, "port", cfg.MetricsPort)
}
dsn := fmt.Sprintf("host=%s port=%d dbname=%s sslmode=disable",
cfg.DBHost, cfg.DBPort, cfg.DBName)
if cfg.DBUser != "" {
dsn += fmt.Sprintf(" user=%s", cfg.DBUser)
}
if cfg.DBPassword != "" {
dsn += fmt.Sprintf(" password=%s", cfg.DBPassword)
}
db, err := database.NewDatabase(dsn)
if err != nil {
return nil, err
}
var addrManager services.AddressManager
if cfg.Bedrock {
addrManager, err = services.NewBedrockAddresses(
l1Client,
cfg.BedrockL1StandardBridgeAddress,
cfg.BedrockOptimismPortalAddress,
)
} else {
addrManager, err = services.NewLegacyAddresses(l1Client, common.HexToAddress(cfg.L1AddressManagerAddress))
}
// TODO https://linear.app/optimism/issue/DX-55/api-implement-rest-api-with-mocked-data
// pass in rpc url from config
l1Client, _, err := dialEthClientWithTimeout(ctx, "http://localhost:8545")
if err != nil {
return nil, err
}
l1IndexingService, err := l1.NewService(l1.ServiceConfig{
Context: ctx,
Metrics: m,
L1Client: l1Client,
RawL1Client: rawl1Client,
ChainID: new(big.Int).SetUint64(cfg.ChainID),
AddressManager: addrManager,
DB: db,
ConfDepth: cfg.L1ConfDepth,
MaxHeaderBatchSize: cfg.MaxHeaderBatchSize,
StartBlockNumber: cfg.L1StartBlockNumber,
Bedrock: cfg.Bedrock,
})
// TODO https://linear.app/optimism/issue/DX-55/api-implement-rest-api-with-mocked-data
// pass in rpc url from config
l2Client, _, err := dialEthClientWithTimeout(ctx, "http://localhost:9545")
if err != nil {
return nil, err
}
l2IndexingService, err := l2.NewService(l2.ServiceConfig{
Context: ctx,
Metrics: m,
L2RPC: l2RPC,
L2Client: l2Client,
DB: db,
ConfDepth: cfg.L2ConfDepth,
MaxHeaderBatchSize: cfg.MaxHeaderBatchSize,
StartBlockNumber: uint64(0),
Bedrock: cfg.Bedrock,
})
if err != nil {
return nil, err
}
return &Indexer{
ctx: ctx,
cfg: cfg,
l1Client: l1Client,
l2Client: l2Client,
l1IndexingService: l1IndexingService,
l2IndexingService: l2IndexingService,
airdropService: services.NewAirdrop(db, m),
router: mux.NewRouter(),
metrics: m,
db: db,
l1Client: l1Client,
l2Client: l2Client,
}, nil
}
// Serve spins up a REST API server at the given hostname and port.
func (b *Indexer) Serve() error {
c := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
})
b.router.HandleFunc("/v1/l1/status", b.l1IndexingService.GetIndexerStatus).Methods("GET")
b.router.HandleFunc("/v1/l2/status", b.l2IndexingService.GetIndexerStatus).Methods("GET")
b.router.HandleFunc("/v1/deposits/0x{address:[a-fA-F0-9]{40}}", b.l1IndexingService.GetDeposits).Methods("GET")
b.router.HandleFunc("/v1/withdrawal/0x{hash:[a-fA-F0-9]{64}}", b.l2IndexingService.GetWithdrawalBatch).Methods("GET")
b.router.HandleFunc("/v1/withdrawals/0x{address:[a-fA-F0-9]{40}}", b.l2IndexingService.GetWithdrawals).Methods("GET")
b.router.HandleFunc("/v1/airdrops/0x{address:[a-fA-F0-9]{40}}", b.airdropService.GetAirdrop)
b.router.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
_, err := w.Write([]byte("OK"))
if err != nil {
log.Error("Error handling /healthz", "error", err)
}
})
middleware := server.LoggingMiddleware(b.metrics, log.New("service", "server"))
port := strconv.FormatUint(b.cfg.RESTPort, 10)
addr := net.JoinHostPort(b.cfg.RESTHostname, port)
b.server = &http.Server{
Addr: addr,
Handler: middleware(c.Handler(b.router)),
}
errCh := make(chan error, 1)
go func() {
errCh <- b.server.ListenAndServe()
}()
// Capture server startup errors
<-time.After(10 * time.Millisecond)
select {
case err := <-errCh:
return err
default:
log.Info("indexer REST server listening on", "addr", addr)
return nil
}
return nil
}
// Start starts the starts the indexing service on L1 and L2 chains and also
// starts the REST server.
func (b *Indexer) Start() error {
if b.cfg.DisableIndexer {
log.Info("indexer disabled, only serving data")
} else {
err := b.l1IndexingService.Start()
if err != nil {
return err
}
err = b.l2IndexingService.Start()
if err != nil {
return err
}
}
return b.Serve()
return nil
}
// Stop stops the indexing service on L1 and L2 chains.
func (b *Indexer) Stop() {
b.db.Close()
if b.server != nil {
// background context here so it waits for
// conns to close
_ = b.server.Shutdown(context.Background())
}
if !b.cfg.DisableIndexer {
b.l1IndexingService.Stop()
b.l2IndexingService.Stop()
}
}
// dialL1EthClientWithTimeout attempts to dial the L1 provider using the
......
......@@ -20,8 +20,8 @@ import (
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/indexer"
"github.com/ethereum-optimism/optimism/indexer/db"
"github.com/ethereum-optimism/optimism/indexer/legacy"
"github.com/ethereum-optimism/optimism/indexer/services/l1"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
......@@ -60,7 +60,7 @@ func TestBedrockIndexer(t *testing.T) {
l2Opts, err := bind.NewKeyedTransactorWithChainID(cfg.Secrets.Alice, cfg.L2ChainIDBig())
require.NoError(t, err)
idxrCfg := indexer.Config{
idxrCfg := legacy.Config{
ChainID: cfg.DeployConfig.L1ChainID,
L1EthRpc: sys.Nodes["l1"].HTTPEndpoint(),
L2EthRpc: sys.Nodes["sequencer"].HTTPEndpoint(),
......@@ -83,7 +83,7 @@ func TestBedrockIndexer(t *testing.T) {
BedrockL1StandardBridgeAddress: cfg.DeployConfig.L1StandardBridgeProxy,
BedrockOptimismPortalAddress: cfg.DeployConfig.OptimismPortalProxy,
}
idxr, err := indexer.NewIndexer(idxrCfg)
idxr, err := legacy.NewIndexer(idxrCfg)
require.NoError(t, err)
errCh := make(chan error, 1)
......
package indexer
package legacy
import (
"errors"
......
package indexer_test
package legacy_test
import (
"fmt"
"testing"
indexer "github.com/ethereum-optimism/optimism/indexer"
legacy "github.com/ethereum-optimism/optimism/indexer/legacy"
"github.com/stretchr/testify/require"
)
var validateConfigTests = []struct {
name string
cfg indexer.Config
cfg legacy.Config
expErr error
}{
{
name: "bad log level",
cfg: indexer.Config{
cfg: legacy.Config{
LogLevel: "unknown",
},
expErr: fmt.Errorf("unknown level: unknown"),
......@@ -27,7 +27,7 @@ var validateConfigTests = []struct {
func TestValidateConfig(t *testing.T) {
for _, test := range validateConfigTests {
t.Run(test.name, func(t *testing.T) {
err := indexer.ValidateConfig(&test.cfg)
err := legacy.ValidateConfig(&test.cfg)
require.Equal(t, err, test.expErr)
})
}
......
package legacy
import (
"context"
"fmt"
"math/big"
"net"
"net/http"
"os"
"strconv"
"time"
"github.com/ethereum-optimism/optimism/indexer/services"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/indexer/metrics"
"github.com/ethereum-optimism/optimism/indexer/server"
"github.com/rs/cors"
database "github.com/ethereum-optimism/optimism/indexer/db"
"github.com/ethereum-optimism/optimism/indexer/services/l1"
"github.com/ethereum-optimism/optimism/indexer/services/l2"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
"github.com/gorilla/mux"
"github.com/urfave/cli"
)
const (
// defaultDialTimeout is default duration the service will wait on
// startup to make a connection to either the L1 or L2 backends.
defaultDialTimeout = 5 * time.Second
)
// Main is the entrypoint into the indexer service. This method returns
// a closure that executes the service and blocks until the service exits. The
// use of a closure allows the parameters bound to the top-level main package,
// e.g. GitVersion, to be captured and used once the function is executed.
func Main(gitVersion string) func(ctx *cli.Context) error {
return func(ctx *cli.Context) error {
cfg, err := NewConfig(ctx)
if err != nil {
return err
}
log.Info("Initializing indexer")
indexer, err := NewIndexer(cfg)
if err != nil {
log.Error("Unable to create indexer", "error", err)
return err
}
log.Info("Starting indexer")
if err := indexer.Start(); err != nil {
return err
}
defer indexer.Stop()
log.Info("Indexer started")
<-(chan struct{})(nil)
return nil
}
}
// Indexer is a service that configures the necessary resources for
// running the Sync and BlockHandler sub-services.
type Indexer struct {
ctx context.Context
cfg Config
l1Client *ethclient.Client
l2Client *ethclient.Client
l1IndexingService *l1.Service
l2IndexingService *l2.Service
airdropService *services.Airdrop
router *mux.Router
metrics *metrics.Metrics
db *database.Database
server *http.Server
}
// NewIndexer initializes the Indexer, gathering any resources
// that will be needed by the TxIndexer and StateIndexer
// sub-services.
func NewIndexer(cfg Config) (*Indexer, error) {
ctx := context.Background()
var logHandler log.Handler
if cfg.LogTerminal {
logHandler = log.StreamHandler(os.Stdout, log.TerminalFormat(true))
} else {
logHandler = log.StreamHandler(os.Stdout, log.JSONFormat())
}
logLevel, err := log.LvlFromString(cfg.LogLevel)
if err != nil {
return nil, err
}
log.Root().SetHandler(log.LvlFilterHandler(logLevel, logHandler))
// Connect to L1 and L2 providers. Perform these last since they are the
// most expensive.
l1Client, rawl1Client, err := dialEthClientWithTimeout(ctx, cfg.L1EthRpc)
if err != nil {
return nil, err
}
l2Client, l2RPC, err := dialEthClientWithTimeout(ctx, cfg.L2EthRpc)
if err != nil {
return nil, err
}
m := metrics.NewMetrics(nil)
if cfg.MetricsServerEnable {
go func() {
_, err := m.Serve(cfg.MetricsHostname, cfg.MetricsPort)
if err != nil {
log.Error("metrics server failed to start", "err", err)
}
}()
log.Info("metrics server enabled", "host", cfg.MetricsHostname, "port", cfg.MetricsPort)
}
dsn := fmt.Sprintf("host=%s port=%d dbname=%s sslmode=disable",
cfg.DBHost, cfg.DBPort, cfg.DBName)
if cfg.DBUser != "" {
dsn += fmt.Sprintf(" user=%s", cfg.DBUser)
}
if cfg.DBPassword != "" {
dsn += fmt.Sprintf(" password=%s", cfg.DBPassword)
}
db, err := database.NewDatabase(dsn)
if err != nil {
return nil, err
}
var addrManager services.AddressManager
if cfg.Bedrock {
addrManager, err = services.NewBedrockAddresses(
l1Client,
cfg.BedrockL1StandardBridgeAddress,
cfg.BedrockOptimismPortalAddress,
)
} else {
addrManager, err = services.NewLegacyAddresses(l1Client, common.HexToAddress(cfg.L1AddressManagerAddress))
}
if err != nil {
return nil, err
}
l1IndexingService, err := l1.NewService(l1.ServiceConfig{
Context: ctx,
Metrics: m,
L1Client: l1Client,
RawL1Client: rawl1Client,
ChainID: new(big.Int).SetUint64(cfg.ChainID),
AddressManager: addrManager,
DB: db,
ConfDepth: cfg.L1ConfDepth,
MaxHeaderBatchSize: cfg.MaxHeaderBatchSize,
StartBlockNumber: cfg.L1StartBlockNumber,
Bedrock: cfg.Bedrock,
})
if err != nil {
return nil, err
}
l2IndexingService, err := l2.NewService(l2.ServiceConfig{
Context: ctx,
Metrics: m,
L2RPC: l2RPC,
L2Client: l2Client,
DB: db,
ConfDepth: cfg.L2ConfDepth,
MaxHeaderBatchSize: cfg.MaxHeaderBatchSize,
StartBlockNumber: uint64(0),
Bedrock: cfg.Bedrock,
})
if err != nil {
return nil, err
}
return &Indexer{
ctx: ctx,
cfg: cfg,
l1Client: l1Client,
l2Client: l2Client,
l1IndexingService: l1IndexingService,
l2IndexingService: l2IndexingService,
airdropService: services.NewAirdrop(db, m),
router: mux.NewRouter(),
metrics: m,
db: db,
}, nil
}
// Serve spins up a REST API server at the given hostname and port.
func (b *Indexer) Serve() error {
c := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
})
b.router.HandleFunc("/v1/l1/status", b.l1IndexingService.GetIndexerStatus).Methods("GET")
b.router.HandleFunc("/v1/l2/status", b.l2IndexingService.GetIndexerStatus).Methods("GET")
b.router.HandleFunc("/v1/deposits/0x{address:[a-fA-F0-9]{40}}", b.l1IndexingService.GetDeposits).Methods("GET")
b.router.HandleFunc("/v1/withdrawal/0x{hash:[a-fA-F0-9]{64}}", b.l2IndexingService.GetWithdrawalBatch).Methods("GET")
b.router.HandleFunc("/v1/withdrawals/0x{address:[a-fA-F0-9]{40}}", b.l2IndexingService.GetWithdrawals).Methods("GET")
b.router.HandleFunc("/v1/airdrops/0x{address:[a-fA-F0-9]{40}}", b.airdropService.GetAirdrop)
b.router.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
_, err := w.Write([]byte("OK"))
if err != nil {
log.Error("Error handling /healthz", "error", err)
}
})
middleware := server.LoggingMiddleware(b.metrics, log.New("service", "server"))
port := strconv.FormatUint(b.cfg.RESTPort, 10)
addr := net.JoinHostPort(b.cfg.RESTHostname, port)
b.server = &http.Server{
Addr: addr,
Handler: middleware(c.Handler(b.router)),
}
errCh := make(chan error, 1)
go func() {
errCh <- b.server.ListenAndServe()
}()
// Capture server startup errors
<-time.After(10 * time.Millisecond)
select {
case err := <-errCh:
return err
default:
log.Info("indexer REST server listening on", "addr", addr)
return nil
}
}
// Start starts the starts the indexing service on L1 and L2 chains and also
// starts the REST server.
func (b *Indexer) Start() error {
if b.cfg.DisableIndexer {
log.Info("indexer disabled, only serving data")
} else {
err := b.l1IndexingService.Start()
if err != nil {
return err
}
err = b.l2IndexingService.Start()
if err != nil {
return err
}
}
return b.Serve()
}
// Stop stops the indexing service on L1 and L2 chains.
func (b *Indexer) Stop() {
b.db.Close()
if b.server != nil {
// background context here so it waits for
// conns to close
_ = b.server.Shutdown(context.Background())
}
if !b.cfg.DisableIndexer {
b.l1IndexingService.Stop()
b.l2IndexingService.Stop()
}
}
// dialL1EthClientWithTimeout attempts to dial the L1 provider using the
// provided URL. If the dial doesn't complete within defaultDialTimeout seconds,
// this method will return an error.
func dialEthClientWithTimeout(ctx context.Context, url string) (
*ethclient.Client, *rpc.Client, error) {
ctxt, cancel := context.WithTimeout(ctx, defaultDialTimeout)
defer cancel()
c, err := rpc.DialContext(ctxt, url)
if err != nil {
return nil, nil, err
}
return ethclient.NewClient(c), c, nil
}
FROM node:18.16.0-bullseye-slim
WORKDIR /app
RUN echo {} > package.json && \
npm install prisma
COPY indexer/ui/schema.prisma schema.prisma
RUN npx prisma generate --schema schema.prisma
CMD ["npx", "prisma", "studio", "--port", "5555", "--hostname", "0.0.0.0", "--schema", "schema.prisma" ]
## @eth-optimism/indexer-ui
A simple UI for exploring the indexer DB using [Prisma studio](https://www.prisma.io)
## Usage
Included in the docker-compose file as `ui` service
```bash
docker-compose up
```
Prisma can be viewed at [localhost:5555](http://localhost:5555)
## Update the schema
The [prisma schema](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference) is what allows prisma to work. It is automatically generated from the db schema.
To update the schema to the latest db schema simply pass in the database url to [prisma pull](https://www.prisma.io/docs/reference/api-reference/command-reference#db-pull). Prisma pull will introspect the schema and generate a prisma schema
```bash
DATABASE_URL=postgresql://db_username:db_password@postgres:5432/db_name npx prisma db pull
```
## Other functionality
We mostly just use prisma as a UI. But brisma provides much other functionality that can be useful including.
- Ability to change the [db schema](https://www.prisma.io/docs/reference/api-reference/command-reference#db-push) direction from modifying the [schema.prisma](./schema.prisma) in place. This can be a fast way to [start prototyping](https://www.prisma.io/docs/guides/migrate/prototyping-schema-db-push)
- Ability to [seed the database](https://www.prisma.io/docs/guides/migrate/seed-database)
- Ability to write quick scripts with [prisma client](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference)
## Running prisma studio outside of docker
Prisma can also be run with [npx](https://docs.npmjs.com/cli/v8/commands/npx)
```bash
npx prisma studio --schema indexer/ui/schema.prisma
```
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model airdrops {
address String @id @db.VarChar(42)
voter_amount String @default("0") @db.VarChar
multisig_signer_amount String @default("0") @db.VarChar
gitcoin_amount String @default("0") @db.VarChar
active_bridged_amount String @default("0") @db.VarChar
op_user_amount String @default("0") @db.VarChar
op_repeat_user_amount String @default("0") @db.VarChar
op_og_amount String @default("0") @db.VarChar
bonus_amount String @default("0") @db.VarChar
total_amount String @db.VarChar
}
model deposits {
guid String @id @db.VarChar
from_address String @db.VarChar
to_address String @db.VarChar
l1_token String @db.VarChar
l2_token String @db.VarChar
amount String @db.VarChar
data Bytes
log_index Int
block_hash String @db.VarChar
tx_hash String @db.VarChar
l1_blocks l1_blocks @relation(fields: [block_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction)
l1_tokens l1_tokens @relation(fields: [l1_token], references: [address], onDelete: NoAction, onUpdate: NoAction)
}
model l1_blocks {
hash String @id @db.VarChar
parent_hash String @db.VarChar
number Int @unique(map: "l1_blocks_number")
timestamp Int
deposits deposits[]
state_batches state_batches[]
}
model l1_tokens {
address String @id @db.VarChar
name String @db.VarChar
symbol String @db.VarChar
decimals Int
deposits deposits[]
}
model l2_blocks {
hash String @id @db.VarChar
parent_hash String @db.VarChar
number Int @unique(map: "l2_blocks_number")
timestamp Int
withdrawals withdrawals[]
}
model l2_tokens {
address String @id
name String
symbol String
decimals Int
withdrawals withdrawals[]
}
model state_batches {
index Int @id
root String @db.VarChar
size Int
prev_total Int
extra_data Bytes
block_hash String @db.VarChar
l1_blocks l1_blocks @relation(fields: [block_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction)
withdrawals withdrawals[]
@@index([block_hash], map: "state_batches_block_hash")
@@index([prev_total], map: "state_batches_prev_total")
@@index([size], map: "state_batches_size")
}
model withdrawals {
guid String @id @db.VarChar
from_address String @db.VarChar
to_address String @db.VarChar
l1_token String @db.VarChar
l2_token String @db.VarChar
amount String @db.VarChar
data Bytes
log_index Int
block_hash String @db.VarChar
tx_hash String @db.VarChar
state_batch Int?
br_withdrawal_hash String? @db.VarChar
br_withdrawal_proven_tx_hash String? @db.VarChar
br_withdrawal_proven_log_index Int?
br_withdrawal_finalized_tx_hash String? @db.VarChar
br_withdrawal_finalized_log_index Int?
br_withdrawal_finalized_success Boolean?
l2_blocks l2_blocks @relation(map: "l2", fields: [block_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction)
l2_tokens l2_tokens @relation(fields: [l2_token], references: [address], onDelete: NoAction, onUpdate: NoAction)
state_batches state_batches? @relation(fields: [state_batch], references: [index], onDelete: NoAction, onUpdate: NoAction)
@@index([br_withdrawal_hash], map: "withdrawals_br_withdrawal_hash")
}
......@@ -4,9 +4,6 @@ import (
"context"
"fmt"
_ "net/http/pprof"
"os"
"os/signal"
"syscall"
gethrpc "github.com/ethereum/go-ethereum/rpc"
"github.com/urfave/cli"
......@@ -16,6 +13,7 @@ import (
"github.com/ethereum-optimism/optimism/op-batcher/rpc"
opservice "github.com/ethereum-optimism/optimism/op-service"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/opio"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
)
......@@ -98,14 +96,7 @@ func Main(version string, cliCtx *cli.Context) error {
m.RecordInfo(version)
m.RecordUp()
interruptChannel := make(chan os.Signal, 1)
signal.Notify(interruptChannel, []os.Signal{
os.Interrupt,
os.Kill,
syscall.SIGTERM,
syscall.SIGQUIT,
}...)
<-interruptChannel
opio.BlockOnInterrupts()
if err := server.Stop(); err != nil {
l.Error("Error shutting down http server: %w", err)
}
......
......@@ -30,7 +30,8 @@ bindings: l1block-bindings \
optimism-mintable-erc721-factory-bindings \
l1-fee-vault-bindings \
basefee-vault-bindings \
legacy-erc20-eth-bindings
legacy-erc20-eth-bindings \
dispute-game-factory-bindings
version:
forge --version
......@@ -122,6 +123,9 @@ deployer-whitelist-bindings: compile
l1-blocknumber-bindings: compile
./gen_bindings.sh contracts/legacy/L1BlockNumber.sol:L1BlockNumber $(pkg)
dispute-game-factory-bindings: compile
./gen_bindings.sh contracts/dispute/DisputeGameFactory.sol:DisputeGameFactory $(pkg)
more:
go run ./gen/main.go \
-artifacts ../packages/contracts-bedrock/artifacts \
......
This diff is collapsed.
This diff is collapsed.
......@@ -43,9 +43,9 @@ type Challenger struct {
l2ooABI *abi.ABI
// dispute game factory contract
// dgfContract *bindings.DisputeGameFactoryCaller
dgfContract *bindings.DisputeGameFactoryCaller
dgfContractAddr common.Address
// dgfABI *abi.ABI
dgfABI *abi.ABI
networkTimeout time.Duration
}
......@@ -79,6 +79,12 @@ func NewChallenger(cfg config.Config, l log.Logger, m metrics.Metricer) (*Challe
return nil, err
}
dgfContract, err := bindings.NewDisputeGameFactoryCaller(cfg.DGFAddress, l1Client)
if err != nil {
cancel()
return nil, err
}
cCtx, cCancel := context.WithTimeout(ctx, cfg.NetworkTimeout)
defer cCancel()
version, err := l2ooContract.Version(&bind.CallOpts{Context: cCtx})
......@@ -88,7 +94,13 @@ func NewChallenger(cfg config.Config, l log.Logger, m metrics.Metricer) (*Challe
}
l.Info("Connected to L2OutputOracle", "address", cfg.L2OOAddress, "version", version)
parsed, err := bindings.L2OutputOracleMetaData.GetAbi()
parsedL2oo, err := bindings.L2OutputOracleMetaData.GetAbi()
if err != nil {
cancel()
return nil, err
}
parsedDgf, err := bindings.DisputeGameFactoryMetaData.GetAbi()
if err != nil {
cancel()
return nil, err
......@@ -110,9 +122,11 @@ func NewChallenger(cfg config.Config, l log.Logger, m metrics.Metricer) (*Challe
l2ooContract: l2ooContract,
l2ooContractAddr: cfg.L2OOAddress,
l2ooABI: parsed,
l2ooABI: parsedL2oo,
dgfContract: dgfContract,
dgfContractAddr: cfg.DGFAddress,
dgfABI: parsedDgf,
networkTimeout: cfg.NetworkTimeout,
}, nil
......
......@@ -4,14 +4,12 @@ import (
"context"
"fmt"
_ "net/http/pprof"
"os"
"os/signal"
"syscall"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-service/opio"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
)
......@@ -73,14 +71,7 @@ func Main(logger log.Logger, version string, cfg *config.Config) error {
m.RecordInfo(version)
m.RecordUp()
interruptChannel := make(chan os.Signal, 1)
signal.Notify(interruptChannel, []os.Signal{
os.Interrupt,
os.Kill,
syscall.SIGTERM,
syscall.SIGQUIT,
}...)
<-interruptChannel
opio.BlockOnInterrupts()
cancel()
return nil
......
package challenger
import (
"context"
"sync"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-service/backoff"
)
// logStore manages log subscriptions.
type logStore struct {
// The log filter query
query ethereum.FilterQuery
// core sync mutex for log store
// this locks the entire log store
mu sync.Mutex
logList []types.Log
logMap map[common.Hash][]types.Log
// Log subscription
subscription *Subscription
// Client to query for logs
client ethereum.LogFilterer
// Logger
log log.Logger
}
// NewLogStore creates a new log store.
func NewLogStore(query ethereum.FilterQuery, client ethereum.LogFilterer, log log.Logger) *logStore {
return &logStore{
query: query,
mu: sync.Mutex{},
logList: make([]types.Log, 0),
logMap: make(map[common.Hash][]types.Log),
subscription: NewSubscription(query, client, log),
client: client,
log: log,
}
}
// Subscribed returns true if the subscription has started.
func (l *logStore) Subscribed() bool {
return l.subscription.Started()
}
// Query returns the log filter query.
func (l *logStore) Query() ethereum.FilterQuery {
return l.query
}
// Client returns the log filter client.
func (l *logStore) Client() ethereum.LogFilterer {
return l.client
}
// GetLogs returns all logs in the log store.
func (l *logStore) GetLogs() []types.Log {
l.mu.Lock()
defer l.mu.Unlock()
logs := make([]types.Log, len(l.logList))
copy(logs, l.logList)
return logs
}
// GetLogByBlockHash returns all logs in the log store for a given block hash.
func (l *logStore) GetLogByBlockHash(blockHash common.Hash) []types.Log {
l.mu.Lock()
defer l.mu.Unlock()
logs := make([]types.Log, len(l.logMap[blockHash]))
copy(logs, l.logMap[blockHash])
return logs
}
// Subscribe starts the subscription.
// This function spawns a new goroutine.
func (l *logStore) Subscribe(ctx context.Context) error {
err := l.subscription.Subscribe()
if err != nil {
l.log.Error("failed to subscribe", "err", err)
return err
}
go l.dispatchLogs(ctx)
return nil
}
// Quit stops all log store asynchronous tasks.
func (l *logStore) Quit() {
l.subscription.Quit()
}
// buildBackoffStrategy builds a [backoff.Strategy].
func (l *logStore) buildBackoffStrategy() backoff.Strategy {
return &backoff.ExponentialStrategy{
Min: 1000,
Max: 20_000,
MaxJitter: 250,
}
}
// resubscribe attempts to re-establish the log store internal
// subscription with a backoff strategy.
func (l *logStore) resubscribe(ctx context.Context) error {
l.log.Info("log store resubscribing with backoff")
backoffStrategy := l.buildBackoffStrategy()
return backoff.DoCtx(ctx, 10, backoffStrategy, func() error {
if l.subscription == nil {
l.log.Error("subscription zeroed out")
return nil
}
err := l.subscription.Subscribe()
if err == nil {
l.log.Info("subscription reconnected", "id", l.subscription.ID())
}
return err
})
}
// insertLog inserts a log into the log store.
func (l *logStore) insertLog(log types.Log) {
l.mu.Lock()
l.logList = append(l.logList, log)
l.logMap[log.BlockHash] = append(l.logMap[log.BlockHash], log)
l.mu.Unlock()
}
// dispatchLogs dispatches logs to the log store.
// This function is intended to be run as a goroutine.
func (l *logStore) dispatchLogs(ctx context.Context) {
for {
select {
case err := <-l.subscription.sub.Err():
l.log.Error("log subscription error", "err", err)
for {
err = l.resubscribe(ctx)
if err == nil {
break
}
}
case log := <-l.subscription.logs:
l.insertLog(log)
case <-l.subscription.quit:
l.log.Info("received quit signal from subscription", "id", l.subscription.ID())
return
}
}
}
package challenger
import (
"context"
"errors"
"testing"
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/stretchr/testify/require"
)
type mockLogStoreClient struct {
sub mockSubscription
logs chan<- types.Log
subcount int
}
func newMockLogStoreClient() *mockLogStoreClient {
return &mockLogStoreClient{
sub: mockSubscription{
errorChan: make(chan error),
},
}
}
func (m *mockLogStoreClient) FilterLogs(context.Context, ethereum.FilterQuery) ([]types.Log, error) {
panic("this should not be called by the Subscription.Subscribe method")
}
func (m *mockLogStoreClient) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, logs chan<- types.Log) (ethereum.Subscription, error) {
m.subcount = m.subcount + 1
m.logs = logs
return m.sub, nil
}
var (
ErrTestError = errors.New("test error")
)
// errLogStoreClient implements the [ethereum.LogFilter] interface for testing.
type errLogStoreClient struct{}
func (m errLogStoreClient) FilterLogs(context.Context, ethereum.FilterQuery) ([]types.Log, error) {
panic("this should not be called by the Subscription.Subscribe method")
}
func (m errLogStoreClient) SubscribeFilterLogs(context.Context, ethereum.FilterQuery, chan<- types.Log) (ethereum.Subscription, error) {
return nil, ErrTestError
}
type mockSubscription struct {
errorChan chan error
}
func (m mockSubscription) Err() <-chan error {
return m.errorChan
}
func (m mockSubscription) Unsubscribe() {}
func newLogStore(t *testing.T) (*logStore, *mockLogStoreClient) {
query := ethereum.FilterQuery{}
client := newMockLogStoreClient()
log := testlog.Logger(t, log.LvlError)
return NewLogStore(query, client, log), client
}
func newErrorLogStore(t *testing.T, client *errLogStoreClient) (*logStore, *errLogStoreClient) {
query := ethereum.FilterQuery{}
log := testlog.Logger(t, log.LvlError)
return NewLogStore(query, client, log), client
}
func TestLogStore_NewLogStore_NotSubscribed(t *testing.T) {
logStore, _ := newLogStore(t)
require.False(t, logStore.Subscribed())
}
func TestLogStore_NewLogStore_EmptyLogs(t *testing.T) {
logStore, _ := newLogStore(t)
require.Empty(t, logStore.GetLogs())
require.Empty(t, logStore.GetLogByBlockHash(common.Hash{}))
}
func TestLogStore_Subscribe_EstablishesSubscription(t *testing.T) {
logStore, client := newLogStore(t)
defer logStore.Quit()
require.Equal(t, 0, client.subcount)
require.False(t, logStore.Subscribed())
require.NoError(t, logStore.Subscribe(context.Background()))
require.True(t, logStore.Subscribed())
require.Equal(t, 1, client.subcount)
}
func TestLogStore_Subscribe_ReceivesLogs(t *testing.T) {
logStore, client := newLogStore(t)
defer logStore.Quit()
require.NoError(t, logStore.Subscribe(context.Background()))
mockLog := types.Log{
BlockHash: common.HexToHash("0x1"),
}
client.logs <- mockLog
timeout, tCancel := context.WithTimeout(context.Background(), 30*time.Second)
defer tCancel()
err := e2eutils.WaitFor(timeout, 500*time.Millisecond, func() (bool, error) {
result := logStore.GetLogByBlockHash(mockLog.BlockHash)
return result[0].BlockHash == mockLog.BlockHash, nil
})
require.NoError(t, err)
}
func TestLogStore_Subscribe_SubscriptionErrors(t *testing.T) {
logStore, client := newLogStore(t)
defer logStore.Quit()
require.NoError(t, logStore.Subscribe(context.Background()))
client.sub.errorChan <- ErrTestError
timeout, tCancel := context.WithTimeout(context.Background(), 30*time.Second)
defer tCancel()
err := e2eutils.WaitFor(timeout, 500*time.Millisecond, func() (bool, error) {
subcount := client.subcount == 2
started := logStore.subscription.Started()
return subcount && started, nil
})
require.NoError(t, err)
}
func TestLogStore_Subscribe_NoClient_Panics(t *testing.T) {
require.Panics(t, func() {
logStore, _ := newErrorLogStore(t, nil)
_ = logStore.Subscribe(context.Background())
})
}
func TestLogStore_Subscribe_ErrorSubscribing(t *testing.T) {
logStore, _ := newErrorLogStore(t, &errLogStoreClient{})
require.False(t, logStore.Subscribed())
require.EqualError(t, logStore.Subscribe(context.Background()), ErrTestError.Error())
}
func TestLogStore_Quit_ResetsSubscription(t *testing.T) {
logStore, _ := newLogStore(t)
require.False(t, logStore.Subscribed())
require.NoError(t, logStore.Subscribe(context.Background()))
require.True(t, logStore.Subscribed())
logStore.Quit()
require.False(t, logStore.Subscribed())
}
func TestLogStore_Quit_NoSubscription_Panics(t *testing.T) {
require.Panics(t, func() {
logStore, _ := newErrorLogStore(t, nil)
logStore.Quit()
})
}
package challenger
import (
"context"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
// SubscriptionId is a unique subscription ID.
type SubscriptionId uint64
// Increment returns the next subscription ID.
func (s *SubscriptionId) Increment() SubscriptionId {
*s++
return *s
}
// Subscription wraps an [ethereum.Subscription] to provide a restart.
type Subscription struct {
// The subscription ID
id SubscriptionId
// The current subscription
sub ethereum.Subscription
// If the subscription is started
started bool
// The query used to create the subscription
query ethereum.FilterQuery
// The log channel
logs chan types.Log
// The quit channel
quit chan struct{}
// Filter client used to open the log subscription
client ethereum.LogFilterer
// Logger
log log.Logger
}
// NewSubscription creates a new subscription.
func NewSubscription(query ethereum.FilterQuery, client ethereum.LogFilterer, log log.Logger) *Subscription {
return &Subscription{
id: SubscriptionId(0),
sub: nil,
started: false,
query: query,
logs: make(chan types.Log),
quit: make(chan struct{}),
client: client,
log: log,
}
}
// ID returns the subscription ID.
func (s *Subscription) ID() SubscriptionId {
return s.id
}
// Started returns true if the subscription has started.
func (s *Subscription) Started() bool {
return s.started
}
// Subscribe constructs the subscription.
func (s *Subscription) Subscribe() error {
s.log.Info("Subscribing to", "query", s.query.Topics, "id", s.id)
sub, err := s.client.SubscribeFilterLogs(context.Background(), s.query, s.logs)
if err != nil {
s.log.Error("failed to subscribe to logs", "err", err)
return err
}
s.sub = sub
s.started = true
return nil
}
// Quit closes the subscription.
func (s *Subscription) Quit() {
s.log.Info("Quitting subscription", "id", s.id)
s.sub.Unsubscribe()
s.quit <- struct{}{}
s.started = false
s.log.Info("Quit subscription", "id", s.id)
}
package challenger
import (
"context"
"errors"
"math"
"testing"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/stretchr/testify/require"
)
type mockLogFilterClient struct{}
func (m mockLogFilterClient) FilterLogs(context.Context, ethereum.FilterQuery) ([]types.Log, error) {
panic("this should not be called by the Subscription.Subscribe method")
}
func (m mockLogFilterClient) SubscribeFilterLogs(context.Context, ethereum.FilterQuery, chan<- types.Log) (ethereum.Subscription, error) {
return nil, nil
}
func newSubscription(t *testing.T, client *mockLogFilterClient) (*Subscription, *mockLogFilterClient) {
query := ethereum.FilterQuery{}
log := testlog.Logger(t, log.LvlError)
return NewSubscription(query, client, log), client
}
func FuzzSubscriptionId_Increment(f *testing.F) {
maxUint64 := uint64(math.MaxUint64)
f.Fuzz(func(t *testing.T, id uint64) {
if id >= maxUint64 {
t.Skip("skipping due to overflow")
} else {
subId := SubscriptionId(id)
require.Equal(t, subId.Increment(), SubscriptionId(id+1))
}
})
}
func TestSubscription_Subscribe_NilClient_Panics(t *testing.T) {
defer func() {
if recover() == nil {
t.Error("expected nil client to panic")
}
}()
subscription, _ := newSubscription(t, nil)
require.NoError(t, subscription.Subscribe())
}
func TestSubscription_Subscribe(t *testing.T) {
subscription, _ := newSubscription(t, &mockLogFilterClient{})
require.NoError(t, subscription.Subscribe())
require.True(t, subscription.Started())
}
var ErrSubscriptionFailed = errors.New("failed to subscribe to logs")
type errLogFilterClient struct{}
func (m errLogFilterClient) FilterLogs(context.Context, ethereum.FilterQuery) ([]types.Log, error) {
panic("this should not be called by the Subscription.Subscribe method")
}
func (m errLogFilterClient) SubscribeFilterLogs(context.Context, ethereum.FilterQuery, chan<- types.Log) (ethereum.Subscription, error) {
return nil, ErrSubscriptionFailed
}
func TestSubscription_Subscribe_SubscriptionErrors(t *testing.T) {
query := ethereum.FilterQuery{}
log := testlog.Logger(t, log.LvlError)
subscription := Subscription{
query: query,
client: errLogFilterClient{},
log: log,
}
require.EqualError(t, subscription.Subscribe(), ErrSubscriptionFailed.Error())
}
package types
import (
"fmt"
"github.com/ethereum-optimism/optimism/op-service/enum"
)
// GameType is the type of dispute game
type GameType uint8
// DefaultGameType returns the default dispute game type.
func DefaultGameType() GameType {
return AttestationDisputeGameType
}
// String returns the string value of a dispute game type.
func (g GameType) String() string {
return DisputeGameTypes[g]
}
const (
// AttestationDisputeGameType is the uint8 enum value for the attestation dispute game
AttestationDisputeGameType GameType = iota
// FaultDisputeGameType is the uint8 enum value for the fault dispute game
FaultDisputeGameType
// ValidityDisputeGameType is the uint8 enum value for the validity dispute game
ValidityDisputeGameType
)
// DisputeGameTypes is a list of dispute game types.
var DisputeGameTypes = []string{"attestation", "fault", "validity"}
// Valid returns true if the game type is within the valid range.
func (g GameType) Valid() bool {
return g >= AttestationDisputeGameType && g <= ValidityDisputeGameType
}
// DisputeGameType is a custom flag type for dispute game type.
type DisputeGameType struct {
Enum []enum.Stringered
selected GameType
}
// NewDisputeGameType returns a new dispute game type.
func NewDisputeGameType() *DisputeGameType {
return &DisputeGameType{
Enum: enum.StringeredList(DisputeGameTypes),
selected: DefaultGameType(),
}
}
// Set sets the dispute game type.
func (d *DisputeGameType) Set(value string) error {
for i, enum := range d.Enum {
if enum.String() == value {
d.selected = GameType(i)
return nil
}
}
return fmt.Errorf("allowed values are %s", enum.EnumString(d.Enum))
}
// String returns the selected dispute game type.
func (d DisputeGameType) String() string {
return d.selected.String()
}
// Type maps the [DisputeGameType] string value to a [GameType] enum value.
func (d DisputeGameType) Type() GameType {
return d.selected
}
package types
import (
"testing"
"github.com/stretchr/testify/require"
)
var (
disputeGames = []struct {
name string
gameType GameType
}{
{"attestation", AttestationDisputeGameType},
{"fault", FaultDisputeGameType},
{"validity", ValidityDisputeGameType},
}
)
// TestDefaultGameType returns the default dispute game type.
func TestDefaultGameType(t *testing.T) {
defaultGameType := disputeGames[0].gameType
require.Equal(t, defaultGameType, DefaultGameType())
}
// TestGameType_Valid tests the Valid function with valid inputs.
func TestGameType_Valid(t *testing.T) {
for _, game := range disputeGames {
require.True(t, game.gameType.Valid())
}
}
// TestGameType_Invalid tests the Valid function with an invalid input.
func TestGameType_Invalid(t *testing.T) {
invalid := disputeGames[len(disputeGames)-1].gameType + 1
require.False(t, GameType(invalid).Valid())
}
// FuzzGameType_Invalid checks that invalid game types are correctly
// returned as invalid by the validation [Valid] function.
func FuzzGameType_Invalid(f *testing.F) {
maxCount := len(DisputeGameTypes)
f.Fuzz(func(t *testing.T, number uint8) {
if number >= uint8(maxCount) {
require.False(t, GameType(number).Valid())
} else {
require.True(t, GameType(number).Valid())
}
})
}
// TestGameType_Default tests the default value of the DisputeGameType.
func TestGameType_Default(t *testing.T) {
d := NewDisputeGameType()
require.Equal(t, DefaultGameType(), d.selected)
require.Equal(t, DefaultGameType(), d.Type())
}
// TestGameType_String tests the Set and String function on the DisputeGameType.
func TestGameType_String(t *testing.T) {
for _, dg := range disputeGames {
t.Run(dg.name, func(t *testing.T) {
d := NewDisputeGameType()
require.Equal(t, dg.name, dg.gameType.String())
require.NoError(t, d.Set(dg.name))
require.Equal(t, dg.name, d.String())
require.Equal(t, dg.gameType, d.selected)
})
}
}
// TestGameType_Type tests the Type function on the DisputeGameType.
func TestGameType_Type(t *testing.T) {
for _, dg := range disputeGames {
t.Run(dg.name, func(t *testing.T) {
d := NewDisputeGameType()
require.Equal(t, dg.name, dg.gameType.String())
require.NoError(t, d.Set(dg.name))
require.Equal(t, dg.gameType, d.Type())
require.Equal(t, dg.gameType, d.selected)
})
}
}
......@@ -27,24 +27,22 @@ var Mainnet = rollup.Config{
// moose: Update this during migration
L2Time: 0,
SystemConfig: eth.SystemConfig{
BatcherAddr: common.HexToAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"),
Overhead: eth.Bytes32(common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000834")),
Scalar: eth.Bytes32(common.HexToHash("0x00000000000000000000000000000000000000000000000000000000000f4240")),
GasLimit: 25_000_000,
BatcherAddr: common.HexToAddress("0x6887246668a3b87f54deb3b94ba47a6f63f32985"),
Overhead: eth.Bytes32(common.HexToHash("0x00000000000000000000000000000000000000000000000000000000000000bc")),
Scalar: eth.Bytes32(common.HexToHash("0x00000000000000000000000000000000000000000000000000000000000a6fe0")),
GasLimit: 30_000_000,
},
},
BlockTime: 2,
MaxSequencerDrift: 600,
SeqWindowSize: 3600,
ChannelTimeout: 300,
L1ChainID: big.NewInt(1),
L2ChainID: big.NewInt(10),
BatchInboxAddress: common.HexToAddress("0xff00000000000000000000000000000000000010"),
// moose: Update this during migration
DepositContractAddress: common.HexToAddress("0x"),
// moose: Update this during migration
L1SystemConfigAddress: common.HexToAddress("0x"),
RegolithTime: u64Ptr(0),
BlockTime: 2,
MaxSequencerDrift: 600,
SeqWindowSize: 3600,
ChannelTimeout: 300,
L1ChainID: big.NewInt(1),
L2ChainID: big.NewInt(10),
BatchInboxAddress: common.HexToAddress("0xff00000000000000000000000000000000000010"),
DepositContractAddress: common.HexToAddress("0xbEb5Fc579115071764c7423A4f12eDde41f106Ed"),
L1SystemConfigAddress: common.HexToAddress("0x229047fed2591dbec1eF1118d64F7aF3dB9EB290"),
RegolithTime: u64Ptr(0),
}
var Goerli = rollup.Config{
......
......@@ -4,9 +4,7 @@ import (
"context"
"net"
"os"
"os/signal"
"strconv"
"syscall"
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-node/cmd/doc"
......@@ -25,6 +23,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/version"
opservice "github.com/ethereum-optimism/optimism/op-service"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/opio"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
)
......@@ -162,14 +161,7 @@ func RollupNodeMain(ctx *cli.Context) error {
defer pprofCancel()
}
interruptChannel := make(chan os.Signal, 1)
signal.Notify(interruptChannel, []os.Signal{
os.Interrupt,
os.Kill,
syscall.SIGTERM,
syscall.SIGQUIT,
}...)
<-interruptChannel
opio.BlockOnInterrupts()
return nil
......
......@@ -7,6 +7,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-node/sources"
openum "github.com/ethereum-optimism/optimism/op-service/enum"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/urfave/cli"
......@@ -68,7 +69,7 @@ var (
L1RPCProviderKind = cli.GenericFlag{
Name: "l1.rpckind",
Usage: "The kind of RPC provider, used to inform optimal transactions receipts fetching, and thus reduce costs. Valid options: " +
EnumString[sources.RPCProviderKind](sources.RPCProviderKinds),
openum.EnumString(sources.RPCProviderKinds),
EnvVar: prefixEnvVar("L1_RPC_KIND"),
Value: func() *sources.RPCProviderKind {
out := sources.RPCKindBasic
......
......@@ -7,9 +7,9 @@ import (
"github.com/urfave/cli"
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
nodeflags "github.com/ethereum-optimism/optimism/op-node/flags"
"github.com/ethereum-optimism/optimism/op-node/sources"
service "github.com/ethereum-optimism/optimism/op-service"
openum "github.com/ethereum-optimism/optimism/op-service/enum"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
)
......@@ -74,7 +74,7 @@ var (
L1RPCProviderKind = cli.GenericFlag{
Name: "l1.rpckind",
Usage: "The kind of RPC provider, used to inform optimal transactions receipts fetching, and thus reduce costs. Valid options: " +
nodeflags.EnumString[sources.RPCProviderKind](sources.RPCProviderKinds),
openum.EnumString(sources.RPCProviderKinds),
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L1_RPC_KIND"),
Value: func() *sources.RPCProviderKind {
out := sources.RPCKindBasic
......
......@@ -6,10 +6,7 @@ import (
"fmt"
"math/big"
_ "net/http/pprof"
"os"
"os/signal"
"sync"
"syscall"
"time"
"github.com/ethereum/go-ethereum/accounts/abi"
......@@ -27,6 +24,7 @@ import (
opservice "github.com/ethereum-optimism/optimism/op-service"
opclient "github.com/ethereum-optimism/optimism/op-service/client"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/opio"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
......@@ -103,14 +101,7 @@ func Main(version string, cliCtx *cli.Context) error {
m.RecordInfo(version)
m.RecordUp()
interruptChannel := make(chan os.Signal, 1)
signal.Notify(interruptChannel, []os.Signal{
os.Interrupt,
os.Kill,
syscall.SIGTERM,
syscall.SIGQUIT,
}...)
<-interruptChannel
opio.BlockOnInterrupts()
cancel()
return nil
......
package flags
package enum
import (
"fmt"
"strings"
)
// Stringered wraps the string type to implement the fmt.Stringer interface.
type Stringered string
// String returns the string value.
func (s Stringered) String() string {
return string(s)
}
// StringeredList converts a list of strings to a list of Stringered.
func StringeredList(values []string) []Stringered {
var out []Stringered
for _, v := range values {
out = append(out, Stringered(v))
}
return out
}
// EnumString returns a comma-separated string of the enum values.
// This is primarily used to generate a cli flag.
func EnumString[T fmt.Stringer](values []T) string {
var out strings.Builder
for i, v := range values {
......
package enum
import (
"testing"
"github.com/stretchr/testify/require"
)
// TestEnumString_MultipleInputs tests the EnumString function with multiple inputs.
func TestEnumString_MultipleInputs(t *testing.T) {
require.Equal(t, "a, b, c", EnumString([]Stringered{"a", "b", "c"}))
}
// TestEnumString_SingleString tests the EnumString function with a single input.
func TestEnumString_SingleString(t *testing.T) {
require.Equal(t, "a", EnumString([]Stringered{"a"}))
}
// TestEnumString_EmptyString tests the EnumString function with no inputs.
func TestEnumString_EmptyString(t *testing.T) {
require.Equal(t, "", EnumString([]Stringered{}))
}
// TestStringeredList_MultipleInputs tests the StringeredList function with multiple inputs.
func TestStringeredList_MultipleInputs(t *testing.T) {
require.Equal(t, []Stringered{"a", "b", "c"}, StringeredList([]string{"a", "b", "c"}))
}
// TestStringeredList_SingleString tests the StringeredList function with a single input.
func TestStringeredList_SingleString(t *testing.T) {
require.Equal(t, []Stringered{"a"}, StringeredList([]string{"a"}))
}
// TestStringeredList_EmptyString tests the StringeredList function with no inputs.
func TestStringeredList_EmptyString(t *testing.T) {
require.Equal(t, []Stringered(nil), StringeredList([]string{}))
}
package opio
import (
"os"
"os/signal"
"syscall"
)
// DefaultInterruptSignals is a set of default interrupt signals.
var DefaultInterruptSignals = []os.Signal{
os.Interrupt,
os.Kill,
syscall.SIGTERM,
syscall.SIGQUIT,
}
// BlockOnInterrupts blocks until a SIGTERM is received.
// Passing in signals will override the default signals.
func BlockOnInterrupts(signals ...os.Signal) {
if len(signals) == 0 {
signals = DefaultInterruptSignals
}
interruptChannel := make(chan os.Signal, 1)
signal.Notify(interruptChannel, signals...)
<-interruptChannel
}
......@@ -2,6 +2,7 @@ package engine
import (
"context"
"encoding/json"
"fmt"
"math/big"
"time"
......@@ -18,6 +19,28 @@ import (
"github.com/ethereum-optimism/optimism/op-node/eth"
)
type PayloadAttributesV2 struct {
Timestamp uint64 `json:"timestamp"`
Random common.Hash `json:"prevRandao"`
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
}
func (p PayloadAttributesV2) MarshalJSON() ([]byte, error) {
type PayloadAttributes struct {
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
Random common.Hash `json:"prevRandao" gencodec:"required"`
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
}
var enc PayloadAttributes
enc.Timestamp = hexutil.Uint64(p.Timestamp)
enc.Random = p.Random
enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient
enc.Withdrawals = make([]*types.Withdrawal, 0)
return json.Marshal(&enc)
}
func DialClient(ctx context.Context, endpoint string, jwtSecret [32]byte) (client.RPC, error) {
auth := node.NewJWTAuth(jwtSecret)
......@@ -69,7 +92,7 @@ func headSafeFinalized(ctx context.Context, client client.RPC) (head *types.Bloc
func insertBlock(ctx context.Context, client client.RPC, payload *engine.ExecutableData) error {
var payloadResult *engine.PayloadStatusV1
if err := client.CallContext(ctx, &payloadResult, "engine_newPayloadV1", payload); err != nil {
if err := client.CallContext(ctx, &payloadResult, "engine_newPayloadV2", payload); err != nil {
return fmt.Errorf("failed to insert block %d: %w", payload.Number, err)
}
if payloadResult.Status != string(eth.ExecutionValid) {
......@@ -80,7 +103,7 @@ func insertBlock(ctx context.Context, client client.RPC, payload *engine.Executa
func updateForkchoice(ctx context.Context, client client.RPC, head, safe, finalized common.Hash) error {
var post engine.ForkChoiceResponse
if err := client.CallContext(ctx, &post, "engine_forkchoiceUpdatedV1",
if err := client.CallContext(ctx, &post, "engine_forkchoiceUpdatedV2",
engine.ForkchoiceStateV1{
HeadBlockHash: head,
SafeBlockHash: safe,
......@@ -112,21 +135,17 @@ func BuildBlock(ctx context.Context, client client.RPC, status *StatusData, sett
}
}
var pre engine.ForkChoiceResponse
if err := client.CallContext(ctx, &pre, "engine_forkchoiceUpdatedV1",
if err := client.CallContext(ctx, &pre, "engine_forkchoiceUpdatedV2",
engine.ForkchoiceStateV1{
HeadBlockHash: status.Head.Hash,
SafeBlockHash: status.Safe.Hash,
FinalizedBlockHash: status.Finalized.Hash,
}, engine.PayloadAttributes{
}, PayloadAttributesV2{
Timestamp: timestamp,
Random: settings.Random,
SuggestedFeeRecipient: settings.FeeRecipient,
// TODO: maybe use the L2 fields to hack in tx embedding CLI option?
//Transactions: nil,
//NoTxPool: false,
//GasLimit: nil,
}); err != nil {
return nil, fmt.Errorf("failed to set forkchoice with new block: %w", err)
return nil, fmt.Errorf("failed to set forkchoice when building new block: %w", err)
}
if pre.PayloadStatus.Status != string(eth.ExecutionValid) {
return nil, fmt.Errorf("pre-block forkchoice update was not valid: %v", pre.PayloadStatus.ValidationError)
......@@ -139,19 +158,19 @@ func BuildBlock(ctx context.Context, client client.RPC, status *StatusData, sett
case <-time.After(settings.BuildTime):
}
var payload *engine.ExecutableData
if err := client.CallContext(ctx, &payload, "engine_getPayloadV1", pre.PayloadID); err != nil {
var payload *engine.ExecutionPayloadEnvelope
if err := client.CallContext(ctx, &payload, "engine_getPayloadV2", pre.PayloadID); err != nil {
return nil, fmt.Errorf("failed to get payload %v, %d time after instructing engine to build it: %w", pre.PayloadID, settings.BuildTime, err)
}
if err := insertBlock(ctx, client, payload); err != nil {
if err := insertBlock(ctx, client, payload.ExecutionPayload); err != nil {
return nil, err
}
if err := updateForkchoice(ctx, client, payload.BlockHash, status.Safe.Hash, status.Finalized.Hash); err != nil {
if err := updateForkchoice(ctx, client, payload.ExecutionPayload.BlockHash, status.Safe.Hash, status.Finalized.Hash); err != nil {
return nil, err
}
return payload, nil
return payload.ExecutionPayload, nil
}
func Auto(ctx context.Context, metrics Metricer, client client.RPC, log log.Logger, shutdown <-chan struct{}, settings *BlockBuildingSettings) error {
......
pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { StdInvariant } from "forge-std/StdInvariant.sol";
import { AddressAliasHelper } from "../../vendor/AddressAliasHelper.sol";
contract AddressAliasHelper_Converter {
bool public failedRoundtrip;
/**
* @dev Allows the actor to convert L1 to L2 addresses and vice versa.
*/
function convertRoundTrip(address addr) external {
// Alias our address
address aliasedAddr = AddressAliasHelper.applyL1ToL2Alias(addr);
// Unalias our address
address undoneAliasAddr = AddressAliasHelper.undoL1ToL2Alias(aliasedAddr);
// If our round trip aliasing did not return the original result, set our state.
if (addr != undoneAliasAddr) {
failedRoundtrip = true;
}
}
}
contract AddressAliasHelper_AddressAliasing_Invariant is StdInvariant, Test {
AddressAliasHelper_Converter internal actor;
function setUp() public {
// Create a converter actor.
actor = new AddressAliasHelper_Converter();
targetContract(address(actor));
bytes4[] memory selectors = new bytes4[](1);
selectors[0] = actor.convertRoundTrip.selector;
FuzzSelector memory selector = FuzzSelector({ addr: address(actor), selectors: selectors });
targetSelector(selector);
}
/**
* @custom:invariant Address aliases are always able to be undone.
*
* Asserts that an address that has been aliased with `applyL1ToL2Alias` can always
* be unaliased with `undoL1ToL2Alias`.
*/
function invariant_round_trip_aliasing() external {
// ASSERTION: The round trip aliasing done in testRoundTrip(...) should never fail.
assertEq(actor.failedRoundtrip(), false);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { StdUtils } from "forge-std/StdUtils.sol";
import { Test } from "forge-std/Test.sol";
import { Vm } from "forge-std/Vm.sol";
import { StdInvariant } from "forge-std/StdInvariant.sol";
import { Burn } from "../../libraries/Burn.sol";
contract Burn_EthBurner is StdUtils {
Vm internal vm;
bool public failedEthBurn;
constructor(Vm _vm) {
vm = _vm;
}
/**
* @notice Takes an integer amount of eth to burn through the Burn library and
* updates the contract state if an incorrect amount of eth moved from the contract
*/
function burnEth(uint256 _value) external {
uint256 preBurnvalue = bound(_value, 0, type(uint128).max);
// Give the burner some ether for gas being used
vm.deal(address(this), preBurnvalue);
// cache the contract's eth balance
uint256 preBurnBalance = address(this).balance;
uint256 value = bound(preBurnvalue, 0, preBurnBalance);
// execute a burn of _value eth
Burn.eth(value);
// check that exactly value eth was transfered from the contract
unchecked {
if (address(this).balance != preBurnBalance - value) {
failedEthBurn = true;
}
}
}
}
contract Burn_BurnEth_Invariant is StdInvariant, Test {
Burn_EthBurner internal actor;
function setUp() public {
// Create a Eth burner actor.
actor = new Burn_EthBurner(vm);
targetContract(address(actor));
bytes4[] memory selectors = new bytes4[](1);
selectors[0] = actor.burnEth.selector;
FuzzSelector memory selector = FuzzSelector({ addr: address(actor), selectors: selectors });
targetSelector(selector);
}
/**
* @custom:invariant `eth(uint256)` always burns the exact amount of eth passed.
*
* Asserts that when `Burn.eth(uint256)` is called, it always burns the exact amount
* of ETH passed to the function.
*/
function invariant_burn_eth() external {
// ASSERTION: The amount burned should always match the amount passed exactly
assertEq(actor.failedEthBurn(), false);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { StdUtils } from "forge-std/StdUtils.sol";
import { Test } from "forge-std/Test.sol";
import { Vm } from "forge-std/Vm.sol";
import { StdInvariant } from "forge-std/StdInvariant.sol";
import { Burn } from "../../libraries/Burn.sol";
contract Burn_GasBurner is StdUtils {
Vm internal vm;
bool public failedGasBurn;
constructor(Vm _vm) {
vm = _vm;
}
/**
* @notice Takes an integer amount of gas to burn through the Burn library and
* updates the contract state if at least that amount of gas was not burned
* by the library
*/
function burnGas(uint256 _value) external {
// cap the value to the max resource limit
uint256 MAX_RESOURCE_LIMIT = 8_000_000;
uint256 value = bound(_value, 0, MAX_RESOURCE_LIMIT);
// cache the contract's current remaining gas
uint256 preBurnGas = gasleft();
// execute the gas burn
Burn.gas(value);
// cache the remaining gas post burn
uint256 postBurnGas = gasleft();
// check that at least value gas was burnt (and that there was no underflow)
unchecked {
if (postBurnGas - preBurnGas <= value && preBurnGas - value > preBurnGas) {
failedGasBurn = true;
}
}
}
}
contract Burn_BurnGas_Invariant is StdInvariant, Test {
Burn_GasBurner internal actor;
function setUp() public {
// Create a gas burner actor.
actor = new Burn_GasBurner(vm);
targetContract(address(actor));
bytes4[] memory selectors = new bytes4[](1);
selectors[0] = actor.burnGas.selector;
FuzzSelector memory selector = FuzzSelector({ addr: address(actor), selectors: selectors });
targetSelector(selector);
}
/**
* @custom:invariant `gas(uint256)` always burns at least the amount of gas passed.
*
* Asserts that when `Burn.gas(uint256)` is called, it always burns at least the amount
* of gas passed to the function.
*/
function invariant_burn_gas() external {
// ASSERTION: The amount burned should always match the amount passed exactly
assertEq(actor.failedGasBurn(), false);
}
}
{
"finalSystemOwner": "0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A",
"controller": "0x78339d822c23d943e4a2d4c3dd5408f66e6d662d",
"portalGuardian": "0x78339d822c23d943e4a2d4c3dd5408f66e6d662d",
"l1StartingBlockTag": "0x126e52a0cc0ae18948f567ee9443f4a8f0db67c437706e35baee424eb314a0d0",
"controller": "0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A",
"portalGuardian": "0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A",
"l1StartingBlockTag": "",
"l1ChainID": 1,
"l2ChainID": 10,
"l2BlockTime": 2,
"maxSequencerDrift": 600,
"sequencerWindowSize": 3600,
"channelTimeout": 300,
"p2pSequencerAddress": "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65",
"p2pSequencerAddress": "0xAAAA45d9549EDA09E70937013520214382Ffc4A2",
"batchInboxAddress": "0xff00000000000000000000000000000000000010",
"batchSenderAddress": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"l2OutputOracleSubmissionInterval": 20,
"l2OutputOracleStartingTimestamp": 1679069195,
"l2OutputOracleStartingBlockNumber": 79149704,
"l2OutputOracleProposer": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC",
"l2OutputOracleChallenger": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC",
"finalizationPeriodSeconds": 2,
"proxyAdminOwner": "0x90F79bf6EB2c4f870365E785982E1f101E93b906",
"batchSenderAddress": "0x6887246668a3b87F54DeB3b94Ba47a6f63F32985",
"l2OutputOracleSubmissionInterval": 1800,
"l2OutputOracleStartingTimestamp": 0,
"l2OutputOracleStartingBlockNumber": 0,
"l2OutputOracleProposer": "0x473300df21D047806A082244b417f96b32f13A33",
"l2OutputOracleChallenger": "0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A",
"finalizationPeriodSeconds": 604800,
"proxyAdminOwner": "0x7871d1187A97cbbE40710aC119AA3d412944e4Fe",
"baseFeeVaultRecipient": "0xa3d596EAfaB6B13Ab18D40FaE1A962700C84ADEa",
"l1FeeVaultRecipient": "0xa3d596EAfaB6B13Ab18D40FaE1A962700C84ADEa",
"sequencerFeeVaultRecipient": "0xa3d596EAfaB6B13Ab18D40FaE1A962700C84ADEa",
"governanceTokenName": "Optimism",
"governanceTokenSymbol": "OP",
"governanceTokenOwner": "0x90F79bf6EB2c4f870365E785982E1f101E93b906",
"governanceTokenOwner": "0x5C4e7Ba1E219E47948e6e3F55019A647bA501005",
"l2GenesisBlockGasLimit": "0x1c9c380",
"l2GenesisBlockCoinbase": "0x4200000000000000000000000000000000000011",
"l2GenesisBlockBaseFeePerGas": "0x3b9aca00",
"gasPriceOracleOverhead": 2100,
"gasPriceOracleScalar": 1000000,
"gasPriceOracleOverhead": 188,
"gasPriceOracleScalar": 684000,
"eip1559Denominator": 50,
"eip1559Elasticity": 10,
"eip1559Elasticity": 6,
"l2GenesisRegolithTimeOffset": "0x0"
}
......@@ -22,6 +22,5 @@ import mainnetJson from './mainnet.json'
//
// The following role is assigned to the Mint Manager contract:
// - governanceTokenOwner
const config: DeployConfig = mainnetJson
export default config
export default mainnetJson satisfies DeployConfig
......@@ -9,13 +9,9 @@ const deployFn: DeployFunction = async (hre) => {
throw new Error(
'L2OutputOracle deployment: l2BlockTime must be greater than 0'
)
} else if (
hre.deployConfig.l2OutputOracleSubmissionInterval <=
hre.deployConfig.l2BlockTime
) {
throw new Error(
'L2OutputOracle deployment: submissionInterval must be greater than the l2BlockTime'
)
}
if (hre.deployConfig.l2OutputOracleSubmissionInterval === 0) {
throw new Error('L2OutputOracle deployment: submissionInterval cannot be 0')
}
await deploy({
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
# `AddressAliasHelper` Invariants
## Address aliases are always able to be undone.
**Test:** [`AddressAliasHelper.t.sol#L48`](../contracts/test/invariants/AddressAliasHelper.t.sol#L48)
Asserts that an address that has been aliased with `applyL1ToL2Alias` can always be unaliased with `undoL1ToL2Alias`.
# `Burn.Eth` Invariants
## `eth(uint256)` always burns the exact amount of eth passed.
**Test:** [`Burn.Eth.t.sol#L68`](../contracts/test/invariants/Burn.Eth.t.sol#L68)
Asserts that when `Burn.eth(uint256)` is called, it always burns the exact amount of ETH passed to the function.
# `Burn.Gas` Invariants
## `gas(uint256)` always burns at least the amount of gas passed.
**Test:** [`Burn.Gas.t.sol#L68`](../contracts/test/invariants/Burn.Gas.t.sol#L68)
Asserts that when `Burn.gas(uint256)` is called, it always burns at least the amount of gas passed to the function.
......@@ -6,7 +6,10 @@ This directory contains documentation for all defined invariant tests within `co
<!-- START autoTOC -->
## Table of Contents
- [AddressAliasHelper](./AddressAliasHelper.md)
- [AddressAliasing](./AddressAliasing.md)
- [Burn.Eth](./Burn.Eth.md)
- [Burn.Gas](./Burn.Gas.md)
- [Burn](./Burn.md)
- [CrossDomainMessenger](./CrossDomainMessenger.md)
- [Encoding](./Encoding.md)
......
......@@ -38,10 +38,15 @@ const checkPredeploys = async (
predeploys.ProxyAdmin,
])
// We only check predeploys 0x420..00 through 0x420..FF on the mainnet network to
// reduce the probability of an RPC timeout. After the migration, all predeploys
// from 0x420..00 through 0x420..07FF should be checked.
const maxCheck = hre.network.name === 'mainnet' ? 256 : 2048
const codeReq = []
const slotReq = []
// First loop for requests
for (let i = 0; i < 2048; i++) {
for (let i = 0; i < maxCheck; i++) {
const num = hre.ethers.utils.hexZeroPad('0x' + i.toString(16), 2)
const addr = hre.ethers.utils.getAddress(
hre.ethers.utils.hexConcat([prefix, num])
......@@ -57,7 +62,7 @@ const checkPredeploys = async (
const slotRes = await Promise.all(slotReq)
// Second loop for response checks
for (let i = 0; i < 2048; i++) {
for (let i = 0; i < maxCheck; i++) {
const num = hre.ethers.utils.hexZeroPad('0x' + i.toString(16), 2)
const addr = hre.ethers.utils.getAddress(
hre.ethers.utils.hexConcat([prefix, num])
......@@ -79,7 +84,7 @@ const checkPredeploys = async (
throw new Error(`incorrect admin slot in ${addr}`)
}
if (i % 200 === 0) {
if (i % (maxCheck / 4) === 0) {
console.log(`Checked through ${addr}`)
}
}
......@@ -186,7 +191,16 @@ const checkGenesisMagic = async (
const L2OutputOracle = new hre.ethers.Contract(address, [abi], l1Provider)
startingBlockNumber = await L2OutputOracle.startingBlockNumber()
// In the migration, the L2OutputOracle proxy is not yet initialized when we
// want to run this script. Fall back on the local config if we get an error
// fetching the starting block number.
try {
startingBlockNumber = await L2OutputOracle.startingBlockNumber()
} catch (e) {
console.log(`Error fetching startingBlockNumber:\n${e.message}`)
console.log('Falling back to local config.')
startingBlockNumber = hre.deployConfig.l2OutputOracleStartingBlockNumber
}
} else {
// We do not have a connection to the L1 chain, use the local config
// The `--network` flag must be set to the L1 network
......@@ -200,7 +214,9 @@ const checkGenesisMagic = async (
const extradata = block.extraData
if (extradata !== magic) {
throw new Error('magic value in extradata does not match')
throw new Error(
`magic value in extradata does not match: got ${extradata}, expected ${magic}`
)
}
}
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment