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

Merge branch 'develop' into refactor/invariants-aliasing

parents 57b0e3b6 57ca63e4
......@@ -190,6 +190,6 @@ require (
nhooyr.io/websocket v1.8.7 // indirect
)
replace github.com/ethereum/go-ethereum v1.11.6 => github.com/ethereum-optimism/op-geth v1.101105.2-0.20230502202351-9cc072e922f6
replace github.com/ethereum/go-ethereum v1.11.6 => github.com/ethereum-optimism/op-geth v1.101105.2-0.20230526154603-bdab05ca786f
//replace github.com/ethereum/go-ethereum v1.11.6 => ../go-ethereum
......@@ -151,8 +151,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs=
github.com/ethereum-optimism/op-geth v1.101105.2-0.20230502202351-9cc072e922f6 h1:Fh9VBmDCwjVn8amx1Dfrx+hIh16C/FDkS17EN25MGO8=
github.com/ethereum-optimism/op-geth v1.101105.2-0.20230502202351-9cc072e922f6/go.mod h1:X9t7oeerFMU9/zMIjZKT/jbIca+O05QqtBTLjL+XVeA=
github.com/ethereum-optimism/op-geth v1.101105.2-0.20230526154603-bdab05ca786f h1:+yZN8K/4AIN5f+gazMwZAeqzDG2EL2GydMrccjnEK+A=
github.com/ethereum-optimism/op-geth v1.101105.2-0.20230526154603-bdab05ca786f/go.mod h1:X9t7oeerFMU9/zMIjZKT/jbIca+O05QqtBTLjL+XVeA=
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ=
......
docker-compose.dev.yml
.env
# @eth-optimism/indexer
## Getting started
### Run indexer vs goerli
- install docker
- `cp example.env .env`
- fill in .env
- run `docker-compose up` to start the indexer vs optimism goerli network
### Run indexer with go
See the flags in `flags.go` for reference of what command line flags to pass to `go run`
### Run indexer vs devnet
TODO add indexer to the optimism devnet compose file (previously removed for breaking CI)
### Run indexer vs a custom configuration
`docker-compose.dev.yml` is git ignored. Fill in your own docker-compose file here.
version: '3.8'
services:
postgres:
image: postgres:latest
environment:
- POSTGRES_USER=db_username
- POSTGRES_PASSWORD=db_password
- POSTGRES_DB=db_name
- PGDATA=/data/postgres
- POSTGRES_HOST_AUTH_METHOD=trust
healthcheck:
test: [ "CMD-SHELL", "pg_isready -q -U db_username -d db_name" ]
ports:
- "5434:5432"
volumes:
- postgres_data:/data/postgres
indexer:
build:
context: ..
dockerfile: indexer/Dockerfile
healthcheck:
test: wget localhost:8080/healthz -q -O - > /dev/null 2>&1
environment:
# Note that you must index goerli with INDEXER_BEDROCK=false first, then
# reindex with INDEXER_BEDROCK=true or seed the database
- INDEXER_BEDROCK=${INDEXER_BEDROCK_GOERLI:-true}
- INDEXER_BUILD_ENV=${INDEXER_BUILD_ENV:-development}
- INDEXER_DB_PORT=${INDEXER_DB_PORT:-5432}
- INDEXER_DB_USER=${INDEXER_DB_USER:-db_username}
- INDEXER_DB_PASSWORD=${INDEXER_DB_PASSWORD:-db_password}
- INDEXER_DB_NAME=${INDEXER_DB_NAME:-db_name}
- INDEXER_DB_HOST=${INDEXER_DB_HOST:-postgres}
- INDEXER_CHAIN_ID=${INDEXER_CHAIN_ID:-5}
- INDEXER_L1_ETH_RPC=$INDEXER_L1_ETH_RPC
- INDEXER_L2_ETH_RPC=$INDEXER_L2_ETH_RPC
- 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
ports:
- 8080:8080
depends_on:
postgres:
condition: service_healthy
volumes:
postgres_data:
# Fill me in with goerli uris and run docker-compose up to run indexer vs goerli
INDEXER_L1_ETH_RPC=FILL_ME_IN
INDEXER_L2_ETH_RPC=FILL_ME_IN
This diff is collapsed.
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)
})
}
}
......@@ -212,7 +212,14 @@ func TestGPOParamsChange(gt *testing.T) {
receipt := alice.LastTxReceipt(t)
require.Equal(t, basefee, receipt.L1GasPrice, "L1 gas price matches basefee of L1 origin")
require.NotZero(t, receipt.L1GasUsed, "L2 tx uses L1 data")
l1Cost := types.L1Cost(receipt.L1GasUsed.Uint64(), basefee, big.NewInt(2100), big.NewInt(1000_000))
require.Equal(t,
new(big.Float).Mul(
new(big.Float).SetInt(basefee),
new(big.Float).Mul(new(big.Float).SetInt(receipt.L1GasUsed), receipt.FeeScalar),
),
new(big.Float).SetInt(receipt.L1Fee), "fee field in receipt matches gas used times scalar times basefee")
// receipt.L1GasUsed includes the overhead already, so subtract that before passing it into the L1 cost func
l1Cost := types.L1Cost(receipt.L1GasUsed.Uint64()-2100, basefee, big.NewInt(2100), big.NewInt(1000_000))
require.Equal(t, l1Cost, receipt.L1Fee, "L1 fee is computed with standard GPO params")
require.Equal(t, "1", receipt.FeeScalar.String(), "1000_000 divided by 6 decimals = float(1)")
......@@ -268,7 +275,8 @@ func TestGPOParamsChange(gt *testing.T) {
receipt = alice.LastTxReceipt(t)
require.Equal(t, basefeeGPOUpdate, receipt.L1GasPrice, "L1 gas price matches basefee of L1 origin")
require.NotZero(t, receipt.L1GasUsed, "L2 tx uses L1 data")
l1Cost = types.L1Cost(receipt.L1GasUsed.Uint64(), basefeeGPOUpdate, big.NewInt(1000), big.NewInt(2_300_000))
// subtract overhead from L1GasUsed receipt field, types.L1Cost applies it again
l1Cost = types.L1Cost(receipt.L1GasUsed.Uint64()-1000, basefeeGPOUpdate, big.NewInt(1000), big.NewInt(2_300_000))
require.Equal(t, l1Cost, receipt.L1Fee, "L1 fee is computed with updated GPO params")
require.Equal(t, "2.3", receipt.FeeScalar.String(), "2_300_000 divided by 6 decimals = float(2.3)")
......@@ -288,7 +296,8 @@ func TestGPOParamsChange(gt *testing.T) {
receipt = alice.LastTxReceipt(t)
require.Equal(t, basefee, receipt.L1GasPrice, "L1 gas price matches basefee of L1 origin")
require.NotZero(t, receipt.L1GasUsed, "L2 tx uses L1 data")
l1Cost = types.L1Cost(receipt.L1GasUsed.Uint64(), basefee, big.NewInt(1000), big.NewInt(2_300_000))
// subtract overhead from L1GasUsed receipt field, types.L1Cost applies it again
l1Cost = types.L1Cost(receipt.L1GasUsed.Uint64()-1000, basefee, big.NewInt(1000), big.NewInt(2_300_000))
require.Equal(t, l1Cost, receipt.L1Fee, "L1 fee is computed with updated GPO params")
require.Equal(t, "2.3", receipt.FeeScalar.String(), "2_300_000 divided by 6 decimals = float(2.3)")
}
......
......@@ -100,6 +100,126 @@ func TestInvalidDepositInFCU(t *testing.T) {
require.Equal(t, 0, balance.Cmp(common.Big0))
}
// TestGethOnlyPendingBlockIsLatest walks through an engine-API block building job,
// and asserts that the pending block is set to match the latest block at every stage,
// for stability and tx-privacy.
func TestGethOnlyPendingBlockIsLatest(t *testing.T) {
InitParallel(t)
cfg := DefaultSystemConfig(t)
cfg.DeployConfig.FundDevAccounts = true
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
opGeth, err := NewOpGeth(t, ctx, &cfg)
require.NoError(t, err)
defer opGeth.Close()
checkPending := func(stage string, number uint64) {
// TODO(CLI-4044): pending-block ID change
pendingBlock, err := opGeth.L2Client.BlockByNumber(ctx, big.NewInt(-1))
require.NoError(t, err, "failed to fetch pending block at stage "+stage)
require.Equal(t, number, pendingBlock.NumberU64(), "pending block must have expected number")
latestBlock, err := opGeth.L2Client.BlockByNumber(ctx, nil)
require.NoError(t, err, "failed to fetch latest block at stage "+stage)
require.Equal(t, pendingBlock.Hash(), latestBlock.Hash(), "pending and latest do not match at stage "+stage)
}
checkPending("genesis", 0)
amount := big.NewInt(42) // send 42 wei
aliceStartBalance, err := opGeth.L2Client.PendingBalanceAt(ctx, cfg.Secrets.Addresses().Alice)
require.NoError(t, err)
require.True(t, aliceStartBalance.Cmp(big.NewInt(0)) > 0, "alice must be funded")
checkPendingBalance := func() {
pendingBalance, err := opGeth.L2Client.PendingBalanceAt(ctx, cfg.Secrets.Addresses().Alice)
require.NoError(t, err)
require.Equal(t, pendingBalance, aliceStartBalance, "pending balance must still be the same")
}
startBlock, err := opGeth.L2Client.BlockByNumber(ctx, nil)
require.NoError(t, err)
signer := types.LatestSigner(opGeth.L2ChainConfig)
tip := big.NewInt(7_000_000_000) // 7 gwei tip
tx := types.MustSignNewTx(cfg.Secrets.Alice, signer, &types.DynamicFeeTx{
ChainID: big.NewInt(int64(cfg.DeployConfig.L2ChainID)),
Nonce: 0,
GasTipCap: tip,
GasFeeCap: new(big.Int).Add(startBlock.BaseFee(), tip),
Gas: 1_000_000,
To: &cfg.Secrets.Addresses().Bob,
Value: amount,
Data: nil,
})
require.NoError(t, opGeth.L2Client.SendTransaction(ctx, tx), "send tx to make pending work different")
checkPending("prepared", 0)
rpcClient, err := opGeth.node.Attach()
require.NoError(t, err)
defer rpcClient.Close()
// Wait for tx to be in tx-pool, for it to be picked up in block building
var txPoolStatus struct {
Pending hexutil.Uint64 `json:"pending"`
}
for i := 0; i < 5; i++ {
require.NoError(t, rpcClient.CallContext(ctx, &txPoolStatus, "txpool_status"))
if txPoolStatus.Pending == 0 {
time.Sleep(time.Second)
} else {
break
}
}
require.NotZero(t, txPoolStatus.Pending, "must have pending tx in pool")
checkPending("in-pool", 0)
checkPendingBalance()
// start building a block
attrs, err := opGeth.CreatePayloadAttributes()
require.NoError(t, err)
attrs.NoTxPool = false // we want to include a tx
fc := eth.ForkchoiceState{
HeadBlockHash: opGeth.L2Head.BlockHash,
SafeBlockHash: opGeth.L2Head.BlockHash,
}
res, err := opGeth.l2Engine.ForkchoiceUpdate(ctx, &fc, attrs)
require.NoError(t, err)
checkPending("building", 0)
checkPendingBalance()
// Now we have to wait until the block-building job picks up the tx from the tx-pool.
// See go routine that spins up in buildPayload() func in payload_building.go in miner package.
// We can't check it, we don't want to finish block-building prematurely, and so we have to wait.
time.Sleep(time.Second * 4) // conservatively wait 4 seconds, CI might lag during block building.
// retrieve the block
payload, err := opGeth.l2Engine.GetPayload(ctx, *res.PayloadID)
require.NoError(t, err)
checkPending("retrieved", 0)
require.Len(t, payload.Transactions, 2, "must include L1 info tx and tx from alice")
checkPendingBalance()
// process the block
status, err := opGeth.l2Engine.NewPayload(ctx, payload)
require.NoError(t, err)
require.Equal(t, eth.ExecutionValid, status.Status)
checkPending("processed", 0)
checkPendingBalance()
// make the block canonical
fc = eth.ForkchoiceState{
HeadBlockHash: payload.BlockHash,
SafeBlockHash: payload.BlockHash,
}
res, err = opGeth.l2Engine.ForkchoiceUpdate(ctx, &fc, nil)
require.NoError(t, err)
require.Equal(t, eth.ExecutionValid, res.PayloadStatus.Status)
checkPending("canonical", 1)
}
func TestPreregolith(t *testing.T) {
InitParallel(t)
futureTimestamp := hexutil.Uint64(4)
......
......@@ -679,7 +679,7 @@ func (sys *System) newMockNetPeer() (host.Host, error) {
_ = ps.AddPubKey(p, sk.GetPublic())
ds := sync.MutexWrap(ds.NewMapDatastore())
eps, err := store.NewExtendedPeerstore(context.Background(), log.Root(), clock.SystemClock, ps, ds)
eps, err := store.NewExtendedPeerstore(context.Background(), log.Root(), clock.SystemClock, ps, ds, 24*time.Hour)
if err != nil {
return nil, err
}
......
......@@ -241,12 +241,14 @@ func TestPendingGasLimit(t *testing.T) {
cfg.GethOptions["sequencer"] = []GethOption{
func(ethCfg *ethconfig.Config, nodeCfg *node.Config) error {
ethCfg.Miner.GasCeil = 10_000_000
ethCfg.Miner.RollupComputePendingBlock = true
return nil
},
}
cfg.GethOptions["verifier"] = []GethOption{
func(ethCfg *ethconfig.Config, nodeCfg *node.Config) error {
ethCfg.Miner.GasCeil = 9_000_000
ethCfg.Miner.RollupComputePendingBlock = true
return nil
},
}
......@@ -1230,6 +1232,14 @@ func TestFees(t *testing.T) {
require.Nil(t, err)
require.Equal(t, l1Fee, gpoL1Fee, "l1 fee mismatch")
require.Equal(t, receipt.L1Fee, l1Fee, "l1 fee in receipt is correct")
require.Equal(t,
new(big.Float).Mul(
new(big.Float).SetInt(l1Header.BaseFee),
new(big.Float).Mul(new(big.Float).SetInt(receipt.L1GasUsed), receipt.FeeScalar),
),
new(big.Float).SetInt(receipt.L1Fee), "fee field in receipt matches gas used times scalar times basefee")
// Calculate total fee
baseFeeRecipientDiff.Add(baseFeeRecipientDiff, coinbaseDiff)
totalFee := new(big.Int).Add(baseFeeRecipientDiff, l1FeeRecipientDiff)
......@@ -1371,3 +1381,45 @@ func latestBlock(t *testing.T, client *ethclient.Client) uint64 {
require.Nil(t, err, "Error getting latest block")
return blockAfter
}
// TestPendingBlockIsLatest tests that we serve the latest block as pending block
func TestPendingBlockIsLatest(t *testing.T) {
InitParallel(t)
cfg := DefaultSystemConfig(t)
sys, err := cfg.Start()
require.Nil(t, err, "Error starting up system")
defer sys.Close()
l2Seq := sys.Clients["sequencer"]
t.Run("block", func(t *testing.T) {
for i := 0; i < 10; i++ {
// TODO(CLI-4044): pending-block ID change
pending, err := l2Seq.BlockByNumber(context.Background(), big.NewInt(-1))
require.NoError(t, err)
latest, err := l2Seq.BlockByNumber(context.Background(), nil)
require.NoError(t, err)
if pending.NumberU64() == latest.NumberU64() {
require.Equal(t, pending.Hash(), latest.Hash(), "pending must exactly match latest block")
return
}
// re-try until we have the same number, as the requests are not an atomic bundle, and the sequencer may create a block.
}
t.Fatal("failed to get pending block with same number as latest block")
})
t.Run("header", func(t *testing.T) {
for i := 0; i < 10; i++ {
// TODO(CLI-4044): pending-block ID change
pending, err := l2Seq.HeaderByNumber(context.Background(), big.NewInt(-1))
require.NoError(t, err)
latest, err := l2Seq.HeaderByNumber(context.Background(), nil)
require.NoError(t, err)
if pending.Number.Uint64() == latest.Number.Uint64() {
require.Equal(t, pending.Hash(), latest.Hash(), "pending must exactly match latest header")
return
}
// re-try until we have the same number, as the requests are not an atomic bundle, and the sequencer may create a block.
}
t.Fatal("failed to get pending header with same number as latest header")
})
}
......@@ -27,10 +27,10 @@ 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,
......@@ -40,10 +40,8 @@ var Mainnet = rollup.Config{
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"),
DepositContractAddress: common.HexToAddress("0xbEb5Fc579115071764c7423A4f12eDde41f106Ed"),
L1SystemConfigAddress: common.HexToAddress("0x229047fed2591dbec1eF1118d64F7aF3dB9EB290"),
RegolithTime: u64Ptr(0),
}
......
......@@ -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
......
......@@ -141,7 +141,8 @@ func (conf *Config) Host(log log.Logger, reporter metrics.Reporter, metrics Host
return nil, fmt.Errorf("failed to open peerstore: %w", err)
}
ps, err := store.NewExtendedPeerstore(context.Background(), log, clock.SystemClock, basePs, conf.Store)
peerScoreParams := conf.PeerScoringParams()
ps, err := store.NewExtendedPeerstore(context.Background(), log, clock.SystemClock, basePs, conf.Store, peerScoreParams.RetainScore)
if err != nil {
return nil, fmt.Errorf("failed to open extended peerstore: %w", err)
}
......
......@@ -76,7 +76,7 @@ func getNetHosts(testSuite *PeerScoresTestSuite, ctx context.Context, n int) []h
log := testlog.Logger(testSuite.T(), log.LvlError)
for i := 0; i < n; i++ {
swarm := tswarm.GenSwarm(testSuite.T())
eps, err := store.NewExtendedPeerstore(ctx, log, clock.SystemClock, swarm.Peerstore(), sync.MutexWrap(ds.NewMapDatastore()))
eps, err := store.NewExtendedPeerstore(ctx, log, clock.SystemClock, swarm.Peerstore(), sync.MutexWrap(ds.NewMapDatastore()), 1*time.Hour)
netw := &customPeerstoreNetwork{swarm, eps}
require.NoError(testSuite.T(), err)
h := bhost.NewBlankHost(netw)
......@@ -99,7 +99,7 @@ func newGossipSubs(testSuite *PeerScoresTestSuite, ctx context.Context, hosts []
dataStore := sync.MutexWrap(ds.NewMapDatastore())
peerStore, err := pstoreds.NewPeerstore(context.Background(), dataStore, pstoreds.DefaultOpts())
require.NoError(testSuite.T(), err)
extPeerStore, err := store.NewExtendedPeerstore(context.Background(), logger, clock.SystemClock, peerStore, dataStore)
extPeerStore, err := store.NewExtendedPeerstore(context.Background(), logger, clock.SystemClock, peerStore, dataStore, 1*time.Hour)
require.NoError(testSuite.T(), err)
scorer := NewScorer(
......
......@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"time"
"github.com/ethereum-optimism/optimism/op-service/clock"
"github.com/ethereum/go-ethereum/log"
......@@ -19,12 +20,12 @@ type extendedStore struct {
*ipBanBook
}
func NewExtendedPeerstore(ctx context.Context, logger log.Logger, clock clock.Clock, ps peerstore.Peerstore, store ds.Batching) (ExtendedPeerstore, error) {
func NewExtendedPeerstore(ctx context.Context, logger log.Logger, clock clock.Clock, ps peerstore.Peerstore, store ds.Batching, scoreRetention time.Duration) (ExtendedPeerstore, error) {
cab, ok := peerstore.GetCertifiedAddrBook(ps)
if !ok {
return nil, errors.New("peerstore should also be a certified address book")
}
sb, err := newScoreBook(ctx, logger, clock, store)
sb, err := newScoreBook(ctx, logger, clock, store, scoreRetention)
if err != nil {
return nil, fmt.Errorf("create scorebook: %w", err)
}
......
......@@ -102,6 +102,9 @@ func (d *recordsBook[K, V]) deleteRecord(key K) error {
func (d *recordsBook[K, V]) getRecord(key K) (v V, err error) {
if val, ok := d.cache.Get(key); ok {
if d.hasExpired(val) {
return v, UnknownRecordErr
}
return val, nil
}
data, err := d.store.Get(d.ctx, d.dsKey(key))
......@@ -114,6 +117,9 @@ func (d *recordsBook[K, V]) getRecord(key K) (v V, err error) {
if err := v.UnmarshalBinary(data); err != nil {
return v, fmt.Errorf("invalid value for key %v: %w", key, err)
}
if d.hasExpired(v) {
return v, UnknownRecordErr
}
d.cache.Add(key, v)
return v, nil
}
......@@ -142,9 +148,9 @@ func (d *recordsBook[K, V]) SetRecord(key K, diff recordDiff[V]) error {
}
// prune deletes entries from the store that are older than the configured prune expiration.
// Note that the expiry period is not a strict TTL. Entries that are eligible for deletion may still be present
// either because the prune function hasn't yet run or because they are still preserved in the in-memory cache after
// having been deleted from the database.
// Entries that are eligible for deletion may still be present either because the prune function hasn't yet run or
// because they are still preserved in the in-memory cache after having been deleted from the database.
// Such expired entries are filtered out in getRecord
func (d *recordsBook[K, V]) prune() error {
results, err := d.store.Query(d.ctx, query.Query{
Prefix: d.dsBaseKey.String(),
......@@ -168,7 +174,7 @@ func (d *recordsBook[K, V]) prune() error {
if err := v.UnmarshalBinary(result.Value); err != nil {
return err
}
if v.LastUpdated().Add(d.recordExpiry).Before(d.clock.Now()) {
if d.hasExpired(v) {
if pending > maxPruneBatchSize {
if err := batch.Commit(d.ctx); err != nil {
return err
......@@ -191,6 +197,10 @@ func (d *recordsBook[K, V]) prune() error {
return nil
}
func (d *recordsBook[K, V]) hasExpired(v V) bool {
return v.LastUpdated().Add(d.recordExpiry).Before(d.clock.Now())
}
func (d *recordsBook[K, V]) Close() {
d.cancelFn()
d.bgTasks.Wait()
......
......@@ -13,7 +13,6 @@ import (
const (
scoreCacheSize = 100
scoreRecordExpiryPeriod = 24 * time.Hour
)
var scoresBase = ds.NewKey("/peers/scores")
......@@ -56,8 +55,8 @@ func peerIDKey(id peer.ID) ds.Key {
return ds.NewKey(base32.RawStdEncoding.EncodeToString([]byte(id)))
}
func newScoreBook(ctx context.Context, logger log.Logger, clock clock.Clock, store ds.Batching) (*scoreBook, error) {
book, err := newRecordsBook[peer.ID, *scoreRecord](ctx, logger, clock, store, scoreCacheSize, scoreRecordExpiryPeriod, scoresBase, newScoreRecord, peerIDKey)
func newScoreBook(ctx context.Context, logger log.Logger, clock clock.Clock, store ds.Batching, retain time.Duration) (*scoreBook, error) {
book, err := newRecordsBook[peer.ID, *scoreRecord](ctx, logger, clock, store, scoreCacheSize, retain, scoresBase, newScoreRecord, peerIDKey)
if err != nil {
return nil, err
}
......
......@@ -81,7 +81,7 @@ func TestPrune(t *testing.T) {
logger := testlog.Logger(t, log.LvlInfo)
store := sync.MutexWrap(ds.NewMapDatastore())
clock := clock.NewDeterministicClock(time.UnixMilli(1000))
book, err := newScoreBook(ctx, logger, clock, store)
book, err := newScoreBook(ctx, logger, clock, store, 24*time.Hour)
require.NoError(t, err)
hasScoreRecorded := func(id peer.ID) bool {
......@@ -135,7 +135,7 @@ func TestPruneMultipleBatches(t *testing.T) {
defer cancelFunc()
logger := testlog.Logger(t, log.LvlInfo)
clock := clock.NewDeterministicClock(time.UnixMilli(1000))
book, err := newScoreBook(ctx, logger, clock, sync.MutexWrap(ds.NewMapDatastore()))
book, err := newScoreBook(ctx, logger, clock, sync.MutexWrap(ds.NewMapDatastore()), 24*time.Hour)
require.NoError(t, err)
hasScoreRecorded := func(id peer.ID) bool {
......@@ -159,6 +159,31 @@ func TestPruneMultipleBatches(t *testing.T) {
}
}
// Check that scores that are eligible for pruning are not returned, even if they haven't yet been removed
func TestIgnoreOutdatedScores(t *testing.T) {
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
logger := testlog.Logger(t, log.LvlInfo)
clock := clock.NewDeterministicClock(time.UnixMilli(1000))
retentionPeriod := 24 * time.Hour
book, err := newScoreBook(ctx, logger, clock, sync.MutexWrap(ds.NewMapDatastore()), retentionPeriod)
require.NoError(t, err)
require.NoError(t, book.SetScore("a", &GossipScores{Total: 123.45}))
clock.AdvanceTime(retentionPeriod + 1)
// Not available from cache
scores, err := book.GetPeerScores("a")
require.NoError(t, err)
require.Equal(t, scores, PeerScores{})
book.book.cache.Purge()
// Not available from disk
scores, err = book.GetPeerScores("a")
require.NoError(t, err)
require.Equal(t, scores, PeerScores{})
}
func assertPeerScores(t *testing.T, store ExtendedPeerstore, id peer.ID, expected PeerScores) {
result, err := store.GetPeerScores(id)
require.NoError(t, err)
......@@ -174,8 +199,8 @@ func createPeerstoreWithBacking(t *testing.T, store *sync.MutexDatastore) Extend
ps, err := pstoreds.NewPeerstore(context.Background(), store, pstoreds.DefaultOpts())
require.NoError(t, err, "Failed to create peerstore")
logger := testlog.Logger(t, log.LvlInfo)
clock := clock.NewDeterministicClock(time.UnixMilli(100))
eps, err := NewExtendedPeerstore(context.Background(), logger, clock, ps, store)
c := clock.NewDeterministicClock(time.UnixMilli(100))
eps, err := NewExtendedPeerstore(context.Background(), logger, c, ps, store, 24*time.Hour)
require.NoError(t, err)
t.Cleanup(func() {
_ = eps.Close()
......
......@@ -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
......
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{}))
}
{
"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 source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
......@@ -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)
// 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}`
)
}
}
......
......@@ -15,6 +15,12 @@ const config: HardhatUserConfig = {
sources: './test/contracts',
},
networks: {
mainnet: {
url: process.env.L1_RPC || 'https://mainnet-l1-rehearsal.optimism.io',
accounts: [
'ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
],
},
devnetL1: {
url: 'http://localhost:8545',
accounts: [
......@@ -43,6 +49,10 @@ const config: HardhatUserConfig = {
},
],
deployments: {
mainnet: [
'../contracts/deployments/mainnet',
'../contracts-bedrock/deployments/mainnet',
],
hivenet: ['../contracts-bedrock/deployments/hivenet'],
devnetL1: ['../contracts-bedrock/deployments/devnetL1'],
goerli: [
......
......@@ -130,8 +130,8 @@ task('deposit-eth', 'Deposits ether to L2.')
contractAddrs = {
l1: {
AddressManager: Deployment__AddressManager.address,
L1CrossDomainMessenger: Deployment__L1CrossDomainMessenger,
L1StandardBridge: Deployment__L1StandardBridge,
L1CrossDomainMessenger: Deployment__L1CrossDomainMessenger.address,
L1StandardBridge: Deployment__L1StandardBridge.address,
StateCommitmentChain: ethers.constants.AddressZero,
CanonicalTransactionChain: ethers.constants.AddressZero,
BondManager: ethers.constants.AddressZero,
......
FROM golang:1.18.0-alpine3.15 as builder
FROM golang:1.20.4-alpine3.18 as builder
ARG GITCOMMIT=docker
ARG GITDATE=docker
......@@ -12,7 +12,7 @@ WORKDIR /app
RUN make proxyd
FROM alpine:3.15
FROM alpine:3.18
COPY ./proxyd/entrypoint.sh /bin/entrypoint.sh
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -93,8 +93,8 @@ backends = ["infura"]
# consensus_ban_period = "1m"
# Maximum delay for update the backend, default 30s
# consensus_max_update_threshold = "20s"
# Maximum block lag, default 50
# consensus_max_block_lag = 10
# Maximum block lag, default 8
# consensus_max_block_lag = 16
# Minimum peer count, default 3
# consensus_min_peer_count = 4
......
This diff is collapsed.
......@@ -18,7 +18,7 @@ consensus_aware = true
consensus_handler = "noop" # allow more control over the consensus poller for tests
consensus_ban_period = "1m"
consensus_max_update_threshold = "2m"
consensus_max_block_lag = 50
consensus_max_block_lag = 8
consensus_min_peer_count = 4
[rpc_method_mappings]
......
This diff is collapsed.
......@@ -10,6 +10,8 @@ import (
type RewriteContext struct {
latest hexutil.Uint64
safe hexutil.Uint64
finalized hexutil.Uint64
}
type RewriteResult uint8
......@@ -180,11 +182,13 @@ func rewriteTag(rctx RewriteContext, current string) (string, bool, error) {
}
switch *bnh.BlockNumber {
case rpc.SafeBlockNumber,
rpc.FinalizedBlockNumber,
rpc.PendingBlockNumber,
case rpc.PendingBlockNumber,
rpc.EarliestBlockNumber:
return current, false, nil
case rpc.FinalizedBlockNumber:
return rctx.finalized.String(), true, nil
case rpc.SafeBlockNumber:
return rctx.safe.String(), true, nil
case rpc.LatestBlockNumber:
return rctx.latest.String(), true, nil
default:
......
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