Commit ba647633 authored by Ethen Pociask's avatar Ethen Pociask

Merge branch 'develop' of https://github.com/epociask/optimism into...

Merge branch 'develop' of https://github.com/epociask/optimism into indexer.withdrawal_type_supplies
parents 968be3c3 38410f27
...@@ -3,7 +3,7 @@ version: 2.1 ...@@ -3,7 +3,7 @@ version: 2.1
parameters: parameters:
ci_builder_image: ci_builder_image:
type: string type: string
default: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:latest default: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:v0.27.0
orbs: orbs:
go: circleci/go@1.8.0 go: circleci/go@1.8.0
...@@ -350,7 +350,7 @@ jobs: ...@@ -350,7 +350,7 @@ jobs:
working_directory: packages/contracts-bedrock working_directory: packages/contracts-bedrock
- run: - run:
name: run tests name: run tests
command: pnpm test command: STRICT_DEPLOYMENT=false pnpm test
environment: environment:
FOUNDRY_PROFILE: ci FOUNDRY_PROFILE: ci
working_directory: packages/contracts-bedrock working_directory: packages/contracts-bedrock
...@@ -1827,6 +1827,7 @@ workflows: ...@@ -1827,6 +1827,7 @@ workflows:
name: chain-mon-docker-publish name: chain-mon-docker-publish
docker_name: chain-mon docker_name: chain-mon
docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>> docker_tags: <<pipeline.git.revision>>,<<pipeline.git.branch>>
resource_class: xlarge
publish: true publish: true
context: context:
- oplabs-gcr - oplabs-gcr
......
d85718785859dc0b5a095d2302d1a20ec06ab77a b205b6add562c778206a9edba1c0676c04a709b1
...@@ -133,10 +133,10 @@ func (db *bridgeTransactionsDB) L1LatestFinalizedBlockHeader() (*L1BlockHeader, ...@@ -133,10 +133,10 @@ func (db *bridgeTransactionsDB) L1LatestFinalizedBlockHeader() (*L1BlockHeader,
provenQuery = provenQuery.Order("l1_contract_events.timestamp DESC").Select("l1_contract_events.*") provenQuery = provenQuery.Order("l1_contract_events.timestamp DESC").Select("l1_contract_events.*")
finalizedQuery := db.gorm.Table("l2_transaction_withdrawals").Order("timestamp DESC").Limit(1) finalizedQuery := db.gorm.Table("l2_transaction_withdrawals").Order("timestamp DESC").Limit(1)
finalizedQuery = finalizedQuery.Joins("INNER JOIN l1_contract_events ON l1_contract_events.guid = l2_transaction_withdrawals.proven_l1_event_guid") finalizedQuery = finalizedQuery.Joins("INNER JOIN l1_contract_events ON l1_contract_events.guid = l2_transaction_withdrawals.finalized_l1_event_guid")
finalizedQuery = finalizedQuery.Select("l1_contract_events.*") finalizedQuery = finalizedQuery.Select("l1_contract_events.*")
relayedQuery := db.gorm.Table("l2_bridge_messages").Order("timestamp DESC") relayedQuery := db.gorm.Table("l2_bridge_messages").Order("timestamp DESC").Limit(1)
relayedQuery = relayedQuery.Joins("INNER JOIN l1_contract_events ON l1_contract_events.guid = l2_bridge_messages.relayed_message_event_guid") relayedQuery = relayedQuery.Joins("INNER JOIN l1_contract_events ON l1_contract_events.guid = l2_bridge_messages.relayed_message_event_guid")
relayedQuery = relayedQuery.Select("l1_contract_events.*") relayedQuery = relayedQuery.Select("l1_contract_events.*")
...@@ -227,49 +227,22 @@ func (db *bridgeTransactionsDB) MarkL2TransactionWithdrawalFinalizedEvent(withdr ...@@ -227,49 +227,22 @@ func (db *bridgeTransactionsDB) MarkL2TransactionWithdrawalFinalizedEvent(withdr
} }
func (db *bridgeTransactionsDB) L2LatestBlockHeader() (*L2BlockHeader, error) { func (db *bridgeTransactionsDB) L2LatestBlockHeader() (*L2BlockHeader, error) {
// L2: Latest Withdrawal, Latest L2 Header of indexed deposit epoch // L2: Latest Withdrawal
var latestWithdrawalHeader, latestL2DepositHeader *L2BlockHeader l2Query := db.gorm.Table("l2_transaction_withdrawals").Order("timestamp DESC")
l2Query = l2Query.Joins("INNER JOIN l2_contract_events ON l2_contract_events.guid = l2_transaction_withdrawals.initiated_l2_event_guid")
var withdrawHeader L2BlockHeader l2Query = l2Query.Joins("INNER JOIN l2_block_headers ON l2_block_headers.hash = l2_contract_events.block_hash")
withdrawalQuery := db.gorm.Table("l2_transaction_withdrawals").Order("timestamp DESC").Limit(1) l2Query = l2Query.Select("l2_block_headers.*")
withdrawalQuery = withdrawalQuery.Joins("INNER JOIN l2_contract_events ON l2_contract_events.guid = l2_transaction_withdrawals.initiated_l2_event_guid")
withdrawalQuery = withdrawalQuery.Joins("INNER JOIN l2_block_headers ON l2_block_headers.hash = l2_contract_events.block_hash")
result := withdrawalQuery.Select("l2_block_headers.*").Take(&withdrawHeader)
if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, result.Error
} else if !errors.Is(result.Error, gorm.ErrRecordNotFound) {
latestWithdrawalHeader = &withdrawHeader
}
// Check for any deposits that may have been included after the latest withdrawal. However, since the bridge var l2Header L2BlockHeader
// processor only inserts entries when the corresponding epoch has been indexed on both L1 and L2, we can result := l2Query.Take(&l2Header)
// simply look for the latest L2 block with at <= time of the latest L1 deposit. if result.Error != nil {
var l1Deposit L1TransactionDeposit if errors.Is(result.Error, gorm.ErrRecordNotFound) {
result = db.gorm.Table("l1_transaction_deposits").Order("timestamp DESC").Limit(1).Take(&l1Deposit) return nil, nil
if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, result.Error
} else if !errors.Is(result.Error, gorm.ErrRecordNotFound) {
var l2DepositHeader L2BlockHeader
result := db.gorm.Table("l2_block_headers").Order("timestamp DESC").Limit(1).Where("timestamp <= ?", l1Deposit.Tx.Timestamp).Take(&l2DepositHeader)
if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, result.Error
} else if !errors.Is(result.Error, gorm.ErrRecordNotFound) {
latestL2DepositHeader = &l2DepositHeader
} }
return nil, result.Error
} }
// compare return &l2Header, nil
if latestWithdrawalHeader == nil {
return latestL2DepositHeader, nil
} else if latestL2DepositHeader == nil {
return latestWithdrawalHeader, nil
}
if latestWithdrawalHeader.Timestamp >= latestL2DepositHeader.Timestamp {
return latestWithdrawalHeader, nil
} else {
return latestL2DepositHeader, nil
}
} }
func (db *bridgeTransactionsDB) L2LatestFinalizedBlockHeader() (*L2BlockHeader, error) { func (db *bridgeTransactionsDB) L2LatestFinalizedBlockHeader() (*L2BlockHeader, error) {
......
...@@ -95,7 +95,6 @@ func NewMetrics(registry *prometheus.Registry) Metricer { ...@@ -95,7 +95,6 @@ func NewMetrics(registry *prometheus.Registry) Metricer {
}), }),
latestHeight: factory.NewGaugeVec(prometheus.GaugeOpts{ latestHeight: factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: MetricsNamespace, Namespace: MetricsNamespace,
Subsystem: "l1",
Name: "height", Name: "height",
Help: "the latest processed l1 block height", Help: "the latest processed l1 block height",
}, []string{ }, []string{
......
...@@ -37,15 +37,15 @@ type RollupClient interface { ...@@ -37,15 +37,15 @@ type RollupClient interface {
// DriverSetup is the collection of input/output interfaces and configuration that the driver operates on. // DriverSetup is the collection of input/output interfaces and configuration that the driver operates on.
type DriverSetup struct { type DriverSetup struct {
Log log.Logger Log log.Logger
Metr metrics.Metricer Metr metrics.Metricer
RollupCfg *rollup.Config RollupConfig *rollup.Config
Cfg BatcherConfig Config BatcherConfig
Txmgr txmgr.TxManager Txmgr txmgr.TxManager
L1Client L1Client L1Client L1Client
L2Client L2Client L2Client L2Client
RollupClient RollupClient RollupClient RollupClient
Channel ChannelConfig ChannelConfig ChannelConfig
} }
// BatchSubmitter encapsulates a service responsible for submitting L2 tx // BatchSubmitter encapsulates a service responsible for submitting L2 tx
...@@ -74,7 +74,7 @@ type BatchSubmitter struct { ...@@ -74,7 +74,7 @@ type BatchSubmitter struct {
func NewBatchSubmitter(setup DriverSetup) *BatchSubmitter { func NewBatchSubmitter(setup DriverSetup) *BatchSubmitter {
return &BatchSubmitter{ return &BatchSubmitter{
DriverSetup: setup, DriverSetup: setup,
state: NewChannelManager(setup.Log, setup.Metr, setup.Channel, setup.RollupCfg), state: NewChannelManager(setup.Log, setup.Metr, setup.ChannelConfig, setup.RollupConfig),
} }
} }
...@@ -171,7 +171,7 @@ func (l *BatchSubmitter) loadBlocksIntoState(ctx context.Context) error { ...@@ -171,7 +171,7 @@ func (l *BatchSubmitter) loadBlocksIntoState(ctx context.Context) error {
latestBlock = block latestBlock = block
} }
l2ref, err := derive.L2BlockToBlockRef(latestBlock, &l.RollupCfg.Genesis) l2ref, err := derive.L2BlockToBlockRef(latestBlock, &l.RollupConfig.Genesis)
if err != nil { if err != nil {
l.Log.Warn("Invalid L2 block loaded into state", "err", err) l.Log.Warn("Invalid L2 block loaded into state", "err", err)
return err return err
...@@ -183,7 +183,7 @@ func (l *BatchSubmitter) loadBlocksIntoState(ctx context.Context) error { ...@@ -183,7 +183,7 @@ func (l *BatchSubmitter) loadBlocksIntoState(ctx context.Context) error {
// loadBlockIntoState fetches & stores a single block into `state`. It returns the block it loaded. // loadBlockIntoState fetches & stores a single block into `state`. It returns the block it loaded.
func (l *BatchSubmitter) loadBlockIntoState(ctx context.Context, blockNumber uint64) (*types.Block, error) { func (l *BatchSubmitter) loadBlockIntoState(ctx context.Context, blockNumber uint64) (*types.Block, error) {
ctx, cancel := context.WithTimeout(ctx, l.Cfg.NetworkTimeout) ctx, cancel := context.WithTimeout(ctx, l.Config.NetworkTimeout)
defer cancel() defer cancel()
block, err := l.L2Client.BlockByNumber(ctx, new(big.Int).SetUint64(blockNumber)) block, err := l.L2Client.BlockByNumber(ctx, new(big.Int).SetUint64(blockNumber))
if err != nil { if err != nil {
...@@ -201,7 +201,7 @@ func (l *BatchSubmitter) loadBlockIntoState(ctx context.Context, blockNumber uin ...@@ -201,7 +201,7 @@ func (l *BatchSubmitter) loadBlockIntoState(ctx context.Context, blockNumber uin
// calculateL2BlockRangeToStore determines the range (start,end] that should be loaded into the local state. // calculateL2BlockRangeToStore determines the range (start,end] that should be loaded into the local state.
// It also takes care of initializing some local state (i.e. will modify l.lastStoredBlock in certain conditions) // It also takes care of initializing some local state (i.e. will modify l.lastStoredBlock in certain conditions)
func (l *BatchSubmitter) calculateL2BlockRangeToStore(ctx context.Context) (eth.BlockID, eth.BlockID, error) { func (l *BatchSubmitter) calculateL2BlockRangeToStore(ctx context.Context) (eth.BlockID, eth.BlockID, error) {
ctx, cancel := context.WithTimeout(ctx, l.Cfg.NetworkTimeout) ctx, cancel := context.WithTimeout(ctx, l.Config.NetworkTimeout)
defer cancel() defer cancel()
syncStatus, err := l.RollupClient.SyncStatus(ctx) syncStatus, err := l.RollupClient.SyncStatus(ctx)
// Ensure that we have the sync status // Ensure that we have the sync status
...@@ -244,11 +244,11 @@ func (l *BatchSubmitter) calculateL2BlockRangeToStore(ctx context.Context) (eth. ...@@ -244,11 +244,11 @@ func (l *BatchSubmitter) calculateL2BlockRangeToStore(ctx context.Context) (eth.
func (l *BatchSubmitter) loop() { func (l *BatchSubmitter) loop() {
defer l.wg.Done() defer l.wg.Done()
ticker := time.NewTicker(l.Cfg.PollInterval) ticker := time.NewTicker(l.Config.PollInterval)
defer ticker.Stop() defer ticker.Stop()
receiptsCh := make(chan txmgr.TxReceipt[txData]) receiptsCh := make(chan txmgr.TxReceipt[txData])
queue := txmgr.NewQueue[txData](l.killCtx, l.Txmgr, l.Cfg.MaxPendingTransactions) queue := txmgr.NewQueue[txData](l.killCtx, l.Txmgr, l.Config.MaxPendingTransactions)
for { for {
select { select {
...@@ -347,7 +347,7 @@ func (l *BatchSubmitter) sendTransaction(txdata txData, queue *txmgr.Queue[txDat ...@@ -347,7 +347,7 @@ func (l *BatchSubmitter) sendTransaction(txdata txData, queue *txmgr.Queue[txDat
} }
candidate := txmgr.TxCandidate{ candidate := txmgr.TxCandidate{
To: &l.RollupCfg.BatchInboxAddress, To: &l.RollupConfig.BatchInboxAddress,
TxData: data, TxData: data,
GasLimit: intrinsicGas, GasLimit: intrinsicGas,
} }
...@@ -387,7 +387,7 @@ func (l *BatchSubmitter) recordConfirmedTx(id txID, receipt *types.Receipt) { ...@@ -387,7 +387,7 @@ func (l *BatchSubmitter) recordConfirmedTx(id txID, receipt *types.Receipt) {
// l1Tip gets the current L1 tip as a L1BlockRef. The passed context is assumed // l1Tip gets the current L1 tip as a L1BlockRef. The passed context is assumed
// to be a lifetime context, so it is internally wrapped with a network timeout. // to be a lifetime context, so it is internally wrapped with a network timeout.
func (l *BatchSubmitter) l1Tip(ctx context.Context) (eth.L1BlockRef, error) { func (l *BatchSubmitter) l1Tip(ctx context.Context) (eth.L1BlockRef, error) {
tctx, cancel := context.WithTimeout(ctx, l.Cfg.NetworkTimeout) tctx, cancel := context.WithTimeout(ctx, l.Config.NetworkTimeout)
defer cancel() defer cancel()
head, err := l.L1Client.HeaderByNumber(tctx, nil) head, err := l.L1Client.HeaderByNumber(tctx, nil)
if err != nil { if err != nil {
......
...@@ -48,7 +48,7 @@ type BatcherService struct { ...@@ -48,7 +48,7 @@ type BatcherService struct {
RollupConfig *rollup.Config RollupConfig *rollup.Config
// Channel builder parameters // Channel builder parameters
Channel ChannelConfig ChannelConfig ChannelConfig
driver *BatchSubmitter driver *BatchSubmitter
...@@ -90,7 +90,7 @@ func (bs *BatcherService) initFromCLIConfig(ctx context.Context, version string, ...@@ -90,7 +90,7 @@ func (bs *BatcherService) initFromCLIConfig(ctx context.Context, version string,
if err := bs.initRPCClients(ctx, cfg); err != nil { if err := bs.initRPCClients(ctx, cfg); err != nil {
return err return err
} }
if err := bs.initRollupCfg(ctx); err != nil { if err := bs.initRollupConfig(ctx); err != nil {
return fmt.Errorf("failed to load rollup config: %w", err) return fmt.Errorf("failed to load rollup config: %w", err)
} }
if err := bs.initChannelConfig(cfg); err != nil { if err := bs.initChannelConfig(cfg); err != nil {
...@@ -153,12 +153,12 @@ func (bs *BatcherService) initBalanceMonitor(cfg *CLIConfig) { ...@@ -153,12 +153,12 @@ func (bs *BatcherService) initBalanceMonitor(cfg *CLIConfig) {
} }
} }
func (bs *BatcherService) initRollupCfg(ctx context.Context) error { func (bs *BatcherService) initRollupConfig(ctx context.Context) error {
rollupCfg, err := bs.RollupNode.RollupConfig(ctx) rollupConfig, err := bs.RollupNode.RollupConfig(ctx)
if err != nil { if err != nil {
return fmt.Errorf("failed to retrieve rollup config: %w", err) return fmt.Errorf("failed to retrieve rollup config: %w", err)
} }
bs.RollupConfig = rollupCfg bs.RollupConfig = rollupConfig
if err := bs.RollupConfig.Check(); err != nil { if err := bs.RollupConfig.Check(); err != nil {
return fmt.Errorf("invalid rollup config: %w", err) return fmt.Errorf("invalid rollup config: %w", err)
} }
...@@ -166,7 +166,7 @@ func (bs *BatcherService) initRollupCfg(ctx context.Context) error { ...@@ -166,7 +166,7 @@ func (bs *BatcherService) initRollupCfg(ctx context.Context) error {
} }
func (bs *BatcherService) initChannelConfig(cfg *CLIConfig) error { func (bs *BatcherService) initChannelConfig(cfg *CLIConfig) error {
bs.Channel = ChannelConfig{ bs.ChannelConfig = ChannelConfig{
SeqWindowSize: bs.RollupConfig.SeqWindowSize, SeqWindowSize: bs.RollupConfig.SeqWindowSize,
ChannelTimeout: bs.RollupConfig.ChannelTimeout, ChannelTimeout: bs.RollupConfig.ChannelTimeout,
MaxChannelDuration: cfg.MaxChannelDuration, MaxChannelDuration: cfg.MaxChannelDuration,
...@@ -175,7 +175,7 @@ func (bs *BatcherService) initChannelConfig(cfg *CLIConfig) error { ...@@ -175,7 +175,7 @@ func (bs *BatcherService) initChannelConfig(cfg *CLIConfig) error {
CompressorConfig: cfg.CompressorConfig.Config(), CompressorConfig: cfg.CompressorConfig.Config(),
BatchType: cfg.BatchType, BatchType: cfg.BatchType,
} }
if err := bs.Channel.Check(); err != nil { if err := bs.ChannelConfig.Check(); err != nil {
return fmt.Errorf("invalid channel configuration: %w", err) return fmt.Errorf("invalid channel configuration: %w", err)
} }
return nil return nil
...@@ -225,15 +225,15 @@ func (bs *BatcherService) initMetricsServer(cfg *CLIConfig) error { ...@@ -225,15 +225,15 @@ func (bs *BatcherService) initMetricsServer(cfg *CLIConfig) error {
func (bs *BatcherService) initDriver() { func (bs *BatcherService) initDriver() {
bs.driver = NewBatchSubmitter(DriverSetup{ bs.driver = NewBatchSubmitter(DriverSetup{
Log: bs.Log, Log: bs.Log,
Metr: bs.Metrics, Metr: bs.Metrics,
RollupCfg: bs.RollupConfig, RollupConfig: bs.RollupConfig,
Cfg: bs.BatcherConfig, Config: bs.BatcherConfig,
Txmgr: bs.TxManager, Txmgr: bs.TxManager,
L1Client: bs.L1Client, L1Client: bs.L1Client,
L2Client: bs.L2Client, L2Client: bs.L2Client,
RollupClient: bs.RollupNode, RollupClient: bs.RollupNode,
Channel: bs.Channel, ChannelConfig: bs.ChannelConfig,
}) })
} }
......
...@@ -352,6 +352,12 @@ func (d *DeployConfig) Check() error { ...@@ -352,6 +352,12 @@ func (d *DeployConfig) Check() error {
if d.L1BlockTime < d.L2BlockTime { if d.L1BlockTime < d.L2BlockTime {
return fmt.Errorf("L2 block time (%d) is larger than L1 block time (%d)", d.L2BlockTime, d.L1BlockTime) return fmt.Errorf("L2 block time (%d) is larger than L1 block time (%d)", d.L2BlockTime, d.L1BlockTime)
} }
if d.RequiredProtocolVersion == (params.ProtocolVersion{}) {
log.Warn("RequiredProtocolVersion is empty")
}
if d.RecommendedProtocolVersion == (params.ProtocolVersion{}) {
log.Warn("RecommendedProtocolVersion is empty")
}
return nil return nil
} }
......
...@@ -29,10 +29,14 @@ type GameBuilderSeq struct { ...@@ -29,10 +29,14 @@ type GameBuilderSeq struct {
} }
func (g *GameBuilder) Seq() *GameBuilderSeq { func (g *GameBuilder) Seq() *GameBuilderSeq {
return g.SeqFrom(g.Game.Claims()[0])
}
func (g *GameBuilder) SeqFrom(claim types.Claim) *GameBuilderSeq {
return &GameBuilderSeq{ return &GameBuilderSeq{
gameBuilder: g, gameBuilder: g,
builder: g.builder, builder: g.builder,
lastClaim: g.Game.Claims()[0], lastClaim: claim,
} }
} }
......
...@@ -7,8 +7,6 @@ import ( ...@@ -7,8 +7,6 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
type ProviderCreator func(ctx context.Context, pre types.Claim, post types.Claim) (types.TraceProvider, error)
func NewSimpleTraceAccessor(trace types.TraceProvider) *Accessor { func NewSimpleTraceAccessor(trace types.TraceProvider) *Accessor {
selector := func(_ context.Context, _ types.Game, _ types.Claim, _ types.Position) (types.TraceProvider, error) { selector := func(_ context.Context, _ types.Game, _ types.Claim, _ types.Position) (types.TraceProvider, error) {
return trace, nil return trace, nil
...@@ -16,8 +14,10 @@ func NewSimpleTraceAccessor(trace types.TraceProvider) *Accessor { ...@@ -16,8 +14,10 @@ func NewSimpleTraceAccessor(trace types.TraceProvider) *Accessor {
return &Accessor{selector} return &Accessor{selector}
} }
type ProviderSelector func(ctx context.Context, game types.Game, ref types.Claim, pos types.Position) (types.TraceProvider, error)
type Accessor struct { type Accessor struct {
selector func(ctx context.Context, game types.Game, ref types.Claim, pos types.Position) (types.TraceProvider, error) selector ProviderSelector
} }
func (t *Accessor) Get(ctx context.Context, game types.Game, ref types.Claim, pos types.Position) (common.Hash, error) { func (t *Accessor) Get(ctx context.Context, game types.Game, ref types.Claim, pos types.Position) (common.Hash, error) {
......
...@@ -3,6 +3,7 @@ package alphabet ...@@ -3,6 +3,7 @@ package alphabet
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"math/big" "math/big"
"strings" "strings"
...@@ -43,7 +44,7 @@ func (ap *AlphabetTraceProvider) GetStepData(ctx context.Context, i types.Positi ...@@ -43,7 +44,7 @@ func (ap *AlphabetTraceProvider) GetStepData(ctx context.Context, i types.Positi
traceIndex = traceIndex.Sub(traceIndex, big.NewInt(1)) traceIndex = traceIndex.Sub(traceIndex, big.NewInt(1))
// The index cannot be larger than the maximum index as computed by the depth. // The index cannot be larger than the maximum index as computed by the depth.
if traceIndex.Cmp(big.NewInt(int64(ap.maxLen))) >= 0 { if traceIndex.Cmp(big.NewInt(int64(ap.maxLen))) >= 0 {
return nil, nil, nil, ErrIndexTooLarge return nil, nil, nil, fmt.Errorf("%w traceIndex: %v max: %v pos: %v", ErrIndexTooLarge, traceIndex, ap.maxLen, i)
} }
// We extend the deepest hash to the maximum depth if the trace is not expansive. // We extend the deepest hash to the maximum depth if the trace is not expansive.
if traceIndex.Cmp(big.NewInt(int64(len(ap.state)))) >= 0 { if traceIndex.Cmp(big.NewInt(int64(len(ap.state)))) >= 0 {
...@@ -54,6 +55,9 @@ func (ap *AlphabetTraceProvider) GetStepData(ctx context.Context, i types.Positi ...@@ -54,6 +55,9 @@ func (ap *AlphabetTraceProvider) GetStepData(ctx context.Context, i types.Positi
// Get returns the claim value at the given index in the trace. // Get returns the claim value at the given index in the trace.
func (ap *AlphabetTraceProvider) Get(ctx context.Context, i types.Position) (common.Hash, error) { func (ap *AlphabetTraceProvider) Get(ctx context.Context, i types.Position) (common.Hash, error) {
if uint64(i.Depth()) > ap.depth {
return common.Hash{}, fmt.Errorf("%w depth: %v max: %v", ErrIndexTooLarge, i.Depth(), ap.depth)
}
// Step data returns the pre-state, so add 1 to get the state for index i // Step data returns the pre-state, so add 1 to get the state for index i
ti := i.TraceIndex(int(ap.depth)) ti := i.TraceIndex(int(ap.depth))
postPosition := types.NewPosition(int(ap.depth), new(big.Int).Add(ti, big.NewInt(1))) postPosition := types.NewPosition(int(ap.depth), new(big.Int).Add(ti, big.NewInt(1)))
......
...@@ -92,6 +92,14 @@ func TestGet_IndexTooLarge(t *testing.T) { ...@@ -92,6 +92,14 @@ func TestGet_IndexTooLarge(t *testing.T) {
require.ErrorIs(t, err, ErrIndexTooLarge) require.ErrorIs(t, err, ErrIndexTooLarge)
} }
func TestGet_DepthTooLarge(t *testing.T) {
depth := 2
ap := NewTraceProvider("abc", uint64(depth))
pos := types.NewPosition(depth+1, big.NewInt(0))
_, err := ap.Get(context.Background(), pos)
require.ErrorIs(t, err, ErrIndexTooLarge)
}
// TestGet_Extends tests the Get function with an index that is larger // TestGet_Extends tests the Get function with an index that is larger
// than the trace, but smaller than the maximum depth. // than the trace, but smaller than the maximum depth.
func TestGet_Extends(t *testing.T) { func TestGet_Extends(t *testing.T) {
......
package outputs
import (
"context"
"errors"
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
)
var (
errRefClaimNotDeepEnough = errors.New("reference claim is not deep enough")
)
type ProviderCreator func(ctx context.Context, pre types.Claim, post types.Claim) (types.TraceProvider, error)
func newSplitProviderSelector(topProvider types.TraceProvider, topDepth int, bottomProviderCreator ProviderCreator) trace.ProviderSelector {
return func(ctx context.Context, game types.Game, ref types.Claim, pos types.Position) (types.TraceProvider, error) {
if pos.Depth() <= topDepth {
return topProvider, nil
}
if ref.Position.Depth() < topDepth {
return nil, fmt.Errorf("%w, claim depth: %v, depth required: %v", errRefClaimNotDeepEnough, ref.Position.Depth(), topDepth)
}
// Find the ancestor claim at the leaf level for the top game.
topLeaf, err := findAncestorAtDepth(game, ref, topDepth)
if err != nil {
return nil, err
}
var pre, post types.Claim
// If pos is to the right of the leaf from the top game, we must be defending that output root
// otherwise, we're attacking it.
if pos.TraceIndex(pos.Depth()).Cmp(topLeaf.TraceIndex(pos.Depth())) > 0 {
// Defending the top leaf claim, so use it as the pre-claim and find the post
pre = topLeaf
postTraceIdx := new(big.Int).Add(pre.TraceIndex(topDepth), big.NewInt(1))
post, err = findAncestorWithTraceIndex(game, topLeaf, topDepth, postTraceIdx)
if err != nil {
return nil, fmt.Errorf("failed to find post claim: %w", err)
}
} else {
// Attacking the top leaf claim, so use it as the post-claim and find the pre
post = topLeaf
postTraceIdx := post.TraceIndex(topDepth)
if postTraceIdx.Cmp(big.NewInt(0)) == 0 {
pre = types.Claim{}
} else {
preTraceIdx := new(big.Int).Sub(postTraceIdx, big.NewInt(1))
pre, err = findAncestorWithTraceIndex(game, topLeaf, topDepth, preTraceIdx)
if err != nil {
return nil, fmt.Errorf("failed to find pre claim: %w", err)
}
}
}
provider, err := bottomProviderCreator(ctx, pre, post)
if err != nil {
return nil, err
}
// Translate such that the root of the bottom game is the level below the top game leaf
return trace.Translate(provider, uint64(topDepth)+1), nil
}
}
func findAncestorAtDepth(game types.Game, claim types.Claim, depth int) (types.Claim, error) {
for claim.Depth() > depth {
parent, err := game.GetParent(claim)
if err != nil {
return types.Claim{}, fmt.Errorf("failed to find ancestor at depth %v: %w", depth, err)
}
claim = parent
}
return claim, nil
}
func findAncestorWithTraceIndex(game types.Game, ref types.Claim, depth int, traceIdx *big.Int) (types.Claim, error) {
candidate := ref
for candidate.TraceIndex(depth).Cmp(traceIdx) != 0 {
parent, err := game.GetParent(candidate)
if err != nil {
return types.Claim{}, fmt.Errorf("failed to get parent of claim %v: %w", candidate.ContractIndex, err)
}
candidate = parent
}
return candidate, nil
}
package outputs
import (
"context"
"fmt"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/test"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/alphabet"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/stretchr/testify/require"
)
const (
topDepth = 3
bottomDepth = 4
)
func TestUseTopProvider(t *testing.T) {
ctx := context.Background()
topProvider, selector, gameBuilder := setupAlphabetSplitSelector(t)
ref := gameBuilder.Game.Claims()[0]
pos := ref.Position
for pos.Depth() <= topDepth {
provider, err := selector(ctx, gameBuilder.Game, ref, ref.Position)
require.NoError(t, err)
require.Same(t, topProvider, provider)
_, err = topProvider.Get(ctx, pos)
require.NoError(t, err, "should be able to use provider for position")
pos = pos.Attack()
}
}
func TestErrorWhenRefAboveTopGameLeafButPositionInBottom(t *testing.T) {
ctx := context.Background()
_, selector, gameBuilder := setupAlphabetSplitSelector(t)
// Generate claims at depths up to but not including the leaf of the top providers
createClaimsToDepth(gameBuilder, topDepth-1)
for _, ref := range gameBuilder.Game.Claims() {
pos := types.NewPosition(topDepth+1, big.NewInt(0))
provider, err := selector(ctx, gameBuilder.Game, ref, pos)
require.ErrorIsf(t, err, errRefClaimNotDeepEnough, "should not get provider with ref claim at depth: %v", ref.Depth())
require.Nil(t, provider)
}
}
func TestTranslatePositionsForBottomProvider(t *testing.T) {
tests := []struct {
name string
setup func(t *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim)
}{
// There are 4 leaf nodes that can be accessed in the top tree of depth 3: 8, 10, 12, 14
// Then you can attack and defend any of those to challenge all blocks
{"attackTopLeafGIndex8", attackTopLeafGIndex8},
{"defendTopLeafGIndex8", defendTopLeafGIndex8},
{"attackTopLeafGIndex10", attackTopLeafGIndex10},
{"defendTopLeafGIndex10", defendTopLeafGIndex10},
{"attackTopLeafGIndex12", attackTopLeafGIndex12},
{"defendTopLeafGIndex12", defendTopLeafGIndex12},
{"attackTopLeafGIndex14", attackTopLeafGIndex14},
{"attackTopLeafGIndex14", defendTopLeafGIndex14},
}
for _, tCase := range tests {
tCase := tCase
t.Run(tCase.name, func(t *testing.T) {
_, selector, gameBuilder := setupAlphabetSplitSelector(t)
ref, pos, _, _ := tCase.setup(t, gameBuilder)
provider, err := selector(context.Background(), gameBuilder.Game, ref, pos)
require.NoError(t, err)
claimPos := pos
localClaimPos := types.NewPositionFromGIndex(big.NewInt(1))
requireSameValue(t, provider, claimPos, asBottomTraceProvider(t, provider).AlphabetTraceProvider, localClaimPos)
requireSameValue(t, provider, claimPos.Attack(), asBottomTraceProvider(t, provider).AlphabetTraceProvider, localClaimPos.Attack())
requireSameValue(t, provider, claimPos.Attack().Defend(), asBottomTraceProvider(t, provider).AlphabetTraceProvider, localClaimPos.Attack().Defend())
})
}
}
func requireSameValue(t *testing.T, a types.TraceProvider, aPos types.Position, b types.TraceProvider, bPos types.Position) {
// Check Get returns the same results
aValue, err := a.Get(context.Background(), aPos)
require.NoError(t, err)
bValue, err := b.Get(context.Background(), bPos)
require.NoError(t, err)
require.Equal(t, aValue, bValue)
// Check GetStepData returns the same results
aPrestate, aProofData, aPreimageData, err := a.GetStepData(context.Background(), aPos)
require.NoError(t, err)
bPrestate, bProofData, bPreimageData, err := b.GetStepData(context.Background(), bPos)
require.NoError(t, err)
require.Equal(t, aPrestate, bPrestate)
require.Equal(t, aProofData, bProofData)
require.Equal(t, aPreimageData, bPreimageData)
}
func TestBottomProviderAttackingTopLeaf(t *testing.T) {
tests := []struct {
name string
setup func(t *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim)
}{
// There are 4 leaf nodes that can be accessed in the top tree of depth 3: 8, 10, 12, 14
// Then you can attack and defend any of those to challenge all blocks
// We can then use these setups to test any other reference claim descending from what these setup since
// that whole subtree should have the same pre and post claim from the top provider.
{"attackTopLeafGIndex8", attackTopLeafGIndex8},
{"defendTopLeafGIndex8", defendTopLeafGIndex8},
{"attackTopLeafGIndex10", attackTopLeafGIndex10},
{"defendTopLeafGIndex10", defendTopLeafGIndex10},
{"attackTopLeafGIndex12", attackTopLeafGIndex12},
{"defendTopLeafGIndex12", defendTopLeafGIndex12},
{"attackTopLeafGIndex14", attackTopLeafGIndex14},
{"attackTopLeafGIndex14", defendTopLeafGIndex14},
}
for _, tCase := range tests {
tCase := tCase
t.Run(tCase.name, func(t *testing.T) {
_, selector, gameBuilder := setupAlphabetSplitSelector(t)
ref, pos, expectedPre, expectedPost := tCase.setup(t, gameBuilder)
runTest := func(ref types.Claim, pos types.Position) {
t.Run(fmt.Sprintf("Ref-d%vi%v_Pos-d%vi%v", ref.Depth(), ref.IndexAtDepth(), pos.Depth(), pos.IndexAtDepth()), func(t *testing.T) {
provider, err := selector(context.Background(), gameBuilder.Game, ref, pos)
require.NoError(t, err)
requireBottomProviderForClaims(t, provider, expectedPre, expectedPost)
})
}
// Check we get the same pre and post for any reference claim lower in the game
var testDescendantClaims func(ref types.Claim, pos types.Position)
testDescendantClaims = func(ref types.Claim, pos types.Position) {
// For each reference claim, check it works with the claim position, or attacking or defending the claim
runTest(ref, pos)
runTest(ref, pos.Attack())
runTest(ref, pos.Defend())
if pos.Depth() >= topDepth+bottomDepth {
return
}
// If the ref is the leaf of the top claim, ensure we respect whether the test is setup
// to attack or defend the top leaf claim.
if ref.Depth() != topDepth || !pos.RightOf(ref.Position) {
gameBuilder.SeqFrom(ref).AttackCorrect()
attackRef := latestClaim(gameBuilder)
testDescendantClaims(attackRef, attackRef.Position)
}
if ref.Depth() != topDepth || pos.RightOf(ref.Position) {
gameBuilder.SeqFrom(ref).DefendCorrect()
defendRef := latestClaim(gameBuilder)
testDescendantClaims(defendRef, defendRef.Position)
}
}
testDescendantClaims(ref, pos)
})
}
}
func attackTopLeafGIndex8(_ *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim) {
// Generate claims down to the top provider's leaf
seq := gameBuilder.Seq() // gindex 1, trace 7
seq = seq.AttackCorrect() // gindex 2, trace 3
seq = seq.AttackCorrect() // gindex 4, trace 1
seq.AttackCorrect() // gindex 8, trace 0
expectPost = latestClaim(gameBuilder)
// No pre-claim as the first output root is being challenged.
expectPre = types.Claim{}
ref = latestClaim(gameBuilder)
pos = ref.Position.Attack()
return
}
func defendTopLeafGIndex8(_ *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim) {
// Generate claims down to the top provider's leaf
seq := gameBuilder.Seq() // gindex 1, trace 7
seq = seq.AttackCorrect() // gindex 2, trace 3
seq = seq.AttackCorrect() // gindex 4, trace 1
expectPost = latestClaim(gameBuilder)
seq.AttackCorrect() // gindex 8, trace 0
expectPre = latestClaim(gameBuilder)
ref = latestClaim(gameBuilder)
pos = ref.Position.Defend()
return
}
func attackTopLeafGIndex10(_ *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim) {
seq := gameBuilder.Seq() // gindex 1, trace 7
seq = seq.AttackCorrect() // gindex 2, trace 3
seq = seq.AttackCorrect() // gindex 4, trace 1
expectPre = latestClaim(gameBuilder)
seq.DefendCorrect() // gindex 10, trace 2
expectPost = latestClaim(gameBuilder)
ref = latestClaim(gameBuilder)
pos = ref.Position.Attack()
return
}
func defendTopLeafGIndex10(_ *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim) {
seq := gameBuilder.Seq() // gindex 1, trace 7
seq = seq.AttackCorrect() // gindex 2, trace 3
expectPost = latestClaim(gameBuilder)
seq = seq.AttackCorrect() // gindex 4, trace 1
seq.DefendCorrect() // gindex 10, trace 2
expectPre = latestClaim(gameBuilder)
ref = latestClaim(gameBuilder)
pos = ref.Position.Defend()
return
}
func attackTopLeafGIndex12(_ *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim) {
seq := gameBuilder.Seq() // gindex 1, trace 7
seq = seq.AttackCorrect() // gindex 2, trace 3
expectPre = latestClaim(gameBuilder)
seq = seq.DefendCorrect() // gindex 6, trace 5
seq.AttackCorrect() // gindex 12, trace 4
expectPost = latestClaim(gameBuilder)
ref = latestClaim(gameBuilder)
pos = ref.Position.Attack()
return
}
func defendTopLeafGIndex12(_ *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim) {
seq := gameBuilder.Seq() // gindex 1, trace 7
seq = seq.AttackCorrect() // gindex 2, trace 3
seq = seq.DefendCorrect() // gindex 6, trace 5
expectPost = latestClaim(gameBuilder)
seq.AttackCorrect() // gindex 12, trace 4
expectPre = latestClaim(gameBuilder)
ref = latestClaim(gameBuilder)
pos = ref.Position.Defend()
return
}
func attackTopLeafGIndex14(_ *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim) {
seq := gameBuilder.Seq() // gindex 1, trace 7
seq = seq.AttackCorrect() // gindex 2, trace 3
seq = seq.DefendCorrect() // gindex 6, trace 5
expectPre = latestClaim(gameBuilder)
seq.DefendCorrect() // gindex 14, trace 6
expectPost = latestClaim(gameBuilder)
ref = latestClaim(gameBuilder)
pos = ref.Position.Attack()
return
}
func defendTopLeafGIndex14(_ *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim) {
seq := gameBuilder.Seq() // gindex 1, trace 7
expectPost = latestClaim(gameBuilder)
seq = seq.AttackCorrect() // gindex 2, trace 3
seq = seq.DefendCorrect() // gindex 6, trace 5
seq.DefendCorrect() // gindex 14, trace 6
expectPre = latestClaim(gameBuilder)
ref = latestClaim(gameBuilder)
pos = ref.Position.Defend()
return
}
func latestClaim(gameBuilder *test.GameBuilder) types.Claim {
return gameBuilder.Game.Claims()[len(gameBuilder.Game.Claims())-1]
}
func createClaimsToDepth(gameBuilder *test.GameBuilder, depth int) {
seq := gameBuilder.Seq()
for i := 0; i < depth; i++ {
seq = seq.AttackCorrect()
}
}
func requireBottomProviderForClaims(t *testing.T, actual types.TraceProvider, expectedPre types.Claim, expectedPost types.Claim) {
if expectedPre != (types.Claim{}) {
require.Equal(t,
new(big.Int).Add(expectedPre.TraceIndex(topDepth), big.NewInt(1)),
expectedPost.TraceIndex(topDepth),
"should expect adjacent top level trace indices")
}
bottomProvider := asBottomTraceProvider(t, actual)
require.Equal(t, expectedPre, bottomProvider.pre, "Incorrect pre claim")
require.Equal(t, expectedPost, bottomProvider.post, "Incorrect post claim")
}
func asBottomTraceProvider(t *testing.T, actual types.TraceProvider) *bottomTraceProvider {
translatingProvider, ok := actual.(*trace.TranslatingProvider)
require.True(t, ok)
bottomProvider, ok := translatingProvider.Original().(*bottomTraceProvider)
require.True(t, ok)
return bottomProvider
}
func setupAlphabetSplitSelector(t *testing.T) (*alphabet.AlphabetTraceProvider, trace.ProviderSelector, *test.GameBuilder) {
top := alphabet.NewTraceProvider("abcdef", topDepth)
bottomCreator := func(ctx context.Context, pre types.Claim, post types.Claim) (types.TraceProvider, error) {
return &bottomTraceProvider{
pre: pre,
post: post,
AlphabetTraceProvider: alphabet.NewTraceProvider(post.Value.Hex(), bottomDepth),
}, nil
}
selector := newSplitProviderSelector(top, topDepth, bottomCreator)
claimBuilder := test.NewAlphabetClaimBuilder(t, topDepth+bottomDepth)
gameBuilder := claimBuilder.GameBuilder(true, true)
return top, selector, gameBuilder
}
type bottomTraceProvider struct {
pre types.Claim
post types.Claim
*alphabet.AlphabetTraceProvider
}
package split
import (
"context"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
var _ types.TraceProvider = (*SplitTraceProvider)(nil)
// SplitTraceProvider is a [types.TraceProvider] implementation that
// routes requests to the correct internal trace provider based on the
// depth of the requested trace.
type SplitTraceProvider struct {
logger log.Logger
topProvider types.TraceProvider
bottomProvider types.TraceProvider
topDepth uint64
}
// NewTraceProvider creates a new [SplitTraceProvider] instance.
// The [topDepth] parameter specifies the depth at which the internal
// [types.TraceProvider] should be switched.
func NewTraceProvider(logger log.Logger, topProvider types.TraceProvider, bottomProvider types.TraceProvider, topDepth uint64) *SplitTraceProvider {
return &SplitTraceProvider{
logger: logger,
topProvider: topProvider,
bottomProvider: bottomProvider,
topDepth: topDepth,
}
}
func (s *SplitTraceProvider) providerForDepth(depth uint64) (uint64, types.TraceProvider) {
if depth <= s.topDepth {
return 0, s.topProvider
}
return s.topDepth, s.bottomProvider
}
// Get routes the Get request to the internal [types.TraceProvider] that
// that serves the trace index at the depth.
func (s *SplitTraceProvider) Get(ctx context.Context, pos types.Position) (common.Hash, error) {
ancestorDepth, provider := s.providerForDepth(uint64(pos.Depth()))
relativePosition, err := pos.RelativeToAncestorAtDepth(ancestorDepth)
if err != nil {
return common.Hash{}, err
}
return provider.Get(ctx, relativePosition)
}
// AbsolutePreStateCommitment returns the absolute prestate from the lowest internal [types.TraceProvider]
func (s *SplitTraceProvider) AbsolutePreStateCommitment(ctx context.Context) (hash common.Hash, err error) {
return s.bottomProvider.AbsolutePreStateCommitment(ctx)
}
// GetStepData routes the GetStepData request to the lowest internal [types.TraceProvider].
func (s *SplitTraceProvider) GetStepData(ctx context.Context, pos types.Position) (prestate []byte, proofData []byte, preimageData *types.PreimageOracleData, err error) {
ancestorDepth, provider := s.providerForDepth(uint64(pos.Depth()))
relativePosition, err := pos.RelativeToAncestorAtDepth(ancestorDepth)
if err != nil {
return nil, nil, nil, err
}
return provider.GetStepData(ctx, relativePosition)
}
package split
import (
"context"
"errors"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
var (
mockGetError = errors.New("mock get error")
mockOutput = common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
mockCommitment = common.HexToHash("0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
)
func TestGet(t *testing.T) {
t.Run("ErrorBubblesUp", func(t *testing.T) {
mockOutputProvider := mockTraceProvider{getError: mockGetError}
splitProvider := newSplitTraceProvider(t, &mockOutputProvider, nil, 40)
_, err := splitProvider.Get(context.Background(), types.NewPosition(1, common.Big0))
require.ErrorIs(t, err, mockGetError)
})
t.Run("ReturnsCorrectOutputFromTopProvider", func(t *testing.T) {
mockOutputProvider := mockTraceProvider{getOutput: mockOutput}
splitProvider := newSplitTraceProvider(t, &mockOutputProvider, &mockTraceProvider{}, 40)
output, err := splitProvider.Get(context.Background(), types.NewPosition(6, big.NewInt(3)))
require.NoError(t, err)
expectedGIndex := types.NewPosition(6, big.NewInt(3)).ToGIndex()
require.Equal(t, common.BigToHash(expectedGIndex), output)
})
t.Run("ReturnsCorrectOutputWithMultipleProviders", func(t *testing.T) {
bottomProvider := mockTraceProvider{getOutput: mockOutput}
splitProvider := newSplitTraceProvider(t, &mockTraceProvider{}, &bottomProvider, 40)
output, err := splitProvider.Get(context.Background(), types.NewPosition(42, big.NewInt(17)))
require.NoError(t, err)
expectedGIndex := types.NewPosition(2, big.NewInt(1)).ToGIndex()
require.Equal(t, common.BigToHash(expectedGIndex), output)
})
}
func TestAbsolutePreStateCommitment(t *testing.T) {
t.Run("ErrorBubblesUp", func(t *testing.T) {
mockOutputProvider := mockTraceProvider{absolutePreStateCommitmentError: mockGetError}
splitProvider := newSplitTraceProvider(t, nil, &mockOutputProvider, 40)
_, err := splitProvider.AbsolutePreStateCommitment(context.Background())
require.ErrorIs(t, err, mockGetError)
})
t.Run("ReturnsCorrectOutput", func(t *testing.T) {
mockOutputProvider := mockTraceProvider{absolutePreStateCommitment: mockCommitment}
splitProvider := newSplitTraceProvider(t, nil, &mockOutputProvider, 40)
output, err := splitProvider.AbsolutePreStateCommitment(context.Background())
require.NoError(t, err)
require.Equal(t, mockCommitment, output)
})
}
func TestGetStepData(t *testing.T) {
t.Run("ErrorBubblesUp", func(t *testing.T) {
mockOutputProvider := mockTraceProvider{getStepDataError: mockGetError}
splitProvider := newSplitTraceProvider(t, &mockOutputProvider, nil, 40)
_, _, _, err := splitProvider.GetStepData(context.Background(), types.NewPosition(0, common.Big0))
require.ErrorIs(t, err, mockGetError)
})
t.Run("ReturnsCorrectStepData", func(t *testing.T) {
expectedStepData := []byte{1, 2, 3, 4}
mockOutputProvider := mockTraceProvider{stepPrestateData: expectedStepData}
splitProvider := newSplitTraceProvider(t, nil, &mockOutputProvider, 40)
output, _, _, err := splitProvider.GetStepData(context.Background(), types.NewPosition(41, common.Big0))
require.NoError(t, err)
require.Equal(t, expectedStepData, output)
})
}
type mockTraceProvider struct {
getOutput common.Hash
getError error
absolutePreStateCommitmentError error
absolutePreStateCommitment common.Hash
absolutePreStateError error
preImageData []byte
getStepDataError error
stepPrestateData []byte
}
func newSplitTraceProvider(t *testing.T, tp *mockTraceProvider, bp *mockTraceProvider, topDepth uint64) SplitTraceProvider {
return SplitTraceProvider{
logger: testlog.Logger(t, log.LvlInfo),
topProvider: tp,
bottomProvider: bp,
topDepth: topDepth,
}
}
func (m *mockTraceProvider) Get(ctx context.Context, pos types.Position) (common.Hash, error) {
if m.getError != nil {
return common.Hash{}, m.getError
}
return common.BigToHash(pos.ToGIndex()), nil
}
func (m *mockTraceProvider) AbsolutePreStateCommitment(ctx context.Context) (hash common.Hash, err error) {
if m.absolutePreStateCommitmentError != nil {
return common.Hash{}, m.absolutePreStateCommitmentError
}
return m.absolutePreStateCommitment, nil
}
func (m *mockTraceProvider) AbsolutePreState(ctx context.Context) (preimage []byte, err error) {
if m.absolutePreStateError != nil {
return []byte{}, m.absolutePreStateError
}
return m.preImageData, nil
}
func (m *mockTraceProvider) GetStepData(ctx context.Context, pos types.Position) ([]byte, []byte, *types.PreimageOracleData, error) {
if m.getStepDataError != nil {
return nil, nil, nil, m.getStepDataError
}
return m.stepPrestateData, nil, nil, nil
}
...@@ -7,36 +7,43 @@ import ( ...@@ -7,36 +7,43 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
type translatingProvider struct { type TranslatingProvider struct {
parentDepth uint64 rootDepth uint64
provider types.TraceProvider provider types.TraceProvider
} }
func Translate(provider types.TraceProvider, parentDepth uint64) types.TraceProvider { // Translate returns a new TraceProvider that translates any requested positions before passing them on to the
return &translatingProvider{ // specified provider.
parentDepth: parentDepth, // The translation is done such that the root node for provider is at rootDepth.
provider: provider, func Translate(provider types.TraceProvider, rootDepth uint64) types.TraceProvider {
return &TranslatingProvider{
rootDepth: rootDepth,
provider: provider,
} }
} }
func (p translatingProvider) Get(ctx context.Context, pos types.Position) (common.Hash, error) { func (p *TranslatingProvider) Original() types.TraceProvider {
relativePos, err := pos.RelativeToAncestorAtDepth(p.parentDepth) return p.provider
}
func (p *TranslatingProvider) Get(ctx context.Context, pos types.Position) (common.Hash, error) {
relativePos, err := pos.RelativeToAncestorAtDepth(p.rootDepth)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
return p.provider.Get(ctx, relativePos) return p.provider.Get(ctx, relativePos)
} }
func (p translatingProvider) GetStepData(ctx context.Context, pos types.Position) (prestate []byte, proofData []byte, preimageData *types.PreimageOracleData, err error) { func (p *TranslatingProvider) GetStepData(ctx context.Context, pos types.Position) (prestate []byte, proofData []byte, preimageData *types.PreimageOracleData, err error) {
relativePos, err := pos.RelativeToAncestorAtDepth(p.parentDepth) relativePos, err := pos.RelativeToAncestorAtDepth(p.rootDepth)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
return p.provider.GetStepData(ctx, relativePos) return p.provider.GetStepData(ctx, relativePos)
} }
func (p translatingProvider) AbsolutePreStateCommitment(ctx context.Context) (hash common.Hash, err error) { func (p *TranslatingProvider) AbsolutePreStateCommitment(ctx context.Context) (hash common.Hash, err error) {
return p.provider.AbsolutePreStateCommitment(ctx) return p.provider.AbsolutePreStateCommitment(ctx)
} }
var _ types.TraceProvider = (*translatingProvider)(nil) var _ types.TraceProvider = (*TranslatingProvider)(nil)
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
) )
var ( var (
ErrPositionDepthTooSmall = errors.New("Position depth is too small") ErrPositionDepthTooSmall = errors.New("position depth is too small")
) )
// Position is a golang wrapper around the dispute game Position type. // Position is a golang wrapper around the dispute game Position type.
...@@ -32,6 +32,10 @@ func NewPositionFromGIndex(x *big.Int) Position { ...@@ -32,6 +32,10 @@ func NewPositionFromGIndex(x *big.Int) Position {
return NewPosition(depth, indexAtDepth) return NewPosition(depth, indexAtDepth)
} }
func (p Position) String() string {
return fmt.Sprintf("Position(depth: %v, indexAtDepth: %v)", p.depth, p.indexAtDepth)
}
func (p Position) MoveRight() Position { func (p Position) MoveRight() Position {
return Position{ return Position{
depth: p.depth, depth: p.depth,
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
...@@ -21,29 +22,69 @@ import ( ...@@ -21,29 +22,69 @@ import (
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
) )
var (
l1RPCFlag = &cli.StringFlag{
Name: "l1-rpc",
Usage: "RPC URL for an Ethereum L1 node. Cannot be used with --l1-starting-block",
}
l1StartingBlockFlag = &cli.PathFlag{
Name: "l1-starting-block",
Usage: "Path to a JSON file containing the L1 starting block. Overrides the need for using an L1 RPC to fetch the block. Cannot be used with --l1-rpc",
}
deployConfigFlag = &cli.PathFlag{
Name: "deploy-config",
Usage: "Path to deploy config file",
Required: true,
}
deploymentDirFlag = &cli.PathFlag{
Name: "deployment-dir",
Usage: "Path to network deployment directory. Cannot be used with --l1-deployments",
}
l1DeploymentsFlag = &cli.PathFlag{
Name: "l1-deployments",
Usage: "Path to L1 deployments JSON file. Cannot be used with --deployment-dir",
}
outfileL2Flag = &cli.PathFlag{
Name: "outfile.l2",
Usage: "Path to L2 genesis output file",
}
outfileRollupFlag = &cli.PathFlag{
Name: "outfile.rollup",
Usage: "Path to rollup output file",
}
l1AllocsFlag = &cli.StringFlag{
Name: "l1-allocs",
Usage: "Path to L1 genesis state dump",
}
outfileL1Flag = &cli.StringFlag{
Name: "outfile.l1",
Usage: "Path to L1 genesis output file",
}
l1Flags = []cli.Flag{
deployConfigFlag,
l1AllocsFlag,
l1DeploymentsFlag,
outfileL1Flag,
}
l2Flags = []cli.Flag{
l1RPCFlag,
l1StartingBlockFlag,
deployConfigFlag,
deploymentDirFlag,
l1DeploymentsFlag,
outfileL2Flag,
outfileRollupFlag,
}
)
var Subcommands = cli.Commands{ var Subcommands = cli.Commands{
{ {
Name: "l1", Name: "l1",
Usage: "Generates a L1 genesis state file", Usage: "Generates a L1 genesis state file",
Flags: []cli.Flag{ Flags: l1Flags,
&cli.StringFlag{
Name: "deploy-config",
Usage: "Path to deploy config file",
Required: true,
},
&cli.StringFlag{
Name: "l1-allocs",
Usage: "Path to L1 genesis state dump",
},
&cli.StringFlag{
Name: "l1-deployments",
Usage: "Path to L1 deployments file",
},
&cli.StringFlag{
Name: "outfile.l1",
Usage: "Path to L1 genesis output file",
},
},
Action: func(ctx *cli.Context) error { Action: func(ctx *cli.Context) error {
deployConfig := ctx.String("deploy-config") deployConfig := ctx.String("deploy-config")
config, err := genesis.NewDeployConfig(deployConfig) config, err := genesis.NewDeployConfig(deployConfig)
...@@ -85,7 +126,7 @@ var Subcommands = cli.Commands{ ...@@ -85,7 +126,7 @@ var Subcommands = cli.Commands{
return err return err
} }
return writeGenesisFile(ctx.String("outfile.l1"), l1Genesis) return writeJSONFile(ctx.String("outfile.l1"), l1Genesis)
}, },
}, },
{ {
...@@ -93,44 +134,20 @@ var Subcommands = cli.Commands{ ...@@ -93,44 +134,20 @@ var Subcommands = cli.Commands{
Usage: "Generates an L2 genesis file and rollup config suitable for a deployed network", Usage: "Generates an L2 genesis file and rollup config suitable for a deployed network",
Description: "Generating the L2 genesis depends on knowledge of L1 contract addresses for the bridge to be secure. " + Description: "Generating the L2 genesis depends on knowledge of L1 contract addresses for the bridge to be secure. " +
"A deploy config and either a deployment directory or an L1 deployments file are used to create the L2 genesis. " + "A deploy config and either a deployment directory or an L1 deployments file are used to create the L2 genesis. " +
"The deploy directory and L1 deployments file are generated by the L1 contract deployments.", "The deploy directory and L1 deployments file are generated by the L1 contract deployments. " +
Flags: []cli.Flag{ "An L1 starting block is necessary, it can either be fetched dynamically using config in the deploy config " +
&cli.StringFlag{ "or it can be provided as a JSON file.",
Name: "l1-rpc", Flags: l2Flags,
Usage: "L1 RPC URL",
},
&cli.StringFlag{
Name: "deploy-config",
Usage: "Path to deploy config file",
Required: true,
},
&cli.StringFlag{
Name: "deployment-dir",
Usage: "Path to network deployment directory. Cannot be used with --l1-deployments",
},
&cli.StringFlag{
Name: "l1-deployments",
Usage: "Path to L1 deployments JSON file. Cannot be used with --deployment-dir",
},
&cli.StringFlag{
Name: "outfile.l2",
Usage: "Path to L2 genesis output file",
},
&cli.StringFlag{
Name: "outfile.rollup",
Usage: "Path to rollup output file",
},
},
Action: func(ctx *cli.Context) error { Action: func(ctx *cli.Context) error {
deployConfig := ctx.String("deploy-config") deployConfig := ctx.Path("deploy-config")
log.Info("Deploy config", "path", deployConfig) log.Info("Deploy config", "path", deployConfig)
config, err := genesis.NewDeployConfig(deployConfig) config, err := genesis.NewDeployConfig(deployConfig)
if err != nil { if err != nil {
return err return err
} }
deployDir := ctx.String("deployment-dir") deployDir := ctx.Path("deployment-dir")
l1Deployments := ctx.String("l1-deployments") l1Deployments := ctx.Path("l1-deployments")
if deployDir != "" && l1Deployments != "" { if deployDir != "" && l1Deployments != "" {
return errors.New("cannot specify both --deployment-dir and --l1-deployments") return errors.New("cannot specify both --deployment-dir and --l1-deployments")
...@@ -139,6 +156,16 @@ var Subcommands = cli.Commands{ ...@@ -139,6 +156,16 @@ var Subcommands = cli.Commands{
return errors.New("must specify either --deployment-dir or --l1-deployments") return errors.New("must specify either --deployment-dir or --l1-deployments")
} }
l1StartBlockPath := ctx.Path("l1-starting-block")
l1RPC := ctx.String("l1-rpc")
if l1StartBlockPath == "" && l1RPC == "" {
return errors.New("must specify either --l1-starting-block or --l1-rpc")
}
if l1StartBlockPath != "" && l1RPC != "" {
return errors.New("cannot specify both --l1-starting-block and --l1-rpc")
}
if deployDir != "" { if deployDir != "" {
log.Info("Deployment directory", "path", deployDir) log.Info("Deployment directory", "path", deployDir)
depPath, network := filepath.Split(deployDir) depPath, network := filepath.Split(deployDir)
...@@ -154,31 +181,49 @@ var Subcommands = cli.Commands{ ...@@ -154,31 +181,49 @@ var Subcommands = cli.Commands{
} }
if l1Deployments != "" { if l1Deployments != "" {
log.Info("L1 deployments", "path", l1Deployments)
deployments, err := genesis.NewL1Deployments(l1Deployments) deployments, err := genesis.NewL1Deployments(l1Deployments)
if err != nil { if err != nil {
return err return fmt.Errorf("cannot read L1 deployments at %s: %w", l1Deployments, err)
} }
config.SetDeployments(deployments) config.SetDeployments(deployments)
} }
client, err := ethclient.Dial(ctx.String("l1-rpc")) var l1StartBlock *types.Block
if err != nil { if l1StartBlockPath != "" {
return fmt.Errorf("cannot dial %s: %w", ctx.String("l1-rpc"), err) if l1StartBlock, err = readBlockJSON(l1StartBlockPath); err != nil {
return fmt.Errorf("cannot read L1 starting block at %s: %w", l1StartBlockPath, err)
}
} }
var l1StartBlock *types.Block if l1RPC != "" {
if config.L1StartingBlockTag == nil { client, err := ethclient.Dial(l1RPC)
l1StartBlock, err = client.BlockByNumber(context.Background(), nil) if err != nil {
tag := rpc.BlockNumberOrHashWithHash(l1StartBlock.Hash(), true) return fmt.Errorf("cannot dial %s: %w", l1RPC, err)
config.L1StartingBlockTag = (*genesis.MarshalableRPCBlockNumberOrHash)(&tag) }
} else if config.L1StartingBlockTag.BlockHash != nil {
l1StartBlock, err = client.BlockByHash(context.Background(), *config.L1StartingBlockTag.BlockHash) if config.L1StartingBlockTag == nil {
} else if config.L1StartingBlockTag.BlockNumber != nil { l1StartBlock, err = client.BlockByNumber(context.Background(), nil)
l1StartBlock, err = client.BlockByNumber(context.Background(), big.NewInt(config.L1StartingBlockTag.BlockNumber.Int64())) if err != nil {
return fmt.Errorf("cannot fetch latest block: %w", err)
}
tag := rpc.BlockNumberOrHashWithHash(l1StartBlock.Hash(), true)
config.L1StartingBlockTag = (*genesis.MarshalableRPCBlockNumberOrHash)(&tag)
} else if config.L1StartingBlockTag.BlockHash != nil {
l1StartBlock, err = client.BlockByHash(context.Background(), *config.L1StartingBlockTag.BlockHash)
if err != nil {
return fmt.Errorf("cannot fetch block by hash: %w", err)
}
} else if config.L1StartingBlockTag.BlockNumber != nil {
l1StartBlock, err = client.BlockByNumber(context.Background(), big.NewInt(config.L1StartingBlockTag.BlockNumber.Int64()))
if err != nil {
return fmt.Errorf("cannot fetch block by number: %w", err)
}
}
} }
if err != nil {
return fmt.Errorf("error getting l1 start block: %w", err) // Ensure that there is a starting L1 block
if l1StartBlock == nil {
return errors.New("no starting L1 block")
} }
// Sanity check the config. Do this after filling in the L1StartingBlockTag // Sanity check the config. Do this after filling in the L1StartingBlockTag
...@@ -204,16 +249,18 @@ var Subcommands = cli.Commands{ ...@@ -204,16 +249,18 @@ var Subcommands = cli.Commands{
return fmt.Errorf("generated rollup config does not pass validation: %w", err) return fmt.Errorf("generated rollup config does not pass validation: %w", err)
} }
if err := writeGenesisFile(ctx.String("outfile.l2"), l2Genesis); err != nil { if err := writeJSONFile(ctx.String("outfile.l2"), l2Genesis); err != nil {
return err return err
} }
return writeGenesisFile(ctx.String("outfile.rollup"), rollupConfig) return writeJSONFile(ctx.String("outfile.rollup"), rollupConfig)
}, },
}, },
} }
func writeGenesisFile(outfile string, input any) error { // writeJSONFile will write a JSON file to disk at the given path
f, err := os.OpenFile(outfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o755) // containing the JSON serialized input value.
func writeJSONFile(outfile string, input any) error {
f, err := os.OpenFile(outfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil { if err != nil {
return err return err
} }
...@@ -223,3 +270,55 @@ func writeGenesisFile(outfile string, input any) error { ...@@ -223,3 +270,55 @@ func writeGenesisFile(outfile string, input any) error {
enc.SetIndent("", " ") enc.SetIndent("", " ")
return enc.Encode(input) return enc.Encode(input)
} }
// rpcBlock represents the JSON serialization of a block from an Ethereum RPC.
type rpcBlock struct {
Hash common.Hash `json:"hash"`
Transactions []rpcTransaction `json:"transactions"`
UncleHashes []common.Hash `json:"uncles"`
Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"`
}
// rpcTransaction represents the JSON serialization of a transaction from an Ethereum RPC.
type rpcTransaction struct {
tx *types.Transaction
txExtraInfo
}
// txExtraInfo includes extra information about a transaction that is returned from
// and Ethereum RPC endpoint.
type txExtraInfo struct {
BlockNumber *string `json:"blockNumber,omitempty"`
BlockHash *common.Hash `json:"blockHash,omitempty"`
From *common.Address `json:"from,omitempty"`
}
// readBlockJSON will read a JSON file from disk containing a serialized block.
// This logic can break if the block format changes but there is no modular way
// to turn a block into JSON in go-ethereum.
func readBlockJSON(path string) (*types.Block, error) {
raw, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("block file at %s not found: %w", path, err)
}
var header types.Header
if err := json.Unmarshal(raw, &header); err != nil {
return nil, fmt.Errorf("cannot unmarshal block: %w", err)
}
var body rpcBlock
if err := json.Unmarshal(raw, &body); err != nil {
return nil, err
}
if len(body.UncleHashes) > 0 {
return nil, fmt.Errorf("cannot unmarshal block with uncles")
}
txs := make([]*types.Transaction, len(body.Transactions))
for i, tx := range body.Transactions {
txs[i] = tx.tx
}
return types.NewBlockWithHeader(&header).WithBody(txs, nil).WithWithdrawals(body.Withdrawals), nil
}
...@@ -2,8 +2,6 @@ FROM --platform=linux/amd64 debian:bullseye-slim as rust-build ...@@ -2,8 +2,6 @@ FROM --platform=linux/amd64 debian:bullseye-slim as rust-build
SHELL ["/bin/bash", "-c"] SHELL ["/bin/bash", "-c"]
WORKDIR /opt
ENV DEBIAN_FRONTEND=noninteractive ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y curl build-essential git clang lld curl apt-get install -y curl build-essential git clang lld curl
...@@ -21,16 +19,10 @@ RUN source $HOME/.profile && cargo install svm-rs ...@@ -21,16 +19,10 @@ RUN source $HOME/.profile && cargo install svm-rs
# Only diff from upstream docker image is this clone instead # Only diff from upstream docker image is this clone instead
# of COPY. We select a specific commit to use. # of COPY. We select a specific commit to use.
COPY ./.foundryrc ./.foundryrc COPY ./.foundryrc ./.foundryrc
RUN git clone https://github.com/foundry-rs/foundry.git ./foundry \ COPY ./ops/scripts/install-foundry.sh ./install-foundry.sh
&& cd foundry && git checkout $(cat ../.foundryrc)
WORKDIR /opt/foundry
RUN source $HOME/.profile && \ RUN curl -L https://foundry.paradigm.xyz | bash
cargo build --release && \ RUN source $HOME/.profile && ./install-foundry.sh
strip /opt/foundry/target/release/forge && \
strip /opt/foundry/target/release/cast && \
strip /opt/foundry/target/release/anvil
FROM --platform=linux/amd64 ghcr.io/crytic/echidna/echidna:v2.0.4 as echidna-test FROM --platform=linux/amd64 ghcr.io/crytic/echidna/echidna:v2.0.4 as echidna-test
...@@ -81,9 +73,9 @@ COPY --from=go-build /go/bin/geth /usr/local/bin/geth ...@@ -81,9 +73,9 @@ COPY --from=go-build /go/bin/geth /usr/local/bin/geth
COPY --from=rust-build /root/.cargo/bin /root/.cargo/bin COPY --from=rust-build /root/.cargo/bin /root/.cargo/bin
COPY --from=rust-build /root/.rustup /root/.rustup COPY --from=rust-build /root/.rustup /root/.rustup
# copy tools # copy tools
COPY --from=rust-build /opt/foundry/target/release/forge /usr/local/bin/forge COPY --from=rust-build /root/.foundry/bin/forge /usr/local/bin/forge
COPY --from=rust-build /opt/foundry/target/release/cast /usr/local/bin/cast COPY --from=rust-build /root/.foundry/bin/cast /usr/local/bin/cast
COPY --from=rust-build /opt/foundry/target/release/anvil /usr/local/bin/anvil COPY --from=rust-build /root/.foundry/bin/anvil /usr/local/bin/anvil
COPY --from=echidna-test /usr/local/bin/echidna-test /usr/local/bin/echidna-test COPY --from=echidna-test /usr/local/bin/echidna-test /usr/local/bin/echidna-test
......
...@@ -3,3 +3,4 @@ ...@@ -3,3 +3,4 @@
!/.abigenrc !/.abigenrc
!/.gethrc !/.gethrc
!/.nvmrc !/.nvmrc
!/ops/scripts/install-foundry.sh
#!/bin/bash
set -e
# Grab the `.foundryrc` commit hash.
SHA=$(cat ./.foundryrc)
# Check if there is a nightly tag corresponding to the `.foundryrc` commit hash
TAG="nightly-$SHA"
# Create a temporary directory
TMP_DIR=$(mktemp -d)
echo "Created tempdir @ $TMP_DIR"
# Clone the foundry repo temporarily. We do this to avoid the need for a personal access
# token to interact with the GitHub REST API, and clean it up after we're done.
git clone https://github.com/foundry-rs/foundry.git $TMP_DIR && cd $TMP_DIR
# If the nightly tag exists, we can download the pre-built binaries rather than building
# from source. Otherwise, clone the repository, check out the commit SHA, and build `forge`,
# `cast`, `anvil`, and `chisel` from source.
if git rev-parse "$TAG" >/dev/null 2>&1; then
echo "Nightly tag exists! Downloading prebuilt binaries..."
foundryup -v $TAG
else
echo "Nightly tag doesn't exist! Building from source..."
foundryup -C $SHA
fi
# Remove the temporary foundry repo; Used just for checking the nightly tag's existence.
rm -rf $TMP_DIR
echo "Removed tempdir @ $TMP_DIR"
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
"release:publish": "pnpm install --frozen-lockfile && npx nx run-many --target=build && pnpm build && changeset publish", "release:publish": "pnpm install --frozen-lockfile && npx nx run-many --target=build && pnpm build && changeset publish",
"release:version": "changeset version && pnpm install --lockfile-only", "release:version": "changeset version && pnpm install --lockfile-only",
"install:foundry": "curl -L https://foundry.paradigm.xyz | bash && pnpm update:foundry", "install:foundry": "curl -L https://foundry.paradigm.xyz | bash && pnpm update:foundry",
"update:foundry": "foundryup -C $(cat .foundryrc)", "update:foundry": "bash ./ops/scripts/install-foundry.sh",
"install:abigen": "go install github.com/ethereum/go-ethereum/cmd/abigen@$(cat .abigenrc)", "install:abigen": "go install github.com/ethereum/go-ethereum/cmd/abigen@$(cat .abigenrc)",
"print:abigen": "abigen --version | sed -e 's/[^0-9]/ /g' -e 's/^ *//g' -e 's/ *$//g' -e 's/ /./g' -e 's/^/v/'", "print:abigen": "abigen --version | sed -e 's/[^0-9]/ /g' -e 's/^ *//g' -e 's/ *$//g' -e 's/ /./g' -e 's/^/v/'",
"check:abigen": "[[ $(abigen --version | sed -e 's/[^0-9]/ /g' -e 's/^ *//g' -e 's/ *$//g' -e 's/ /./g' -e 's/^/v/') = $(cat .abigenrc) ]] && echo '✓ abigen versions match' || (echo '✗ abigen version mismatch. Run `pnpm upgrade:abigen` to upgrade.' && exit 1)", "check:abigen": "[[ $(abigen --version | sed -e 's/[^0-9]/ /g' -e 's/^ *//g' -e 's/ *$//g' -e 's/ /./g' -e 's/^/v/') = $(cat .abigenrc) ]] && echo '✓ abigen versions match' || (echo '✗ abigen version mismatch. Run `pnpm upgrade:abigen` to upgrade.' && exit 1)",
...@@ -40,8 +40,8 @@ ...@@ -40,8 +40,8 @@
"devDependencies": { "devDependencies": {
"@babel/eslint-parser": "^7.18.2", "@babel/eslint-parser": "^7.18.2",
"@changesets/changelog-github": "^0.4.8", "@changesets/changelog-github": "^0.4.8",
"@types/chai": "^4.3.8", "@types/chai": "^4.3.10",
"@types/chai-as-promised": "^7.1.4", "@types/chai-as-promised": "^7.1.8",
"@types/mocha": "^10.0.4", "@types/mocha": "^10.0.4",
"@types/node": "^20.9.0", "@types/node": "^20.9.0",
"@typescript-eslint/eslint-plugin": "^6.10.0", "@typescript-eslint/eslint-plugin": "^6.10.0",
......
...@@ -3,12 +3,14 @@ artifacts ...@@ -3,12 +3,14 @@ artifacts
forge-artifacts forge-artifacts
cache cache
broadcast broadcast
typechain
# Metrics # Metrics
coverage.out coverage.out
.resource-metering.csv .resource-metering.csv
# Testing State
.testdata
# Scripts # Scripts
scripts/go-ffi/go-ffi scripts/go-ffi/go-ffi
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
"validate-spacers": "pnpm build && pnpm validate-spacers:no-build", "validate-spacers": "pnpm build && pnpm validate-spacers:no-build",
"slither": "./scripts/slither.sh", "slither": "./scripts/slither.sh",
"slither:triage": "TRIAGE_MODE=1 ./scripts/slither.sh", "slither:triage": "TRIAGE_MODE=1 ./scripts/slither.sh",
"clean": "rm -rf ./artifacts ./forge-artifacts ./cache ./tsconfig.tsbuildinfo ./tsconfig.build.tsbuildinfo ./scripts/go-ffi/go-ffi", "clean": "rm -rf ./artifacts ./forge-artifacts ./cache ./tsconfig.tsbuildinfo ./tsconfig.build.tsbuildinfo ./scripts/go-ffi/go-ffi ./.testdata",
"preinstall": "npx only-allow pnpm", "preinstall": "npx only-allow pnpm",
"pre-pr:no-build": "pnpm gas-snapshot:no-build && pnpm storage-snapshot && pnpm semver-lock && pnpm autogen:invariant-docs && pnpm lint && pnpm bindings:go", "pre-pr:no-build": "pnpm gas-snapshot:no-build && pnpm storage-snapshot && pnpm semver-lock && pnpm autogen:invariant-docs && pnpm lint && pnpm bindings:go",
"pre-pr": "pnpm clean && pnpm build:go-ffi && pnpm build && pnpm pre-pr:no-build", "pre-pr": "pnpm clean && pnpm build:go-ffi && pnpm build && pnpm pre-pr:no-build",
......
#!/usr/bin/env bash
# Create a L2 genesis.json suitable for the solidity tests to
# ingest using `vm.loadAllocs(string)`.
# This script depends on the relative path to the op-node from
# contracts-bedrock
SCRIPTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" > /dev/null && pwd)"
CONTRACTS_DIR="$(realpath "$SCRIPTS_DIR/..")"
MONOREPO_BASE="$(realpath "$CONTRACTS_DIR/../..")"
DEPLOY_ARTIFACT="$CONTRACTS_DIR/deployments/hardhat/.deploy"
OP_NODE="$MONOREPO_BASE/op-node/cmd/main.go"
L1_STARTING_BLOCK_PATH="$CONTRACTS_DIR/test/mocks/block.json"
TESTDATA_DIR="$CONTRACTS_DIR/.testdata"
OUTFILE_L2="$TESTDATA_DIR/genesis.json"
OUTFILE_ROLLUP="$TESTDATA_DIR/rollup.json"
OUTFILE_ALLOC="$TESTDATA_DIR/alloc.json"
if [ ! -f "$DEPLOY_ARTIFACT" ]; then
forge script $CONTRACTS_DIR/scripts/Deploy.s.sol:Deploy 2>&1 /dev/null
fi
if [ ! -d "$TESTDATA_DIR" ]; then
mkdir -p "$TESTDATA_DIR"
go run $OP_NODE genesis l2 \
--deploy-config "$CONTRACTS_DIR/deploy-config/hardhat.json" \
--l1-deployments "$DEPLOY_ARTIFACT" \
--l1-starting-block "$L1_STARTING_BLOCK_PATH" \
--outfile.l2 "$OUTFILE_L2" \
--outfile.rollup "$OUTFILE_ROLLUP" >/dev/null 2>&1
fi
{
"hash": "0xfd3c5e25a80f54a53c58bd3ad8c076dc1c0cdbd44ec2164d2d2b8cc50481cb78",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"miner": "0x0000000000000000000000000000000000000000",
"stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
"transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
"number": "0x0",
"gasUsed": "0x0",
"gasLimit": "0x1c9c380",
"extraData": "0x",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"timestamp": "0x654caabb",
"difficulty": "0x0",
"totalDifficulty": "0x0",
"sealFields": [
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0x0000000000000000"
],
"uncles": [],
"transactions": [],
"size": "0x202",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"nonce": "0x0000000000000000",
"baseFeePerGas": "0x3b9aca00"
}
...@@ -41,8 +41,8 @@ ...@@ -41,8 +41,8 @@
"@ethersproject/transactions": "^5.7.0", "@ethersproject/transactions": "^5.7.0",
"@nomiclabs/hardhat-ethers": "^2.2.3", "@nomiclabs/hardhat-ethers": "^2.2.3",
"@nomiclabs/hardhat-waffle": "^2.0.1", "@nomiclabs/hardhat-waffle": "^2.0.1",
"@types/chai": "^4.3.8", "@types/chai": "^4.3.10",
"@types/chai-as-promised": "^7.1.5", "@types/chai-as-promised": "^7.1.8",
"@types/mocha": "^10.0.4", "@types/mocha": "^10.0.4",
"@types/node": "^20.9.0", "@types/node": "^20.9.0",
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
......
...@@ -22,11 +22,11 @@ importers: ...@@ -22,11 +22,11 @@ importers:
specifier: ^0.4.8 specifier: ^0.4.8
version: 0.4.8 version: 0.4.8
'@types/chai': '@types/chai':
specifier: ^4.3.8 specifier: ^4.3.10
version: 4.3.8 version: 4.3.10
'@types/chai-as-promised': '@types/chai-as-promised':
specifier: ^7.1.4 specifier: ^7.1.8
version: 7.1.5 version: 7.1.8
'@types/mocha': '@types/mocha':
specifier: ^10.0.4 specifier: ^10.0.4
version: 10.0.4 version: 10.0.4
...@@ -484,11 +484,11 @@ importers: ...@@ -484,11 +484,11 @@ importers:
specifier: ^2.0.1 specifier: ^2.0.1
version: 2.0.1(@nomiclabs/hardhat-ethers@2.2.3)(ethereum-waffle@4.0.10)(ethers@5.7.2)(hardhat@2.19.0) version: 2.0.1(@nomiclabs/hardhat-ethers@2.2.3)(ethereum-waffle@4.0.10)(ethers@5.7.2)(hardhat@2.19.0)
'@types/chai': '@types/chai':
specifier: ^4.3.8 specifier: ^4.3.10
version: 4.3.8 version: 4.3.10
'@types/chai-as-promised': '@types/chai-as-promised':
specifier: ^7.1.5 specifier: ^7.1.8
version: 7.1.5 version: 7.1.8
'@types/mocha': '@types/mocha':
specifier: ^10.0.4 specifier: ^10.0.4
version: 10.0.4 version: 10.0.4
...@@ -3876,20 +3876,20 @@ packages: ...@@ -3876,20 +3876,20 @@ packages:
'@types/node': 20.9.0 '@types/node': 20.9.0
dev: true dev: true
/@types/chai-as-promised@7.1.5: /@types/chai-as-promised@7.1.8:
resolution: {integrity: sha512-jStwss93SITGBwt/niYrkf2C+/1KTeZCZl1LaeezTlqppAKeoQC7jxyqYuP72sxBGKCIbw7oHgbYssIRzT5FCQ==} resolution: {integrity: sha512-ThlRVIJhr69FLlh6IctTXFkmhtP3NpMZ2QGq69StYLyKZFp/HOp1VdKZj7RvfNWYYcJ1xlbLGLLWj1UvP5u/Gw==}
dependencies: dependencies:
'@types/chai': 4.3.8 '@types/chai': 4.3.10
dev: true dev: true
/@types/chai-subset@1.3.3: /@types/chai-subset@1.3.3:
resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==} resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==}
dependencies: dependencies:
'@types/chai': 4.3.8 '@types/chai': 4.3.10
dev: true dev: true
/@types/chai@4.3.7: /@types/chai@4.3.10:
resolution: {integrity: sha512-/k+vesl92vMvMygmQrFe9Aimxi6oQXFUX9mA5HanTrKUSAMoLauSi6PNFOdRw0oeqilaW600GNx2vSaT2f8aIQ==} resolution: {integrity: sha512-of+ICnbqjmFCiixUnqRulbylyXQrPqIGf/B3Jax1wIF3DvSheysQxAWvqHhZiW3IQrycvokcLcFQlveGp+vyNg==}
dev: true dev: true
/@types/chai@4.3.8: /@types/chai@4.3.8:
...@@ -4162,7 +4162,7 @@ packages: ...@@ -4162,7 +4162,7 @@ packages:
/@types/sinon-chai@3.2.5: /@types/sinon-chai@3.2.5:
resolution: {integrity: sha512-bKQqIpew7mmIGNRlxW6Zli/QVyc3zikpGzCa797B/tRnD9OtHvZ/ts8sYXV+Ilj9u3QRaUEM8xrjgd1gwm1BpQ==} resolution: {integrity: sha512-bKQqIpew7mmIGNRlxW6Zli/QVyc3zikpGzCa797B/tRnD9OtHvZ/ts8sYXV+Ilj9u3QRaUEM8xrjgd1gwm1BpQ==}
dependencies: dependencies:
'@types/chai': 4.3.8 '@types/chai': 4.3.10
'@types/sinon': 10.0.2 '@types/sinon': 10.0.2
dev: true dev: true
...@@ -14602,7 +14602,7 @@ packages: ...@@ -14602,7 +14602,7 @@ packages:
webdriverio: webdriverio:
optional: true optional: true
dependencies: dependencies:
'@types/chai': 4.3.8 '@types/chai': 4.3.10
'@types/chai-subset': 1.3.3 '@types/chai-subset': 1.3.3
'@types/node': 20.9.0 '@types/node': 20.9.0
'@vitest/expect': 0.34.1 '@vitest/expect': 0.34.1
...@@ -14667,7 +14667,7 @@ packages: ...@@ -14667,7 +14667,7 @@ packages:
webdriverio: webdriverio:
optional: true optional: true
dependencies: dependencies:
'@types/chai': 4.3.8 '@types/chai': 4.3.10
'@types/chai-subset': 1.3.3 '@types/chai-subset': 1.3.3
'@types/node': 20.9.0 '@types/node': 20.9.0
'@vitest/expect': 0.34.2 '@vitest/expect': 0.34.2
...@@ -14732,7 +14732,7 @@ packages: ...@@ -14732,7 +14732,7 @@ packages:
webdriverio: webdriverio:
optional: true optional: true
dependencies: dependencies:
'@types/chai': 4.3.7 '@types/chai': 4.3.8
'@types/chai-subset': 1.3.3 '@types/chai-subset': 1.3.3
'@types/node': 20.8.9 '@types/node': 20.8.9
'@vitest/expect': 0.34.2 '@vitest/expect': 0.34.2
...@@ -14798,7 +14798,7 @@ packages: ...@@ -14798,7 +14798,7 @@ packages:
webdriverio: webdriverio:
optional: true optional: true
dependencies: dependencies:
'@types/chai': 4.3.7 '@types/chai': 4.3.8
'@types/chai-subset': 1.3.3 '@types/chai-subset': 1.3.3
'@types/node': 20.8.9 '@types/node': 20.8.9
'@vitest/expect': 0.34.4 '@vitest/expect': 0.34.4
......
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