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

Merge branch 'develop' into dependabot/go_modules/github.com/hashicorp/golang-lru-1.0.2

parents b61a2c6e 5e7efe5a
...@@ -822,11 +822,9 @@ jobs: ...@@ -822,11 +822,9 @@ jobs:
name: print go's available MIPS targets name: print go's available MIPS targets
command: go tool dist list | grep mips command: go tool dist list | grep mips
- run: - run:
name: generate cannon prestate name: Run all init steps for op-e2e
command: make cannon-prestate command: make pre-test
- run: working_directory: <<parameters.module>>
name: generate L1 state
command: make devnet-allocs
- run: - run:
name: run tests name: run tests
command: | command: |
......
...@@ -28,11 +28,8 @@ func (e *EthBridge) GetDepositsByBlockRange(ctx context.Context, start, end uint ...@@ -28,11 +28,8 @@ func (e *EthBridge) GetDepositsByBlockRange(ctx context.Context, start, end uint
End: &end, End: &end,
} }
var iter *bindings.L1StandardBridgeETHDepositInitiatedIterator iter, err := backoff.Do(ctx, 3, backoff.Exponential(), func() (*bindings.L1StandardBridgeETHDepositInitiatedIterator, error) {
err := backoff.Do(3, backoff.Exponential(), func() error { return e.contract.FilterETHDepositInitiated(opts, nil, nil)
var err error
iter, err = e.contract.FilterETHDepositInitiated(opts, nil, nil)
return err
}) })
if err != nil { if err != nil {
return nil, err return nil, err
......
...@@ -37,11 +37,8 @@ func (p *Portal) GetProvenWithdrawalsByBlockRange(ctx context.Context, start, en ...@@ -37,11 +37,8 @@ func (p *Portal) GetProvenWithdrawalsByBlockRange(ctx context.Context, start, en
End: &end, End: &end,
} }
var iter *bindings.OptimismPortalWithdrawalProvenIterator iter, err := backoff.Do(ctx, 3, backoff.Exponential(), func() (*bindings.OptimismPortalWithdrawalProvenIterator, error) {
err := backoff.Do(3, backoff.Exponential(), func() error { return p.contract.FilterWithdrawalProven(opts, nil, nil, nil)
var err error
iter, err = p.contract.FilterWithdrawalProven(opts, nil, nil, nil)
return err
}) })
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -71,11 +68,8 @@ func (p *Portal) GetFinalizedWithdrawalsByBlockRange(ctx context.Context, start, ...@@ -71,11 +68,8 @@ func (p *Portal) GetFinalizedWithdrawalsByBlockRange(ctx context.Context, start,
End: &end, End: &end,
} }
var iter *bindings.OptimismPortalWithdrawalFinalizedIterator iter, err := backoff.Do(ctx, 3, backoff.Exponential(), func() (*bindings.OptimismPortalWithdrawalFinalizedIterator, error) {
err := backoff.Do(3, backoff.Exponential(), func() error { return p.contract.FilterWithdrawalFinalized(opts, nil)
var err error
iter, err = p.contract.FilterWithdrawalFinalized(opts, nil)
return err
}) })
if err != nil { if err != nil {
return nil, err return nil, err
......
...@@ -28,11 +28,8 @@ func (s *StandardBridge) GetDepositsByBlockRange(ctx context.Context, start, end ...@@ -28,11 +28,8 @@ func (s *StandardBridge) GetDepositsByBlockRange(ctx context.Context, start, end
End: &end, End: &end,
} }
var iter *bindings.L1StandardBridgeERC20DepositInitiatedIterator iter, err := backoff.Do(ctx, 3, backoff.Exponential(), func() (*bindings.L1StandardBridgeERC20DepositInitiatedIterator, error) {
err := backoff.Do(3, backoff.Exponential(), func() error { return s.contract.FilterERC20DepositInitiated(opts, nil, nil, nil)
var err error
iter, err = s.contract.FilterERC20DepositInitiated(opts, nil, nil, nil)
return err
}) })
if err != nil { if err != nil {
return nil, err return nil, err
......
...@@ -35,11 +35,8 @@ func (s *StandardBridge) GetWithdrawalsByBlockRange(ctx context.Context, start, ...@@ -35,11 +35,8 @@ func (s *StandardBridge) GetWithdrawalsByBlockRange(ctx context.Context, start,
End: &end, End: &end,
} }
var iter *bindings.L2StandardBridgeWithdrawalInitiatedIterator iter, err := backoff.Do(ctx, 3, backoff.Exponential(), func() (*bindings.L2StandardBridgeWithdrawalInitiatedIterator, error) {
err := backoff.Do(3, backoff.Exponential(), func() error { return s.l2SB.FilterWithdrawalInitiated(opts, nil, nil, nil)
var err error
iter, err = s.l2SB.FilterWithdrawalInitiated(opts, nil, nil, nil)
return err
}) })
if err != nil { if err != nil {
return nil, err return nil, err
......
...@@ -10,11 +10,7 @@ import ( ...@@ -10,11 +10,7 @@ import (
// HeaderByNumberWithRetry retries getting headers. // HeaderByNumberWithRetry retries getting headers.
func HeaderByNumberWithRetry(ctx context.Context, client *ethclient.Client) (*types.Header, error) { func HeaderByNumberWithRetry(ctx context.Context, client *ethclient.Client) (*types.Header, error) {
var res *types.Header return backoff.Do(ctx, 3, backoff.Exponential(), func() (*types.Header, error) {
err := backoff.DoCtx(ctx, 3, backoff.Exponential(), func() error { return client.HeaderByNumber(ctx, nil)
var err error
res, err = client.HeaderByNumber(ctx, nil)
return err
}) })
return res, err
} }
...@@ -50,17 +50,17 @@ func NewBatchSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger, m metrics.Metri ...@@ -50,17 +50,17 @@ func NewBatchSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger, m metrics.Metri
// Connect to L1 and L2 providers. Perform these last since they are the // Connect to L1 and L2 providers. Perform these last since they are the
// most expensive. // most expensive.
l1Client, err := opclient.DialEthClientWithTimeout(ctx, cfg.L1EthRpc, opclient.DefaultDialTimeout) l1Client, err := opclient.DialEthClientWithTimeout(opclient.DefaultDialTimeout, l, cfg.L1EthRpc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
l2Client, err := opclient.DialEthClientWithTimeout(ctx, cfg.L2EthRpc, opclient.DefaultDialTimeout) l2Client, err := opclient.DialEthClientWithTimeout(opclient.DefaultDialTimeout, l, cfg.L2EthRpc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
rollupClient, err := opclient.DialRollupClientWithTimeout(ctx, cfg.RollupRpc, opclient.DefaultDialTimeout) rollupClient, err := opclient.DialRollupClientWithTimeout(opclient.DefaultDialTimeout, l, cfg.RollupRpc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
This diff is collapsed.
This diff is collapsed.
...@@ -423,6 +423,19 @@ func checkL2ERC721Bridge(addr common.Address, client *ethclient.Client) error { ...@@ -423,6 +423,19 @@ func checkL2ERC721Bridge(addr common.Address, client *ethclient.Client) error {
if otherBridge == (common.Address{}) { if otherBridge == (common.Address{}) {
return errors.New("L2ERC721Bridge.OTHERBRIDGE is zero address") return errors.New("L2ERC721Bridge.OTHERBRIDGE is zero address")
} }
initialized, err := getInitialized("L2ERC721Bridge", addr, client)
if err != nil {
return err
}
log.Info("L2ERC721Bridge", "_initialized", initialized)
initializing, err := getInitializing("L2ERC721Bridge", addr, client)
if err != nil {
return err
}
log.Info("L2ERC721Bridge", "_initializing", initializing)
version, err := contract.Version(&bind.CallOpts{}) version, err := contract.Version(&bind.CallOpts{})
if err != nil { if err != nil {
return err return err
...@@ -614,6 +627,12 @@ func checkL2StandardBridge(addr common.Address, client *ethclient.Client) error ...@@ -614,6 +627,12 @@ func checkL2StandardBridge(addr common.Address, client *ethclient.Client) error
} }
log.Info("L2StandardBridge", "_initialized", initialized) log.Info("L2StandardBridge", "_initialized", initialized)
initializing, err := getInitializing("L2StandardBridge", addr, client)
if err != nil {
return err
}
log.Info("L2StandardBridge", "_initializing", initializing)
log.Info("L2StandardBridge version", "version", version) log.Info("L2StandardBridge version", "version", version)
return nil return nil
} }
...@@ -727,6 +746,12 @@ func checkL2CrossDomainMessenger(addr common.Address, client *ethclient.Client) ...@@ -727,6 +746,12 @@ func checkL2CrossDomainMessenger(addr common.Address, client *ethclient.Client)
} }
log.Info("L2CrossDomainMessenger", "_initialized", initialized) log.Info("L2CrossDomainMessenger", "_initialized", initialized)
initializing, err := getInitializing("L2CrossDomainMessenger", addr, client)
if err != nil {
return err
}
log.Info("L2CrossDomainMessenger", "_initializing", initializing)
log.Info("L2CrossDomainMessenger version", "version", version) log.Info("L2CrossDomainMessenger version", "version", version)
return nil return nil
} }
...@@ -823,11 +848,33 @@ func getEIP1967ImplementationAddress(client *ethclient.Client, addr common.Addre ...@@ -823,11 +848,33 @@ func getEIP1967ImplementationAddress(client *ethclient.Client, addr common.Addre
// This is an incrementing number that starts at 1 and increments each time that // This is an incrementing number that starts at 1 and increments each time that
// the contract is upgraded. // the contract is upgraded.
func getInitialized(name string, addr common.Address, client *ethclient.Client) (*big.Int, error) { func getInitialized(name string, addr common.Address, client *ethclient.Client) (*big.Int, error) {
value, err := getStorageValue(name, "_initialized", addr, client)
if err != nil {
return nil, err
}
return new(big.Int).SetBytes(value), nil
}
// getInitializing will get the _initializing value in storage of a contract.
func getInitializing(name string, addr common.Address, client *ethclient.Client) (bool, error) {
value, err := getStorageValue(name, "_initializing", addr, client)
if err != nil {
return false, err
}
if len(value) != 1 {
return false, fmt.Errorf("Unexpected length for _initializing: %d", len(value))
}
return value[0] == 1, nil
}
// getStorageValue will get the value of a named storage slot in a contract. It isn't smart about
// automatically converting from a byte slice to a type, it is the caller's responsibility to do that.
func getStorageValue(name, entryName string, addr common.Address, client *ethclient.Client) ([]byte, error) {
layout, err := bindings.GetStorageLayout(name) layout, err := bindings.GetStorageLayout(name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
entry, err := layout.GetStorageLayoutEntry("_initialized") entry, err := layout.GetStorageLayoutEntry(entryName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -848,6 +895,5 @@ func getInitialized(name string, addr common.Address, client *ethclient.Client) ...@@ -848,6 +895,5 @@ func getInitialized(name string, addr common.Address, client *ethclient.Client)
for i, j := 0, len(slice)-1; i < j; i, j = i+1, j-1 { for i, j := 0, len(slice)-1; i < j; i, j = i+1, j-1 {
slice[i], slice[j] = slice[j], slice[i] slice[i], slice[j] = slice[j], slice[i]
} }
initialized := new(big.Int).SetBytes(slice[entry.Offset : entry.Offset+typ.NumberOfBytes]) return slice[entry.Offset : entry.Offset+typ.NumberOfBytes], nil
return initialized, nil
} }
...@@ -25,7 +25,6 @@ var ( ...@@ -25,7 +25,6 @@ var (
cannonL2 = "http://example.com:9545" cannonL2 = "http://example.com:9545"
alphabetTrace = "abcdefghijz" alphabetTrace = "abcdefghijz"
agreeWithProposedOutput = "true" agreeWithProposedOutput = "true"
gameDepth = "4"
) )
func TestLogLevel(t *testing.T) { func TestLogLevel(t *testing.T) {
...@@ -45,14 +44,14 @@ func TestLogLevel(t *testing.T) { ...@@ -45,14 +44,14 @@ func TestLogLevel(t *testing.T) {
func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) { func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs(config.TraceTypeAlphabet)) cfg := configForArgs(t, addRequiredArgs(config.TraceTypeAlphabet))
defaultCfg := config.NewConfig(l1EthRpc, common.HexToAddress(gameAddressValue), config.TraceTypeAlphabet, true, 4) defaultCfg := config.NewConfig(l1EthRpc, common.HexToAddress(gameAddressValue), config.TraceTypeAlphabet, true)
// Add in the extra CLI options required when using alphabet trace type // Add in the extra CLI options required when using alphabet trace type
defaultCfg.AlphabetTrace = alphabetTrace defaultCfg.AlphabetTrace = alphabetTrace
require.Equal(t, defaultCfg, cfg) require.Equal(t, defaultCfg, cfg)
} }
func TestDefaultConfigIsValid(t *testing.T) { func TestDefaultConfigIsValid(t *testing.T) {
cfg := config.NewConfig(l1EthRpc, common.HexToAddress(gameAddressValue), config.TraceTypeAlphabet, true, 4) cfg := config.NewConfig(l1EthRpc, common.HexToAddress(gameAddressValue), config.TraceTypeAlphabet, true)
// Add in options that are required based on the specific trace type // Add in options that are required based on the specific trace type
// To avoid needing to specify unused options, these aren't included in the params for NewConfig // To avoid needing to specify unused options, these aren't included in the params for NewConfig
cfg.AlphabetTrace = alphabetTrace cfg.AlphabetTrace = alphabetTrace
...@@ -130,18 +129,6 @@ func TestAgreeWithProposedOutput(t *testing.T) { ...@@ -130,18 +129,6 @@ func TestAgreeWithProposedOutput(t *testing.T) {
}) })
} }
func TestGameDepth(t *testing.T) {
t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag game-depth is required", addRequiredArgsExcept(config.TraceTypeAlphabet, "--game-depth"))
})
t.Run("Valid", func(t *testing.T) {
value := "4"
cfg := configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--game-depth", "--game-depth="+value))
require.Equal(t, value, fmt.Sprint(cfg.GameDepth))
})
}
func TestCannonBin(t *testing.T) { func TestCannonBin(t *testing.T) {
t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--cannon-bin")) configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--cannon-bin"))
...@@ -327,7 +314,6 @@ func addRequiredArgsExcept(traceType config.TraceType, name string, optionalArgs ...@@ -327,7 +314,6 @@ func addRequiredArgsExcept(traceType config.TraceType, name string, optionalArgs
func requiredArgs(traceType config.TraceType) map[string]string { func requiredArgs(traceType config.TraceType) map[string]string {
args := map[string]string{ args := map[string]string{
"--game-depth": gameDepth,
"--agree-with-proposed-output": agreeWithProposedOutput, "--agree-with-proposed-output": agreeWithProposedOutput,
"--l1-eth-rpc": l1EthRpc, "--l1-eth-rpc": l1EthRpc,
"--game-address": gameAddressValue, "--game-address": gameAddressValue,
......
...@@ -58,7 +58,7 @@ func ValidTraceType(value TraceType) bool { ...@@ -58,7 +58,7 @@ func ValidTraceType(value TraceType) bool {
return false return false
} }
const DefaultCannonSnapshotFreq = uint(10_000) const DefaultCannonSnapshotFreq = uint(1_000_000_000)
// Config is a well typed config that is parsed from the CLI params. // Config is a well typed config that is parsed from the CLI params.
// This also contains config options for auxiliary services. // This also contains config options for auxiliary services.
...@@ -67,7 +67,6 @@ type Config struct { ...@@ -67,7 +67,6 @@ type Config struct {
L1EthRpc string // L1 RPC Url L1EthRpc string // L1 RPC Url
GameAddress common.Address // Address of the fault game GameAddress common.Address // Address of the fault game
AgreeWithProposedOutput bool // Temporary config if we agree or disagree with the posted output AgreeWithProposedOutput bool // Temporary config if we agree or disagree with the posted output
GameDepth int // Depth of the game tree
TraceType TraceType // Type of trace TraceType TraceType // Type of trace
...@@ -93,14 +92,12 @@ func NewConfig( ...@@ -93,14 +92,12 @@ func NewConfig(
gameAddress common.Address, gameAddress common.Address,
traceType TraceType, traceType TraceType,
agreeWithProposedOutput bool, agreeWithProposedOutput bool,
gameDepth int,
) Config { ) Config {
return Config{ return Config{
L1EthRpc: l1EthRpc, L1EthRpc: l1EthRpc,
GameAddress: gameAddress, GameAddress: gameAddress,
AgreeWithProposedOutput: agreeWithProposedOutput, AgreeWithProposedOutput: agreeWithProposedOutput,
GameDepth: gameDepth,
TraceType: traceType, TraceType: traceType,
......
...@@ -19,11 +19,10 @@ var ( ...@@ -19,11 +19,10 @@ var (
validCannonDatadir = "/tmp/cannon" validCannonDatadir = "/tmp/cannon"
validCannonL2 = "http://localhost:9545" validCannonL2 = "http://localhost:9545"
agreeWithProposedOutput = true agreeWithProposedOutput = true
gameDepth = 4
) )
func validConfig(traceType TraceType) Config { func validConfig(traceType TraceType) Config {
cfg := NewConfig(validL1EthRpc, validGameAddress, traceType, agreeWithProposedOutput, gameDepth) cfg := NewConfig(validL1EthRpc, validGameAddress, traceType, agreeWithProposedOutput)
switch traceType { switch traceType {
case TraceTypeAlphabet: case TraceTypeAlphabet:
cfg.AlphabetTrace = validAlphabetTrace cfg.AlphabetTrace = validAlphabetTrace
......
...@@ -141,7 +141,7 @@ func (a *Agent) step(ctx context.Context, claim types.Claim, game types.Game) er ...@@ -141,7 +141,7 @@ func (a *Agent) step(ctx context.Context, claim types.Claim, game types.Game) er
} }
a.log.Info("Updating oracle data", "oracleKey", oracleData.OracleKey, "oracleData", oracleData.OracleData) a.log.Info("Updating oracle data", "oracleKey", oracleData.OracleKey, "oracleData", oracleData.OracleData)
if err := a.updater.UpdateOracle(ctx, *oracleData); err != nil { if err := a.updater.UpdateOracle(ctx, oracleData); err != nil {
return fmt.Errorf("failed to load oracle data: %w", err) return fmt.Errorf("failed to load oracle data: %w", err)
} }
......
...@@ -21,7 +21,7 @@ func NewOracleUpdater(logger log.Logger) *alphabetUpdater { ...@@ -21,7 +21,7 @@ func NewOracleUpdater(logger log.Logger) *alphabetUpdater {
} }
// UpdateOracle updates the oracle with the given data. // UpdateOracle updates the oracle with the given data.
func (u *alphabetUpdater) UpdateOracle(ctx context.Context, data types.PreimageOracleData) error { func (u *alphabetUpdater) UpdateOracle(ctx context.Context, data *types.PreimageOracleData) error {
u.logger.Info("alphabet oracle updater called") u.logger.Info("alphabet oracle updater called")
return nil return nil
} }
...@@ -15,5 +15,5 @@ import ( ...@@ -15,5 +15,5 @@ import (
func TestAlphabetUpdater(t *testing.T) { func TestAlphabetUpdater(t *testing.T) {
logger := testlog.Logger(t, log.LvlInfo) logger := testlog.Logger(t, log.LvlInfo)
updater := NewOracleUpdater(logger) updater := NewOracleUpdater(logger)
require.Nil(t, updater.UpdateOracle(context.Background(), types.PreimageOracleData{})) require.Nil(t, updater.UpdateOracle(context.Background(), &types.PreimageOracleData{}))
} }
package cannon
import (
"encoding/json"
"fmt"
"os"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
)
func parseState(path string) (*mipsevm.State, error) {
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("cannot open state file (%v): %w", path, err)
}
defer file.Close()
var state mipsevm.State
err = json.NewDecoder(file).Decode(&state)
if err != nil {
return nil, fmt.Errorf("invalid mipsevm state (%v): %w", path, err)
}
return &state, nil
}
...@@ -20,6 +20,7 @@ import ( ...@@ -20,6 +20,7 @@ import (
const ( const (
snapsDir = "snapshots" snapsDir = "snapshots"
preimagesDir = "preimages" preimagesDir = "preimages"
finalState = "final.json"
) )
var snapshotNameRegexp = regexp.MustCompile(`^[0-9]+\.json$`) var snapshotNameRegexp = regexp.MustCompile(`^[0-9]+\.json$`)
...@@ -71,15 +72,21 @@ func (e *Executor) GenerateProof(ctx context.Context, dir string, i uint64) erro ...@@ -71,15 +72,21 @@ func (e *Executor) GenerateProof(ctx context.Context, dir string, i uint64) erro
} }
proofDir := filepath.Join(dir, proofsDir) proofDir := filepath.Join(dir, proofsDir)
dataDir := filepath.Join(e.dataDir, preimagesDir) dataDir := filepath.Join(e.dataDir, preimagesDir)
lastGeneratedState := filepath.Join(dir, finalState)
args := []string{ args := []string{
"run", "run",
"--input", start, "--input", start,
"--output", filepath.Join(dir, "out.json"), "--output", lastGeneratedState,
"--meta", "", "--meta", "",
"--proof-at", "=" + strconv.FormatUint(i, 10), "--proof-at", "=" + strconv.FormatUint(i, 10),
"--proof-fmt", filepath.Join(proofDir, "%d.json"), "--proof-fmt", filepath.Join(proofDir, "%d.json"),
"--snapshot-at", "%" + strconv.FormatUint(uint64(e.snapshotFreq), 10), "--snapshot-at", "%" + strconv.FormatUint(uint64(e.snapshotFreq), 10),
"--snapshot-fmt", filepath.Join(snapshotDir, "%d.json"), "--snapshot-fmt", filepath.Join(snapshotDir, "%d.json"),
}
if i < math.MaxUint64 {
args = append(args, "--stop-at", "="+strconv.FormatUint(i+1, 10))
}
args = append(args,
"--", "--",
e.server, "--server", e.server, "--server",
"--l1", e.l1, "--l1", e.l1,
...@@ -90,10 +97,7 @@ func (e *Executor) GenerateProof(ctx context.Context, dir string, i uint64) erro ...@@ -90,10 +97,7 @@ func (e *Executor) GenerateProof(ctx context.Context, dir string, i uint64) erro
"--l2.outputroot", e.inputs.l2OutputRoot.Hex(), "--l2.outputroot", e.inputs.l2OutputRoot.Hex(),
"--l2.claim", e.inputs.l2Claim.Hex(), "--l2.claim", e.inputs.l2Claim.Hex(),
"--l2.blocknumber", e.inputs.l2BlockNumber.Text(10), "--l2.blocknumber", e.inputs.l2BlockNumber.Text(10),
} )
if i < math.MaxUint64 {
args = append(args, "--stop-at", "="+strconv.FormatUint(i+1, 10))
}
if e.network != "" { if e.network != "" {
args = append(args, "--network", e.network) args = append(args, "--network", e.network)
} }
......
...@@ -21,7 +21,7 @@ const execTestCannonPrestate = "/foo/pre.json" ...@@ -21,7 +21,7 @@ const execTestCannonPrestate = "/foo/pre.json"
func TestGenerateProof(t *testing.T) { func TestGenerateProof(t *testing.T) {
input := "starting.json" input := "starting.json"
cfg := config.NewConfig("http://localhost:8888", common.Address{0xaa}, config.TraceTypeCannon, true, 5) cfg := config.NewConfig("http://localhost:8888", common.Address{0xaa}, config.TraceTypeCannon, true)
cfg.CannonDatadir = t.TempDir() cfg.CannonDatadir = t.TempDir()
cfg.CannonAbsolutePreState = "pre.json" cfg.CannonAbsolutePreState = "pre.json"
cfg.CannonBin = "./bin/cannon" cfg.CannonBin = "./bin/cannon"
...@@ -36,7 +36,7 @@ func TestGenerateProof(t *testing.T) { ...@@ -36,7 +36,7 @@ func TestGenerateProof(t *testing.T) {
l2Claim: common.Hash{0x44}, l2Claim: common.Hash{0x44},
l2BlockNumber: big.NewInt(3333), l2BlockNumber: big.NewInt(3333),
} }
captureExec := func(cfg config.Config, proofAt uint64) (string, string, map[string]string) { captureExec := func(t *testing.T, cfg config.Config, proofAt uint64) (string, string, map[string]string) {
executor := NewExecutor(testlog.Logger(t, log.LvlInfo), &cfg, inputs) executor := NewExecutor(testlog.Logger(t, log.LvlInfo), &cfg, inputs)
executor.selectSnapshot = func(logger log.Logger, dir string, absolutePreState string, i uint64) (string, error) { executor.selectSnapshot = func(logger log.Logger, dir string, absolutePreState string, i uint64) (string, error) {
return input, nil return input, nil
...@@ -67,7 +67,7 @@ func TestGenerateProof(t *testing.T) { ...@@ -67,7 +67,7 @@ func TestGenerateProof(t *testing.T) {
cfg.CannonNetwork = "mainnet" cfg.CannonNetwork = "mainnet"
cfg.CannonRollupConfigPath = "" cfg.CannonRollupConfigPath = ""
cfg.CannonL2GenesisPath = "" cfg.CannonL2GenesisPath = ""
binary, subcommand, args := captureExec(cfg, 150_000_000) binary, subcommand, args := captureExec(t, cfg, 150_000_000)
require.DirExists(t, filepath.Join(cfg.CannonDatadir, preimagesDir)) require.DirExists(t, filepath.Join(cfg.CannonDatadir, preimagesDir))
require.DirExists(t, filepath.Join(cfg.CannonDatadir, proofsDir)) require.DirExists(t, filepath.Join(cfg.CannonDatadir, proofsDir))
require.DirExists(t, filepath.Join(cfg.CannonDatadir, snapsDir)) require.DirExists(t, filepath.Join(cfg.CannonDatadir, snapsDir))
...@@ -76,7 +76,7 @@ func TestGenerateProof(t *testing.T) { ...@@ -76,7 +76,7 @@ func TestGenerateProof(t *testing.T) {
require.Equal(t, input, args["--input"]) require.Equal(t, input, args["--input"])
require.Contains(t, args, "--meta") require.Contains(t, args, "--meta")
require.Equal(t, "", args["--meta"]) require.Equal(t, "", args["--meta"])
require.Equal(t, filepath.Join(cfg.CannonDatadir, "out.json"), args["--output"]) require.Equal(t, filepath.Join(cfg.CannonDatadir, finalState), args["--output"])
require.Equal(t, "=150000000", args["--proof-at"]) require.Equal(t, "=150000000", args["--proof-at"])
require.Equal(t, "=150000001", args["--stop-at"]) require.Equal(t, "=150000001", args["--stop-at"])
require.Equal(t, "%500", args["--snapshot-at"]) require.Equal(t, "%500", args["--snapshot-at"])
...@@ -105,7 +105,7 @@ func TestGenerateProof(t *testing.T) { ...@@ -105,7 +105,7 @@ func TestGenerateProof(t *testing.T) {
cfg.CannonNetwork = "" cfg.CannonNetwork = ""
cfg.CannonRollupConfigPath = "rollup.json" cfg.CannonRollupConfigPath = "rollup.json"
cfg.CannonL2GenesisPath = "genesis.json" cfg.CannonL2GenesisPath = "genesis.json"
_, _, args := captureExec(cfg, 150_000_000) _, _, args := captureExec(t, cfg, 150_000_000)
require.NotContains(t, args, "--network") require.NotContains(t, args, "--network")
require.Equal(t, cfg.CannonRollupConfigPath, args["--rollup.config"]) require.Equal(t, cfg.CannonRollupConfigPath, args["--rollup.config"])
require.Equal(t, cfg.CannonL2GenesisPath, args["--l2.genesis"]) require.Equal(t, cfg.CannonL2GenesisPath, args["--l2.genesis"])
...@@ -115,7 +115,7 @@ func TestGenerateProof(t *testing.T) { ...@@ -115,7 +115,7 @@ func TestGenerateProof(t *testing.T) {
cfg.CannonNetwork = "mainnet" cfg.CannonNetwork = "mainnet"
cfg.CannonRollupConfigPath = "rollup.json" cfg.CannonRollupConfigPath = "rollup.json"
cfg.CannonL2GenesisPath = "genesis.json" cfg.CannonL2GenesisPath = "genesis.json"
_, _, args := captureExec(cfg, math.MaxUint64) _, _, args := captureExec(t, cfg, math.MaxUint64)
// stop-at would need to be one more than the proof step which would overflow back to 0 // stop-at would need to be one more than the proof step which would overflow back to 0
// so expect that it will be omitted. We'll ultimately want cannon to execute until the program exits. // so expect that it will be omitted. We'll ultimately want cannon to execute until the program exits.
require.NotContains(t, args, "--stop-at") require.NotContains(t, args, "--stop-at")
......
...@@ -15,6 +15,7 @@ import ( ...@@ -15,6 +15,7 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
...@@ -38,9 +39,14 @@ type ProofGenerator interface { ...@@ -38,9 +39,14 @@ type ProofGenerator interface {
} }
type CannonTraceProvider struct { type CannonTraceProvider struct {
logger log.Logger
dir string dir string
prestate string prestate string
generator ProofGenerator generator ProofGenerator
// lastStep stores the last step in the actual trace if known. 0 indicates unknown.
// Cached as an optimisation to avoid repeatedly attempting to execute beyond the end of the trace.
lastStep uint64
} }
func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config, l1Client bind.ContractCaller) (*CannonTraceProvider, error) { func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config, l1Client bind.ContractCaller) (*CannonTraceProvider, error) {
...@@ -58,6 +64,7 @@ func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config ...@@ -58,6 +64,7 @@ func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config
return nil, fmt.Errorf("fetch local game inputs: %w", err) return nil, fmt.Errorf("fetch local game inputs: %w", err)
} }
return &CannonTraceProvider{ return &CannonTraceProvider{
logger: logger,
dir: cfg.CannonDatadir, dir: cfg.CannonDatadir,
prestate: cfg.CannonAbsolutePreState, prestate: cfg.CannonAbsolutePreState,
generator: NewExecutor(logger, cfg, l1Head), generator: NewExecutor(logger, cfg, l1Head),
...@@ -65,7 +72,7 @@ func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config ...@@ -65,7 +72,7 @@ func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config
} }
func (p *CannonTraceProvider) GetOracleData(ctx context.Context, i uint64) (*types.PreimageOracleData, error) { func (p *CannonTraceProvider) GetOracleData(ctx context.Context, i uint64) (*types.PreimageOracleData, error) {
proof, err := p.loadProof(ctx, i) proof, err := p.loadProofData(ctx, i)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -74,10 +81,14 @@ func (p *CannonTraceProvider) GetOracleData(ctx context.Context, i uint64) (*typ ...@@ -74,10 +81,14 @@ func (p *CannonTraceProvider) GetOracleData(ctx context.Context, i uint64) (*typ
} }
func (p *CannonTraceProvider) Get(ctx context.Context, i uint64) (common.Hash, error) { func (p *CannonTraceProvider) Get(ctx context.Context, i uint64) (common.Hash, error) {
proof, err := p.loadProof(ctx, i) proof, state, err := p.loadProof(ctx, i)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
if proof == nil && state != nil {
// Use the hash from the final state
return crypto.Keccak256Hash(state.EncodeWitness()), nil
}
value := common.BytesToHash(proof.ClaimValue) value := common.BytesToHash(proof.ClaimValue)
if value == (common.Hash{}) { if value == (common.Hash{}) {
...@@ -87,7 +98,7 @@ func (p *CannonTraceProvider) Get(ctx context.Context, i uint64) (common.Hash, e ...@@ -87,7 +98,7 @@ func (p *CannonTraceProvider) Get(ctx context.Context, i uint64) (common.Hash, e
} }
func (p *CannonTraceProvider) GetPreimage(ctx context.Context, i uint64) ([]byte, []byte, error) { func (p *CannonTraceProvider) GetPreimage(ctx context.Context, i uint64) ([]byte, []byte, error) {
proof, err := p.loadProof(ctx, i) proof, err := p.loadProofData(ctx, i)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
...@@ -103,38 +114,77 @@ func (p *CannonTraceProvider) GetPreimage(ctx context.Context, i uint64) ([]byte ...@@ -103,38 +114,77 @@ func (p *CannonTraceProvider) GetPreimage(ctx context.Context, i uint64) ([]byte
} }
func (p *CannonTraceProvider) AbsolutePreState(ctx context.Context) ([]byte, error) { func (p *CannonTraceProvider) AbsolutePreState(ctx context.Context) ([]byte, error) {
path := filepath.Join(p.dir, p.prestate) state, err := parseState(p.prestate)
file, err := os.Open(path)
if err != nil { if err != nil {
return []byte{}, fmt.Errorf("cannot open state file (%v): %w", path, err) return []byte{}, fmt.Errorf("cannot load absolute pre-state: %w", err)
} }
defer file.Close() return state.EncodeWitness(), nil
var state mipsevm.State }
err = json.NewDecoder(file).Decode(&state)
// loadProofData loads the proof data for the specified step.
// If the requested index is beyond the end of the actual trace, the proof data from the last step is returned.
// Cannon will be executed a second time if required to generate the full proof data.
func (p *CannonTraceProvider) loadProofData(ctx context.Context, i uint64) (*proofData, error) {
proof, state, err := p.loadProof(ctx, i)
if err != nil { if err != nil {
return []byte{}, fmt.Errorf("invalid mipsevm state (%v): %w", path, err) return nil, err
} else if proof == nil && state != nil {
p.logger.Info("Re-executing to generate proof for last step", "step", state.Step)
proof, _, err = p.loadProof(ctx, state.Step)
if err != nil {
return nil, err
} }
return state.EncodeWitness(), nil if proof == nil {
return nil, fmt.Errorf("proof at step %v was not generated", i)
}
return proof, nil
}
return proof, nil
} }
func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofData, error) { // loadProof will attempt to load or generate the proof data at the specified index
// If the requested index is beyond the end of the actual trace:
// - When the actual trace length is known, the proof data from the last step is returned with nil state
// - When the actual trace length is not yet know, the state from after the last step is returned with nil proofData
// and the actual trace length is cached for future runs
func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofData, *mipsevm.State, error) {
if p.lastStep != 0 && i > p.lastStep {
// If the requested index is after the last step in the actual trace, use the last step
i = p.lastStep
}
path := filepath.Join(p.dir, proofsDir, fmt.Sprintf("%d.json", i)) path := filepath.Join(p.dir, proofsDir, fmt.Sprintf("%d.json", i))
file, err := os.Open(path) file, err := os.Open(path)
if errors.Is(err, os.ErrNotExist) { if errors.Is(err, os.ErrNotExist) {
if err := p.generator.GenerateProof(ctx, p.dir, i); err != nil { if err := p.generator.GenerateProof(ctx, p.dir, i); err != nil {
return nil, fmt.Errorf("generate cannon trace with proof at %v: %w", i, err) return nil, nil, fmt.Errorf("generate cannon trace with proof at %v: %w", i, err)
} }
// Try opening the file again now and it should exist. // Try opening the file again now and it should exist.
file, err = os.Open(path) file, err = os.Open(path)
if errors.Is(err, os.ErrNotExist) {
// Expected proof wasn't generated, check if we reached the end of execution
state, err := parseState(filepath.Join(p.dir, finalState))
if err != nil {
return nil, nil, fmt.Errorf("cannot read final state: %w", err)
}
if state.Exited && state.Step < i {
p.logger.Warn("Requested proof was after the program exited", "proof", i, "last", state.Step)
// The final instruction has already been applied to this state, so the last step we can execute
// is one before its Step value.
p.lastStep = state.Step - 1
return nil, state, nil
} else {
return nil, nil, fmt.Errorf("expected proof not generated but final state was not exited, requested step %v, final state at step %v", i, state.Step)
}
}
} }
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot open proof file (%v): %w", path, err) return nil, nil, fmt.Errorf("cannot open proof file (%v): %w", path, err)
} }
defer file.Close() defer file.Close()
var proof proofData var proof proofData
err = json.NewDecoder(file).Decode(&proof) err = json.NewDecoder(file).Decode(&proof)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read proof (%v): %w", path, err) return nil, nil, fmt.Errorf("failed to read proof (%v): %w", path, err)
} }
return &proof, nil return &proof, nil, nil
} }
...@@ -4,12 +4,17 @@ import ( ...@@ -4,12 +4,17 @@ import (
"context" "context"
"embed" "embed"
_ "embed" _ "embed"
"encoding/json"
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -19,29 +24,35 @@ var testData embed.FS ...@@ -19,29 +24,35 @@ var testData embed.FS
func TestGet(t *testing.T) { func TestGet(t *testing.T) {
dataDir, prestate := setupTestData(t) dataDir, prestate := setupTestData(t)
t.Run("ExistingProof", func(t *testing.T) { t.Run("ExistingProof", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate) provider, generator := setupWithTestData(t, dataDir, prestate)
value, err := provider.Get(context.Background(), 0) value, err := provider.Get(context.Background(), 0)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, common.HexToHash("0x45fd9aa59768331c726e719e76aa343e73123af888804604785ae19506e65e87"), value) require.Equal(t, common.HexToHash("0x45fd9aa59768331c726e719e76aa343e73123af888804604785ae19506e65e87"), value)
require.Empty(t, generator.generated) require.Empty(t, generator.generated)
}) })
t.Run("ProofUnavailable", func(t *testing.T) { t.Run("ProofAfterEndOfTrace", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate) provider, generator := setupWithTestData(t, dataDir, prestate)
_, err := provider.Get(context.Background(), 7) generator.finalState = &mipsevm.State{
require.ErrorIs(t, err, os.ErrNotExist) Memory: &mipsevm.Memory{},
require.Contains(t, generator.generated, 7, "should have tried to generate the proof") Step: 10,
Exited: true,
}
value, err := provider.Get(context.Background(), 7000)
require.NoError(t, err)
require.Contains(t, generator.generated, 7000, "should have tried to generate the proof")
require.Equal(t, crypto.Keccak256Hash(generator.finalState.EncodeWitness()), value)
}) })
t.Run("MissingPostHash", func(t *testing.T) { t.Run("MissingPostHash", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate) provider, generator := setupWithTestData(t, dataDir, prestate)
_, err := provider.Get(context.Background(), 1) _, err := provider.Get(context.Background(), 1)
require.ErrorContains(t, err, "missing post hash") require.ErrorContains(t, err, "missing post hash")
require.Empty(t, generator.generated) require.Empty(t, generator.generated)
}) })
t.Run("IgnoreUnknownFields", func(t *testing.T) { t.Run("IgnoreUnknownFields", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate) provider, generator := setupWithTestData(t, dataDir, prestate)
value, err := provider.Get(context.Background(), 2) value, err := provider.Get(context.Background(), 2)
require.NoError(t, err) require.NoError(t, err)
expected := common.HexToHash("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb") expected := common.HexToHash("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
...@@ -53,7 +64,7 @@ func TestGet(t *testing.T) { ...@@ -53,7 +64,7 @@ func TestGet(t *testing.T) {
func TestGetOracleData(t *testing.T) { func TestGetOracleData(t *testing.T) {
dataDir, prestate := setupTestData(t) dataDir, prestate := setupTestData(t)
t.Run("ExistingProof", func(t *testing.T) { t.Run("ExistingProof", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate) provider, generator := setupWithTestData(t, dataDir, prestate)
oracleData, err := provider.GetOracleData(context.Background(), 420) oracleData, err := provider.GetOracleData(context.Background(), 420)
require.NoError(t, err) require.NoError(t, err)
require.False(t, oracleData.IsLocal) require.False(t, oracleData.IsLocal)
...@@ -64,15 +75,32 @@ func TestGetOracleData(t *testing.T) { ...@@ -64,15 +75,32 @@ func TestGetOracleData(t *testing.T) {
require.Empty(t, generator.generated) require.Empty(t, generator.generated)
}) })
t.Run("ProofUnavailable", func(t *testing.T) { t.Run("ProofAfterEndOfTrace", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate) provider, generator := setupWithTestData(t, dataDir, prestate)
_, err := provider.GetOracleData(context.Background(), 7) generator.finalState = &mipsevm.State{
require.ErrorIs(t, err, os.ErrNotExist) Memory: &mipsevm.Memory{},
require.Contains(t, generator.generated, 7, "should have tried to generate the proof") Step: 10,
Exited: true,
}
generator.proof = &proofData{
ClaimValue: common.Hash{0xaa}.Bytes(),
StateData: []byte{0xbb},
ProofData: []byte{0xcc},
OracleKey: common.Hash{0xdd}.Bytes(),
OracleValue: []byte{0xdd},
OracleOffset: 10,
}
oracleData, err := provider.GetOracleData(context.Background(), 7000)
require.NoError(t, err)
require.Contains(t, generator.generated, 7000, "should have tried to generate the proof")
require.Contains(t, generator.generated, 9, "should have regenerated proof from last step")
require.False(t, oracleData.IsLocal)
require.EqualValues(t, generator.proof.OracleKey, oracleData.OracleKey)
require.EqualValues(t, generator.proof.OracleValue, oracleData.OracleData)
}) })
t.Run("IgnoreUnknownFields", func(t *testing.T) { t.Run("IgnoreUnknownFields", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate) provider, generator := setupWithTestData(t, dataDir, prestate)
oracleData, err := provider.GetOracleData(context.Background(), 421) oracleData, err := provider.GetOracleData(context.Background(), 421)
require.NoError(t, err) require.NoError(t, err)
require.False(t, oracleData.IsLocal) require.False(t, oracleData.IsLocal)
...@@ -87,7 +115,7 @@ func TestGetOracleData(t *testing.T) { ...@@ -87,7 +115,7 @@ func TestGetOracleData(t *testing.T) {
func TestGetPreimage(t *testing.T) { func TestGetPreimage(t *testing.T) {
dataDir, prestate := setupTestData(t) dataDir, prestate := setupTestData(t)
t.Run("ExistingProof", func(t *testing.T) { t.Run("ExistingProof", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate) provider, generator := setupWithTestData(t, dataDir, prestate)
value, proof, err := provider.GetPreimage(context.Background(), 0) value, proof, err := provider.GetPreimage(context.Background(), 0)
require.NoError(t, err) require.NoError(t, err)
expected := common.Hex2Bytes("b8f068de604c85ea0e2acd437cdb47add074a2d70b81d018390c504b71fe26f400000000000000000000000000000000000000000000000000000000000000000000000000") expected := common.Hex2Bytes("b8f068de604c85ea0e2acd437cdb47add074a2d70b81d018390c504b71fe26f400000000000000000000000000000000000000000000000000000000000000000000000000")
...@@ -97,22 +125,38 @@ func TestGetPreimage(t *testing.T) { ...@@ -97,22 +125,38 @@ func TestGetPreimage(t *testing.T) {
require.Empty(t, generator.generated) require.Empty(t, generator.generated)
}) })
t.Run("ProofUnavailable", func(t *testing.T) { t.Run("ProofAfterEndOfTrace", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate) provider, generator := setupWithTestData(t, dataDir, prestate)
_, _, err := provider.GetPreimage(context.Background(), 7) generator.finalState = &mipsevm.State{
require.ErrorIs(t, err, os.ErrNotExist) Memory: &mipsevm.Memory{},
require.Contains(t, generator.generated, 7, "should have tried to generate the proof") Step: 10,
Exited: true,
}
generator.proof = &proofData{
ClaimValue: common.Hash{0xaa}.Bytes(),
StateData: []byte{0xbb},
ProofData: []byte{0xcc},
OracleKey: common.Hash{0xdd}.Bytes(),
OracleValue: []byte{0xdd},
OracleOffset: 10,
}
preimage, proof, err := provider.GetPreimage(context.Background(), 7000)
require.NoError(t, err)
require.Contains(t, generator.generated, 7000, "should have tried to generate the proof")
require.Contains(t, generator.generated, 9, "should have regenerated proof from last step")
require.EqualValues(t, generator.proof.StateData, preimage)
require.EqualValues(t, generator.proof.ProofData, proof)
}) })
t.Run("MissingStateData", func(t *testing.T) { t.Run("MissingStateData", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate) provider, generator := setupWithTestData(t, dataDir, prestate)
_, _, err := provider.GetPreimage(context.Background(), 1) _, _, err := provider.GetPreimage(context.Background(), 1)
require.ErrorContains(t, err, "missing state data") require.ErrorContains(t, err, "missing state data")
require.Empty(t, generator.generated) require.Empty(t, generator.generated)
}) })
t.Run("IgnoreUnknownFields", func(t *testing.T) { t.Run("IgnoreUnknownFields", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate) provider, generator := setupWithTestData(t, dataDir, prestate)
value, proof, err := provider.GetPreimage(context.Background(), 2) value, proof, err := provider.GetPreimage(context.Background(), 2)
require.NoError(t, err) require.NoError(t, err)
expected := common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc") expected := common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc")
...@@ -130,21 +174,21 @@ func TestAbsolutePreState(t *testing.T) { ...@@ -130,21 +174,21 @@ func TestAbsolutePreState(t *testing.T) {
prestate := "state.json" prestate := "state.json"
t.Run("StateUnavailable", func(t *testing.T) { t.Run("StateUnavailable", func(t *testing.T) {
provider, _ := setupWithTestData("/dir/does/not/exist", prestate) provider, _ := setupWithTestData(t, "/dir/does/not/exist", prestate)
_, err := provider.AbsolutePreState(context.Background()) _, err := provider.AbsolutePreState(context.Background())
require.ErrorIs(t, err, os.ErrNotExist) require.ErrorIs(t, err, os.ErrNotExist)
}) })
t.Run("InvalidStateFile", func(t *testing.T) { t.Run("InvalidStateFile", func(t *testing.T) {
setupPreState(t, dataDir, "invalid.json") setupPreState(t, dataDir, "invalid.json")
provider, _ := setupWithTestData(dataDir, prestate) provider, _ := setupWithTestData(t, dataDir, prestate)
_, err := provider.AbsolutePreState(context.Background()) _, err := provider.AbsolutePreState(context.Background())
require.ErrorContains(t, err, "invalid mipsevm state") require.ErrorContains(t, err, "invalid mipsevm state")
}) })
t.Run("ExpectedAbsolutePreState", func(t *testing.T) { t.Run("ExpectedAbsolutePreState", func(t *testing.T) {
setupPreState(t, dataDir, "state.json") setupPreState(t, dataDir, "state.json")
provider, _ := setupWithTestData(dataDir, prestate) provider, _ := setupWithTestData(t, dataDir, prestate)
preState, err := provider.AbsolutePreState(context.Background()) preState, err := provider.AbsolutePreState(context.Background())
require.NoError(t, err) require.NoError(t, err)
state := mipsevm.State{ state := mipsevm.State{
...@@ -190,20 +234,39 @@ func setupTestData(t *testing.T) (string, string) { ...@@ -190,20 +234,39 @@ func setupTestData(t *testing.T) (string, string) {
return dataDir, "state.json" return dataDir, "state.json"
} }
func setupWithTestData(dataDir string, prestate string) (*CannonTraceProvider, *stubGenerator) { func setupWithTestData(t *testing.T, dataDir string, prestate string) (*CannonTraceProvider, *stubGenerator) {
generator := &stubGenerator{} generator := &stubGenerator{}
return &CannonTraceProvider{ return &CannonTraceProvider{
logger: testlog.Logger(t, log.LvlInfo),
dir: dataDir, dir: dataDir,
generator: generator, generator: generator,
prestate: prestate, prestate: filepath.Join(dataDir, prestate),
}, generator }, generator
} }
type stubGenerator struct { type stubGenerator struct {
generated []int // Using int makes assertions easier generated []int // Using int makes assertions easier
finalState *mipsevm.State
proof *proofData
} }
func (e *stubGenerator) GenerateProof(ctx context.Context, dir string, i uint64) error { func (e *stubGenerator) GenerateProof(ctx context.Context, dir string, i uint64) error {
e.generated = append(e.generated, int(i)) e.generated = append(e.generated, int(i))
if e.finalState != nil && e.finalState.Step <= i {
// Requesting a trace index past the end of the trace
data, err := json.Marshal(e.finalState)
if err != nil {
return err
}
return os.WriteFile(filepath.Join(dir, finalState), data, 0644)
}
if e.proof != nil {
proofFile := filepath.Join(dir, proofsDir, fmt.Sprintf("%d.json", i))
data, err := json.Marshal(e.proof)
if err != nil {
return err
}
return os.WriteFile(proofFile, data, 0644)
}
return nil return nil
} }
...@@ -86,7 +86,10 @@ func NewOracleUpdaterWithOracle( ...@@ -86,7 +86,10 @@ func NewOracleUpdaterWithOracle(
} }
// UpdateOracle updates the oracle with the given data. // UpdateOracle updates the oracle with the given data.
func (u *cannonUpdater) UpdateOracle(ctx context.Context, data types.PreimageOracleData) error { func (u *cannonUpdater) UpdateOracle(ctx context.Context, data *types.PreimageOracleData) error {
if len(data.OracleKey) == 0 {
return nil
}
if data.IsLocal { if data.IsLocal {
return u.sendLocalOracleData(ctx, data) return u.sendLocalOracleData(ctx, data)
} }
...@@ -94,7 +97,7 @@ func (u *cannonUpdater) UpdateOracle(ctx context.Context, data types.PreimageOra ...@@ -94,7 +97,7 @@ func (u *cannonUpdater) UpdateOracle(ctx context.Context, data types.PreimageOra
} }
// sendLocalOracleData sends the local oracle data to the [txmgr]. // sendLocalOracleData sends the local oracle data to the [txmgr].
func (u *cannonUpdater) sendLocalOracleData(ctx context.Context, data types.PreimageOracleData) error { func (u *cannonUpdater) sendLocalOracleData(ctx context.Context, data *types.PreimageOracleData) error {
txData, err := u.BuildLocalOracleData(data) txData, err := u.BuildLocalOracleData(data)
if err != nil { if err != nil {
return fmt.Errorf("local oracle tx data build: %w", err) return fmt.Errorf("local oracle tx data build: %w", err)
...@@ -103,7 +106,7 @@ func (u *cannonUpdater) sendLocalOracleData(ctx context.Context, data types.Prei ...@@ -103,7 +106,7 @@ func (u *cannonUpdater) sendLocalOracleData(ctx context.Context, data types.Prei
} }
// sendGlobalOracleData sends the global oracle data to the [txmgr]. // sendGlobalOracleData sends the global oracle data to the [txmgr].
func (u *cannonUpdater) sendGlobalOracleData(ctx context.Context, data types.PreimageOracleData) error { func (u *cannonUpdater) sendGlobalOracleData(ctx context.Context, data *types.PreimageOracleData) error {
txData, err := u.BuildGlobalOracleData(data) txData, err := u.BuildGlobalOracleData(data)
if err != nil { if err != nil {
return fmt.Errorf("global oracle tx data build: %w", err) return fmt.Errorf("global oracle tx data build: %w", err)
...@@ -114,7 +117,7 @@ func (u *cannonUpdater) sendGlobalOracleData(ctx context.Context, data types.Pre ...@@ -114,7 +117,7 @@ func (u *cannonUpdater) sendGlobalOracleData(ctx context.Context, data types.Pre
// BuildLocalOracleData takes the local preimage key and data // BuildLocalOracleData takes the local preimage key and data
// and creates tx data to load the key, data pair into the // and creates tx data to load the key, data pair into the
// PreimageOracle contract from the FaultDisputeGame contract call. // PreimageOracle contract from the FaultDisputeGame contract call.
func (u *cannonUpdater) BuildLocalOracleData(data types.PreimageOracleData) ([]byte, error) { func (u *cannonUpdater) BuildLocalOracleData(data *types.PreimageOracleData) ([]byte, error) {
return u.fdgAbi.Pack( return u.fdgAbi.Pack(
"addLocalData", "addLocalData",
data.GetIdent(), data.GetIdent(),
...@@ -125,7 +128,7 @@ func (u *cannonUpdater) BuildLocalOracleData(data types.PreimageOracleData) ([]b ...@@ -125,7 +128,7 @@ func (u *cannonUpdater) BuildLocalOracleData(data types.PreimageOracleData) ([]b
// BuildGlobalOracleData takes the global preimage key and data // BuildGlobalOracleData takes the global preimage key and data
// and creates tx data to load the key, data pair into the // and creates tx data to load the key, data pair into the
// PreimageOracle contract. // PreimageOracle contract.
func (u *cannonUpdater) BuildGlobalOracleData(data types.PreimageOracleData) ([]byte, error) { func (u *cannonUpdater) BuildGlobalOracleData(data *types.PreimageOracleData) ([]byte, error) {
return u.preimageOracleAbi.Pack( return u.preimageOracleAbi.Pack(
"loadKeccak256PreimagePart", "loadKeccak256PreimagePart",
big.NewInt(int64(data.OracleOffset)), big.NewInt(int64(data.OracleOffset)),
......
...@@ -74,7 +74,8 @@ func newTestCannonUpdater(t *testing.T, sendFails bool) (*cannonUpdater, *mockTx ...@@ -74,7 +74,8 @@ func newTestCannonUpdater(t *testing.T, sendFails bool) (*cannonUpdater, *mockTx
func TestCannonUpdater_UpdateOracle(t *testing.T) { func TestCannonUpdater_UpdateOracle(t *testing.T) {
t.Run("succeeds", func(t *testing.T) { t.Run("succeeds", func(t *testing.T) {
updater, mockTxMgr := newTestCannonUpdater(t, false) updater, mockTxMgr := newTestCannonUpdater(t, false)
require.Nil(t, updater.UpdateOracle(context.Background(), types.PreimageOracleData{ require.NoError(t, updater.UpdateOracle(context.Background(), &types.PreimageOracleData{
OracleKey: common.Hash{0xaa}.Bytes(),
OracleData: common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"), OracleData: common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"),
})) }))
require.Equal(t, 1, mockTxMgr.sends) require.Equal(t, 1, mockTxMgr.sends)
...@@ -82,18 +83,25 @@ func TestCannonUpdater_UpdateOracle(t *testing.T) { ...@@ -82,18 +83,25 @@ func TestCannonUpdater_UpdateOracle(t *testing.T) {
t.Run("send fails", func(t *testing.T) { t.Run("send fails", func(t *testing.T) {
updater, mockTxMgr := newTestCannonUpdater(t, true) updater, mockTxMgr := newTestCannonUpdater(t, true)
require.Error(t, updater.UpdateOracle(context.Background(), types.PreimageOracleData{ require.Error(t, updater.UpdateOracle(context.Background(), &types.PreimageOracleData{
OracleKey: common.Hash{0xaa}.Bytes(),
OracleData: common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"), OracleData: common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"),
})) }))
require.Equal(t, 1, mockTxMgr.failedSends) require.Equal(t, 1, mockTxMgr.failedSends)
}) })
t.Run("skip empty data", func(t *testing.T) {
updater, mockTxMgr := newTestCannonUpdater(t, true)
require.NoError(t, updater.UpdateOracle(context.Background(), &types.PreimageOracleData{}))
require.Equal(t, 0, mockTxMgr.sends)
})
} }
// TestCannonUpdater_BuildLocalOracleData tests the [cannonUpdater] // TestCannonUpdater_BuildLocalOracleData tests the [cannonUpdater]
// builds a valid tx candidate for a local oracle update. // builds a valid tx candidate for a local oracle update.
func TestCannonUpdater_BuildLocalOracleData(t *testing.T) { func TestCannonUpdater_BuildLocalOracleData(t *testing.T) {
updater, _ := newTestCannonUpdater(t, false) updater, _ := newTestCannonUpdater(t, false)
oracleData := types.PreimageOracleData{ oracleData := &types.PreimageOracleData{
OracleKey: common.Hex2Bytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), OracleKey: common.Hex2Bytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
OracleData: common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"), OracleData: common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"),
OracleOffset: 7, OracleOffset: 7,
...@@ -117,7 +125,7 @@ func TestCannonUpdater_BuildLocalOracleData(t *testing.T) { ...@@ -117,7 +125,7 @@ func TestCannonUpdater_BuildLocalOracleData(t *testing.T) {
// builds a valid tx candidate for a global oracle update. // builds a valid tx candidate for a global oracle update.
func TestCannonUpdater_BuildGlobalOracleData(t *testing.T) { func TestCannonUpdater_BuildGlobalOracleData(t *testing.T) {
updater, _ := newTestCannonUpdater(t, false) updater, _ := newTestCannonUpdater(t, false)
oracleData := types.PreimageOracleData{ oracleData := &types.PreimageOracleData{
OracleKey: common.Hex2Bytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), OracleKey: common.Hex2Bytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
OracleData: common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"), OracleData: common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"),
OracleOffset: 7, OracleOffset: 7,
......
...@@ -19,11 +19,13 @@ type ClaimFetcher interface { ...@@ -19,11 +19,13 @@ type ClaimFetcher interface {
Clock *big.Int Clock *big.Int
}, error) }, error)
ClaimDataLen(opts *bind.CallOpts) (*big.Int, error) ClaimDataLen(opts *bind.CallOpts) (*big.Int, error)
MAXGAMEDEPTH(opts *bind.CallOpts) (*big.Int, error)
} }
// Loader is a minimal interface for loading onchain [Claim] data. // Loader is a minimal interface for loading onchain [Claim] data.
type Loader interface { type Loader interface {
FetchClaims(ctx context.Context) ([]types.Claim, error) FetchClaims(ctx context.Context) ([]types.Claim, error)
FetchGameDepth(ctx context.Context) (uint64, error)
} }
// loader pulls in fault dispute game claim data periodically and over subscriptions. // loader pulls in fault dispute game claim data periodically and over subscriptions.
...@@ -38,6 +40,20 @@ func NewLoader(claimFetcher ClaimFetcher) *loader { ...@@ -38,6 +40,20 @@ func NewLoader(claimFetcher ClaimFetcher) *loader {
} }
} }
// FetchGameDepth fetches the game depth from the fault dispute game.
func (l *loader) FetchGameDepth(ctx context.Context) (uint64, error) {
callOpts := bind.CallOpts{
Context: ctx,
}
gameDepth, err := l.claimFetcher.MAXGAMEDEPTH(&callOpts)
if err != nil {
return 0, err
}
return gameDepth.Uint64(), nil
}
// fetchClaim fetches a single [Claim] with a hydrated parent. // fetchClaim fetches a single [Claim] with a hydrated parent.
func (l *loader) fetchClaim(ctx context.Context, arrIndex uint64) (types.Claim, error) { func (l *loader) fetchClaim(ctx context.Context, arrIndex uint64) (types.Claim, error) {
callOpts := bind.CallOpts{ callOpts := bind.CallOpts{
......
...@@ -14,11 +14,14 @@ import ( ...@@ -14,11 +14,14 @@ import (
var ( var (
mockClaimDataError = fmt.Errorf("claim data errored") mockClaimDataError = fmt.Errorf("claim data errored")
mockClaimLenError = fmt.Errorf("claim len errored") mockClaimLenError = fmt.Errorf("claim len errored")
mockMaxGameDepthError = fmt.Errorf("max game depth errored")
) )
type mockClaimFetcher struct { type mockClaimFetcher struct {
claimDataError bool claimDataError bool
claimLenError bool claimLenError bool
maxGameDepthError bool
maxGameDepth uint64
currentIndex uint64 currentIndex uint64
returnClaims []struct { returnClaims []struct {
ParentIndex uint32 ParentIndex uint32
...@@ -88,6 +91,34 @@ func (m *mockClaimFetcher) ClaimDataLen(opts *bind.CallOpts) (*big.Int, error) { ...@@ -88,6 +91,34 @@ func (m *mockClaimFetcher) ClaimDataLen(opts *bind.CallOpts) (*big.Int, error) {
return big.NewInt(int64(len(m.returnClaims))), nil return big.NewInt(int64(len(m.returnClaims))), nil
} }
func (m *mockClaimFetcher) MAXGAMEDEPTH(opts *bind.CallOpts) (*big.Int, error) {
if m.maxGameDepthError {
return nil, mockMaxGameDepthError
}
return big.NewInt(int64(m.maxGameDepth)), nil
}
// TestLoader_FetchGameDepth tests [loader.FetchGameDepth].
func TestLoader_FetchGameDepth(t *testing.T) {
t.Run("Succeeds", func(t *testing.T) {
mockClaimFetcher := newMockClaimFetcher()
mockClaimFetcher.maxGameDepth = 10
loader := NewLoader(mockClaimFetcher)
depth, err := loader.FetchGameDepth(context.Background())
require.NoError(t, err)
require.Equal(t, uint64(10), depth)
})
t.Run("Errors", func(t *testing.T) {
mockClaimFetcher := newMockClaimFetcher()
mockClaimFetcher.maxGameDepthError = true
loader := NewLoader(mockClaimFetcher)
depth, err := loader.FetchGameDepth(context.Background())
require.ErrorIs(t, mockMaxGameDepthError, err)
require.Equal(t, depth, uint64(0))
})
}
// TestLoader_FetchClaims_Succeeds tests [loader.FetchClaims]. // TestLoader_FetchClaims_Succeeds tests [loader.FetchClaims].
func TestLoader_FetchClaims_Succeeds(t *testing.T) { func TestLoader_FetchClaims_Succeeds(t *testing.T) {
mockClaimFetcher := newMockClaimFetcher() mockClaimFetcher := newMockClaimFetcher()
......
...@@ -41,6 +41,19 @@ func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*se ...@@ -41,6 +41,19 @@ func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*se
return nil, fmt.Errorf("failed to dial L1: %w", err) return nil, fmt.Errorf("failed to dial L1: %w", err)
} }
contract, err := bindings.NewFaultDisputeGameCaller(cfg.GameAddress, client)
if err != nil {
return nil, fmt.Errorf("failed to bind the fault dispute game contract: %w", err)
}
loader := NewLoader(contract)
gameDepth, err := loader.FetchGameDepth(ctx)
if err != nil {
return nil, fmt.Errorf("failed to fetch the game depth: %w", err)
}
gameDepth = uint64(gameDepth)
var trace types.TraceProvider var trace types.TraceProvider
var updater types.OracleUpdater var updater types.OracleUpdater
switch cfg.TraceType { switch cfg.TraceType {
...@@ -54,23 +67,17 @@ func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*se ...@@ -54,23 +67,17 @@ func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*se
return nil, fmt.Errorf("failed to create the cannon updater: %w", err) return nil, fmt.Errorf("failed to create the cannon updater: %w", err)
} }
case config.TraceTypeAlphabet: case config.TraceTypeAlphabet:
trace = alphabet.NewTraceProvider(cfg.AlphabetTrace, uint64(cfg.GameDepth)) trace = alphabet.NewTraceProvider(cfg.AlphabetTrace, gameDepth)
updater = alphabet.NewOracleUpdater(logger) updater = alphabet.NewOracleUpdater(logger)
default: default:
return nil, fmt.Errorf("unsupported trace type: %v", cfg.TraceType) return nil, fmt.Errorf("unsupported trace type: %v", cfg.TraceType)
} }
return newTypedService(ctx, logger, cfg, client, trace, updater, txMgr) return newTypedService(ctx, logger, cfg, loader, gameDepth, client, trace, updater, txMgr)
} }
// newTypedService creates a new Service from a provided trace provider. // newTypedService creates a new Service from a provided trace provider.
func newTypedService(ctx context.Context, logger log.Logger, cfg *config.Config, client *ethclient.Client, provider types.TraceProvider, updater types.OracleUpdater, txMgr txmgr.TxManager) (*service, error) { func newTypedService(ctx context.Context, logger log.Logger, cfg *config.Config, loader Loader, gameDepth uint64, client *ethclient.Client, provider types.TraceProvider, updater types.OracleUpdater, txMgr txmgr.TxManager) (*service, error) {
contract, err := bindings.NewFaultDisputeGameCaller(cfg.GameAddress, client)
if err != nil {
return nil, fmt.Errorf("failed to bind the fault dispute game contract: %w", err)
}
loader := NewLoader(contract)
gameLogger := logger.New("game", cfg.GameAddress) gameLogger := logger.New("game", cfg.GameAddress)
responder, err := NewFaultResponder(gameLogger, txMgr, cfg.GameAddress) responder, err := NewFaultResponder(gameLogger, txMgr, cfg.GameAddress)
if err != nil { if err != nil {
...@@ -82,7 +89,7 @@ func newTypedService(ctx context.Context, logger log.Logger, cfg *config.Config, ...@@ -82,7 +89,7 @@ func newTypedService(ctx context.Context, logger log.Logger, cfg *config.Config,
return nil, fmt.Errorf("failed to bind the fault contract: %w", err) return nil, fmt.Errorf("failed to bind the fault contract: %w", err)
} }
agent := NewAgent(loader, cfg.GameDepth, provider, responder, updater, cfg.AgreeWithProposedOutput, gameLogger) agent := NewAgent(loader, int(gameDepth), provider, responder, updater, cfg.AgreeWithProposedOutput, gameLogger)
return &service{ return &service{
agent: agent, agent: agent,
......
...@@ -65,7 +65,7 @@ type StepCallData struct { ...@@ -65,7 +65,7 @@ type StepCallData struct {
// OracleUpdater is a generic interface for updating oracles. // OracleUpdater is a generic interface for updating oracles.
type OracleUpdater interface { type OracleUpdater interface {
// UpdateOracle updates the oracle with the given data. // UpdateOracle updates the oracle with the given data.
UpdateOracle(ctx context.Context, data PreimageOracleData) error UpdateOracle(ctx context.Context, data *PreimageOracleData) error
} }
// TraceProvider is a generic way to get a claim value at a specific step in the trace. // TraceProvider is a generic way to get a claim value at a specific step in the trace.
......
...@@ -48,11 +48,6 @@ var ( ...@@ -48,11 +48,6 @@ var (
Usage: "Temporary hardcoded flag if we agree or disagree with the proposed output.", Usage: "Temporary hardcoded flag if we agree or disagree with the proposed output.",
EnvVars: prefixEnvVars("AGREE_WITH_PROPOSED_OUTPUT"), EnvVars: prefixEnvVars("AGREE_WITH_PROPOSED_OUTPUT"),
} }
GameDepthFlag = &cli.IntFlag{
Name: "game-depth",
Usage: "Depth of the game tree.",
EnvVars: prefixEnvVars("GAME_DEPTH"),
}
// Optional Flags // Optional Flags
AlphabetFlag = &cli.StringFlag{ AlphabetFlag = &cli.StringFlag{
Name: "alphabet", Name: "alphabet",
...@@ -113,7 +108,6 @@ var requiredFlags = []cli.Flag{ ...@@ -113,7 +108,6 @@ var requiredFlags = []cli.Flag{
DGFAddressFlag, DGFAddressFlag,
TraceTypeFlag, TraceTypeFlag,
AgreeWithProposedOutputFlag, AgreeWithProposedOutputFlag,
GameDepthFlag,
} }
// optionalFlags is a list of unchecked cli flags // optionalFlags is a list of unchecked cli flags
...@@ -212,7 +206,6 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) { ...@@ -212,7 +206,6 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
CannonL2: ctx.String(CannonL2Flag.Name), CannonL2: ctx.String(CannonL2Flag.Name),
CannonSnapshotFreq: ctx.Uint(CannonSnapshotFreqFlag.Name), CannonSnapshotFreq: ctx.Uint(CannonSnapshotFreqFlag.Name),
AgreeWithProposedOutput: ctx.Bool(AgreeWithProposedOutputFlag.Name), AgreeWithProposedOutput: ctx.Bool(AgreeWithProposedOutputFlag.Name),
GameDepth: ctx.Int(GameDepthFlag.Name),
TxMgrConfig: txMgrConfig, TxMgrConfig: txMgrConfig,
}, nil }, nil
} }
...@@ -24,5 +24,4 @@ $CHALLENGER_DIR/bin/op-challenger \ ...@@ -24,5 +24,4 @@ $CHALLENGER_DIR/bin/op-challenger \
--game-address $FAULT_GAME_ADDRESS \ --game-address $FAULT_GAME_ADDRESS \
--private-key $CHARLIE_KEY \ --private-key $CHARLIE_KEY \
--num-confirmations 1 \ --num-confirmations 1 \
--game-depth 4 \
--agree-with-proposed-output=true --agree-with-proposed-output=true
...@@ -24,5 +24,4 @@ $CHALLENGER_DIR/bin/op-challenger \ ...@@ -24,5 +24,4 @@ $CHALLENGER_DIR/bin/op-challenger \
--game-address $FAULT_GAME_ADDRESS \ --game-address $FAULT_GAME_ADDRESS \
--private-key $MALLORY_KEY \ --private-key $MALLORY_KEY \
--num-confirmations 1 \ --num-confirmations 1 \
--game-depth 4 \
--agree-with-proposed-output=false --agree-with-proposed-output=false
devnet-allocs: test: pre-test
PYTHONPATH=../bedrock-devnet python3 ../bedrock-devnet/main.py --monorepo-dir=.. --allocs go test -v ./...
cannon-prestate:
make -C .. cannon-prestate
# We depend on the absolute pre-state generated by cannon to deploy the dispute game contracts.
devnet-allocs: pre-test-cannon
make -C .. devnet-allocs
pre-test: pre-test-cannon pre-test-allocs
pre-test:
$(shell ../ops/scripts/newer-file.sh ../.devnet/allocs-l1.json .././packages/contracts-bedrock) pre-test-cannon:
if [ $(.SHELLSTATUS) -ne 0 ]; then \ @if [ ! -e ../op-program/bin ]; then \
make cannon-prestate; \
fi
pre-test-allocs:
@if [ ! -e ../.devnet ]; then \
make devnet-allocs; \ make devnet-allocs; \
fi fi
test: pre-test clean:
go test -v ./... rm -r ../.devnet
rm -r ../op-program/bin
lint: lint:
golangci-lint run -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint -e "errors.As" -e "errors.Is" golangci-lint run -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint -e "errors.As" -e "errors.Is"
......
...@@ -16,7 +16,6 @@ func (g *AlphabetGameHelper) StartChallenger(ctx context.Context, l1Endpoint str ...@@ -16,7 +16,6 @@ func (g *AlphabetGameHelper) StartChallenger(ctx context.Context, l1Endpoint str
opts := []challenger.Option{ opts := []challenger.Option{
func(c *config.Config) { func(c *config.Config) {
c.GameAddress = g.addr c.GameAddress = g.addr
c.GameDepth = alphabetGameDepth
c.TraceType = config.TraceTypeAlphabet c.TraceType = config.TraceTypeAlphabet
// By default the challenger agrees with the root claim (thus disagrees with the proposed output) // By default the challenger agrees with the root claim (thus disagrees with the proposed output)
// This can be overridden by passing in options // This can be overridden by passing in options
......
...@@ -20,7 +20,6 @@ func (g *CannonGameHelper) StartChallenger(ctx context.Context, rollupCfg *rollu ...@@ -20,7 +20,6 @@ func (g *CannonGameHelper) StartChallenger(ctx context.Context, rollupCfg *rollu
opts := []challenger.Option{ opts := []challenger.Option{
func(c *config.Config) { func(c *config.Config) {
c.GameAddress = g.addr c.GameAddress = g.addr
c.GameDepth = cannonGameDepth
c.TraceType = config.TraceTypeCannon c.TraceType = config.TraceTypeCannon
c.AgreeWithProposedOutput = false c.AgreeWithProposedOutput = false
c.CannonL2 = l2Endpoint c.CannonL2 = l2Endpoint
...@@ -28,7 +27,7 @@ func (g *CannonGameHelper) StartChallenger(ctx context.Context, rollupCfg *rollu ...@@ -28,7 +27,7 @@ func (g *CannonGameHelper) StartChallenger(ctx context.Context, rollupCfg *rollu
c.CannonDatadir = g.t.TempDir() c.CannonDatadir = g.t.TempDir()
c.CannonServer = "../op-program/bin/op-program" c.CannonServer = "../op-program/bin/op-program"
c.CannonAbsolutePreState = "../op-program/bin/prestate.json" c.CannonAbsolutePreState = "../op-program/bin/prestate.json"
c.CannonSnapshotFreq = config.DefaultCannonSnapshotFreq c.CannonSnapshotFreq = 10_000_000
genesisBytes, err := json.Marshal(l2Genesis) genesisBytes, err := json.Marshal(l2Genesis)
g.require.NoError(err, "marshall l2 genesis config") g.require.NoError(err, "marshall l2 genesis config")
......
...@@ -22,7 +22,6 @@ type FaultGameHelper struct { ...@@ -22,7 +22,6 @@ type FaultGameHelper struct {
client *ethclient.Client client *ethclient.Client
opts *bind.TransactOpts opts *bind.TransactOpts
game *bindings.FaultDisputeGame game *bindings.FaultDisputeGame
maxDepth int
addr common.Address addr common.Address
} }
...@@ -33,7 +32,7 @@ func (g *FaultGameHelper) GameDuration(ctx context.Context) time.Duration { ...@@ -33,7 +32,7 @@ func (g *FaultGameHelper) GameDuration(ctx context.Context) time.Duration {
} }
func (g *FaultGameHelper) WaitForClaimCount(ctx context.Context, count int64) { func (g *FaultGameHelper) WaitForClaimCount(ctx context.Context, count int64) {
ctx, cancel := context.WithTimeout(ctx, time.Minute) ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel() defer cancel()
err := utils.WaitFor(ctx, time.Second, func() (bool, error) { err := utils.WaitFor(ctx, time.Second, func() (bool, error) {
actual, err := g.game.ClaimDataLen(&bind.CallOpts{Context: ctx}) actual, err := g.game.ClaimDataLen(&bind.CallOpts{Context: ctx})
...@@ -54,6 +53,12 @@ type ContractClaim struct { ...@@ -54,6 +53,12 @@ type ContractClaim struct {
Clock *big.Int Clock *big.Int
} }
func (g *FaultGameHelper) MaxDepth(ctx context.Context) int64 {
depth, err := g.game.MAXGAMEDEPTH(&bind.CallOpts{Context: ctx})
g.require.NoError(err, "Failed to load game depth")
return depth.Int64()
}
func (g *FaultGameHelper) WaitForClaim(ctx context.Context, predicate func(claim ContractClaim) bool) { func (g *FaultGameHelper) WaitForClaim(ctx context.Context, predicate func(claim ContractClaim) bool) {
ctx, cancel := context.WithTimeout(ctx, time.Minute) ctx, cancel := context.WithTimeout(ctx, time.Minute)
defer cancel() defer cancel()
...@@ -78,9 +83,10 @@ func (g *FaultGameHelper) WaitForClaim(ctx context.Context, predicate func(claim ...@@ -78,9 +83,10 @@ func (g *FaultGameHelper) WaitForClaim(ctx context.Context, predicate func(claim
} }
func (g *FaultGameHelper) WaitForClaimAtMaxDepth(ctx context.Context, countered bool) { func (g *FaultGameHelper) WaitForClaimAtMaxDepth(ctx context.Context, countered bool) {
maxDepth := g.MaxDepth(ctx)
g.WaitForClaim(ctx, func(claim ContractClaim) bool { g.WaitForClaim(ctx, func(claim ContractClaim) bool {
pos := types.NewPositionFromGIndex(claim.Position.Uint64()) pos := types.NewPositionFromGIndex(claim.Position.Uint64())
return pos.Depth() == g.maxDepth && claim.Countered == countered return int64(pos.Depth()) == maxDepth && claim.Countered == countered
}) })
} }
...@@ -109,3 +115,10 @@ func (g *FaultGameHelper) WaitForGameStatus(ctx context.Context, expected Status ...@@ -109,3 +115,10 @@ func (g *FaultGameHelper) WaitForGameStatus(ctx context.Context, expected Status
}) })
g.require.NoError(err, "wait for game status") g.require.NoError(err, "wait for game status")
} }
func (g *FaultGameHelper) Attack(ctx context.Context, claimIdx int64, claim common.Hash) {
tx, err := g.game.Attack(g.opts, big.NewInt(claimIdx), claim)
g.require.NoError(err, "Attack transaction did not send")
_, err = utils.WaitReceiptOK(ctx, g.client, tx.Hash())
g.require.NoError(err, "Attack transaction was not OK")
}
...@@ -22,7 +22,6 @@ import ( ...@@ -22,7 +22,6 @@ import (
const alphabetGameType uint8 = 0 const alphabetGameType uint8 = 0
const cannonGameType uint8 = 1 const cannonGameType uint8 = 1
const alphabetGameDepth = 4 const alphabetGameDepth = 4
const cannonGameDepth = 64
const lastAlphabetTraceIndex = 1<<alphabetGameDepth - 1 const lastAlphabetTraceIndex = 1<<alphabetGameDepth - 1
type Status uint8 type Status uint8
...@@ -114,7 +113,6 @@ func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet s ...@@ -114,7 +113,6 @@ func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet s
client: h.client, client: h.client,
opts: h.opts, opts: h.opts,
game: game, game: game,
maxDepth: alphabetGameDepth,
addr: createdEvent.DisputeProxy, addr: createdEvent.DisputeProxy,
}, },
claimedAlphabet: claimedAlphabet, claimedAlphabet: claimedAlphabet,
...@@ -148,7 +146,6 @@ func (h *FactoryHelper) StartCannonGame(ctx context.Context, rootClaim common.Ha ...@@ -148,7 +146,6 @@ func (h *FactoryHelper) StartCannonGame(ctx context.Context, rootClaim common.Ha
client: h.client, client: h.client,
opts: h.opts, opts: h.opts,
game: game, game: game,
maxDepth: cannonGameDepth,
addr: createdEvent.DisputeProxy, addr: createdEvent.DisputeProxy,
}, },
} }
......
...@@ -145,7 +145,6 @@ func TestChallengerCompleteDisputeGame(t *testing.T) { ...@@ -145,7 +145,6 @@ func TestChallengerCompleteDisputeGame(t *testing.T) {
} }
func TestCannonDisputeGame(t *testing.T) { func TestCannonDisputeGame(t *testing.T) {
t.Skip("CLI-4290: op-challenger doesn't handle trace extension correctly for cannon")
InitParallel(t) InitParallel(t)
ctx := context.Background() ctx := context.Background()
...@@ -161,8 +160,18 @@ func TestCannonDisputeGame(t *testing.T) { ...@@ -161,8 +160,18 @@ func TestCannonDisputeGame(t *testing.T) {
c.TxMgrConfig.PrivateKey = e2eutils.EncodePrivKeyToString(sys.cfg.Secrets.Alice) c.TxMgrConfig.PrivateKey = e2eutils.EncodePrivKeyToString(sys.cfg.Secrets.Alice)
}) })
// Challenger should counter the root claim maxDepth := game.MaxDepth(ctx)
game.WaitForClaimCount(ctx, 2) for claimCount := int64(1); claimCount < maxDepth; {
claimCount++
// Wait for the challenger to counter
game.WaitForClaimCount(ctx, claimCount)
// Post our own counter to the latest challenger claim
game.Attack(ctx, claimCount-1, common.Hash{byte(claimCount)})
claimCount++
game.WaitForClaimCount(ctx, claimCount)
}
game.WaitForClaimAtMaxDepth(ctx, false)
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx)) sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
require.NoError(t, utils.WaitNextBlock(ctx, l1Client)) require.NoError(t, utils.WaitNextBlock(ctx, l1Client))
......
...@@ -409,7 +409,7 @@ func TestMixedWithdrawalValidity(t *testing.T) { ...@@ -409,7 +409,7 @@ func TestMixedWithdrawalValidity(t *testing.T) {
l2Verif := sys.Clients["verifier"] l2Verif := sys.Clients["verifier"]
require.NoError(t, err) require.NoError(t, err)
systemConfig, err := bindings.NewSystemConfigCaller(cfg.L1Deployments.SystemConfig, l1Client) systemConfig, err := bindings.NewSystemConfigCaller(cfg.L1Deployments.SystemConfigProxy, l1Client)
require.NoError(t, err) require.NoError(t, err)
unsafeBlockSigner, err := systemConfig.UnsafeBlockSigner(nil) unsafeBlockSigner, err := systemConfig.UnsafeBlockSigner(nil)
require.NoError(t, err) require.NoError(t, err)
......
...@@ -102,22 +102,16 @@ func NewRPC(ctx context.Context, lgr log.Logger, addr string, opts ...RPCOption) ...@@ -102,22 +102,16 @@ func NewRPC(ctx context.Context, lgr log.Logger, addr string, opts ...RPCOption)
// Dials a JSON-RPC endpoint repeatedly, with a backoff, until a client connection is established. Auth is optional. // Dials a JSON-RPC endpoint repeatedly, with a backoff, until a client connection is established. Auth is optional.
func dialRPCClientWithBackoff(ctx context.Context, log log.Logger, addr string, attempts int, opts ...rpc.ClientOption) (*rpc.Client, error) { func dialRPCClientWithBackoff(ctx context.Context, log log.Logger, addr string, attempts int, opts ...rpc.ClientOption) (*rpc.Client, error) {
bOff := backoff.Exponential() bOff := backoff.Exponential()
var ret *rpc.Client return backoff.Do(ctx, attempts, bOff, func() (*rpc.Client, error) {
err := backoff.DoCtx(ctx, attempts, bOff, func() error {
client, err := rpc.DialOptions(ctx, addr, opts...) client, err := rpc.DialOptions(ctx, addr, opts...)
if err != nil { if err != nil {
if client == nil { if client == nil {
return fmt.Errorf("failed to dial address (%s): %w", addr, err) return nil, fmt.Errorf("failed to dial address (%s): %w", addr, err)
} }
log.Warn("failed to dial address, but may connect later", "addr", addr, "err", err) log.Warn("failed to dial address, but may connect later", "addr", addr, "err", err)
} }
ret = client return client, nil
return nil
}) })
if err != nil {
return nil, err
}
return ret, nil
} }
// BaseRPCClient is a wrapper around a concrete *rpc.Client instance to make it compliant // BaseRPCClient is a wrapper around a concrete *rpc.Client instance to make it compliant
......
...@@ -142,12 +142,12 @@ func (s *SyncClient) eventLoop() { ...@@ -142,12 +142,12 @@ func (s *SyncClient) eventLoop() {
s.log.Debug("Shutting down RPC sync worker") s.log.Debug("Shutting down RPC sync worker")
return return
case reqNum := <-s.requests: case reqNum := <-s.requests:
err := backoff.DoCtx(s.resCtx, 5, backoffStrategy, func() error { _, err := backoff.Do(s.resCtx, 5, backoffStrategy, func() (interface{}, error) {
// Limit the maximum time for fetching payloads // Limit the maximum time for fetching payloads
ctx, cancel := context.WithTimeout(s.resCtx, time.Second*10) ctx, cancel := context.WithTimeout(s.resCtx, time.Second*10)
defer cancel() defer cancel()
// We are only fetching one block at a time here. // We are only fetching one block at a time here.
return s.fetchUnsafeBlockFromRpc(ctx, reqNum) return nil, s.fetchUnsafeBlockFromRpc(ctx, reqNum)
}) })
if err != nil { if err != nil {
if err == s.resCtx.Err() { if err == s.resCtx.Err() {
......
...@@ -28,49 +28,33 @@ func NewRetryingL1Source(logger log.Logger, source L1Source) *RetryingL1Source { ...@@ -28,49 +28,33 @@ func NewRetryingL1Source(logger log.Logger, source L1Source) *RetryingL1Source {
} }
func (s *RetryingL1Source) InfoByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, error) { func (s *RetryingL1Source) InfoByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, error) {
var info eth.BlockInfo return backoff.Do(ctx, maxAttempts, s.strategy, func() (eth.BlockInfo, error) {
err := backoff.DoCtx(ctx, maxAttempts, s.strategy, func() error {
res, err := s.source.InfoByHash(ctx, blockHash) res, err := s.source.InfoByHash(ctx, blockHash)
if err != nil { if err != nil {
s.logger.Warn("Failed to retrieve info", "hash", blockHash, "err", err) s.logger.Warn("Failed to retrieve info", "hash", blockHash, "err", err)
return err
} }
info = res return res, err
return nil
}) })
return info, err
} }
func (s *RetryingL1Source) InfoAndTxsByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Transactions, error) { func (s *RetryingL1Source) InfoAndTxsByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Transactions, error) {
var info eth.BlockInfo return backoff.Do2(ctx, maxAttempts, s.strategy, func() (eth.BlockInfo, types.Transactions, error) {
var txs types.Transactions
err := backoff.DoCtx(ctx, maxAttempts, s.strategy, func() error {
i, t, err := s.source.InfoAndTxsByHash(ctx, blockHash) i, t, err := s.source.InfoAndTxsByHash(ctx, blockHash)
if err != nil { if err != nil {
s.logger.Warn("Failed to retrieve l1 info and txs", "hash", blockHash, "err", err) s.logger.Warn("Failed to retrieve l1 info and txs", "hash", blockHash, "err", err)
return err
} }
info = i return i, t, err
txs = t
return nil
}) })
return info, txs, err
} }
func (s *RetryingL1Source) FetchReceipts(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Receipts, error) { func (s *RetryingL1Source) FetchReceipts(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Receipts, error) {
var info eth.BlockInfo return backoff.Do2(ctx, maxAttempts, s.strategy, func() (eth.BlockInfo, types.Receipts, error) {
var rcpts types.Receipts
err := backoff.DoCtx(ctx, maxAttempts, s.strategy, func() error {
i, r, err := s.source.FetchReceipts(ctx, blockHash) i, r, err := s.source.FetchReceipts(ctx, blockHash)
if err != nil { if err != nil {
s.logger.Warn("Failed to fetch receipts", "hash", blockHash, "err", err) s.logger.Warn("Failed to fetch receipts", "hash", blockHash, "err", err)
return err
} }
info = i return i, r, err
rcpts = r
return nil
}) })
return info, rcpts, err
} }
var _ L1Source = (*RetryingL1Source)(nil) var _ L1Source = (*RetryingL1Source)(nil)
...@@ -82,61 +66,44 @@ type RetryingL2Source struct { ...@@ -82,61 +66,44 @@ type RetryingL2Source struct {
} }
func (s *RetryingL2Source) InfoAndTxsByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Transactions, error) { func (s *RetryingL2Source) InfoAndTxsByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Transactions, error) {
var info eth.BlockInfo return backoff.Do2(ctx, maxAttempts, s.strategy, func() (eth.BlockInfo, types.Transactions, error) {
var txs types.Transactions
err := backoff.DoCtx(ctx, maxAttempts, s.strategy, func() error {
i, t, err := s.source.InfoAndTxsByHash(ctx, blockHash) i, t, err := s.source.InfoAndTxsByHash(ctx, blockHash)
if err != nil { if err != nil {
s.logger.Warn("Failed to retrieve l2 info and txs", "hash", blockHash, "err", err) s.logger.Warn("Failed to retrieve l2 info and txs", "hash", blockHash, "err", err)
return err
} }
info = i return i, t, err
txs = t
return nil
}) })
return info, txs, err
} }
func (s *RetryingL2Source) NodeByHash(ctx context.Context, hash common.Hash) ([]byte, error) { func (s *RetryingL2Source) NodeByHash(ctx context.Context, hash common.Hash) ([]byte, error) {
var node []byte return backoff.Do(ctx, maxAttempts, s.strategy, func() ([]byte, error) {
err := backoff.DoCtx(ctx, maxAttempts, s.strategy, func() error {
n, err := s.source.NodeByHash(ctx, hash) n, err := s.source.NodeByHash(ctx, hash)
if err != nil { if err != nil {
s.logger.Warn("Failed to retrieve node", "hash", hash, "err", err) s.logger.Warn("Failed to retrieve node", "hash", hash, "err", err)
return err
} }
node = n return n, err
return nil
}) })
return node, err
} }
func (s *RetryingL2Source) CodeByHash(ctx context.Context, hash common.Hash) ([]byte, error) { func (s *RetryingL2Source) CodeByHash(ctx context.Context, hash common.Hash) ([]byte, error) {
var code []byte return backoff.Do(ctx, maxAttempts, s.strategy, func() ([]byte, error) {
err := backoff.DoCtx(ctx, maxAttempts, s.strategy, func() error {
c, err := s.source.CodeByHash(ctx, hash) c, err := s.source.CodeByHash(ctx, hash)
if err != nil { if err != nil {
s.logger.Warn("Failed to retrieve code", "hash", hash, "err", err) s.logger.Warn("Failed to retrieve code", "hash", hash, "err", err)
return err
} }
code = c return c, err
return nil
}) })
return code, err
} }
func (s *RetryingL2Source) OutputByRoot(ctx context.Context, root common.Hash) (eth.Output, error) { func (s *RetryingL2Source) OutputByRoot(ctx context.Context, root common.Hash) (eth.Output, error) {
var output eth.Output return backoff.Do(ctx, maxAttempts, s.strategy, func() (eth.Output, error) {
err := backoff.DoCtx(ctx, maxAttempts, s.strategy, func() error {
o, err := s.source.OutputByRoot(ctx, root) o, err := s.source.OutputByRoot(ctx, root)
if err != nil { if err != nil {
s.logger.Warn("Failed to fetch l2 output", "root", root, "err", err) s.logger.Warn("Failed to fetch l2 output", "root", root, "err", err)
return err return o, err
} }
output = o return o, nil
return nil
}) })
return output, err
} }
func NewRetryingL2Source(logger log.Logger, source L2Source) *RetryingL2Source { func NewRetryingL2Source(logger log.Logger, source L2Source) *RetryingL2Source {
......
...@@ -157,13 +157,12 @@ func NewL2OutputSubmitterConfigFromCLIConfig(cfg CLIConfig, l log.Logger, m metr ...@@ -157,13 +157,12 @@ func NewL2OutputSubmitterConfigFromCLIConfig(cfg CLIConfig, l log.Logger, m metr
} }
// Connect to L1 and L2 providers. Perform these last since they are the most expensive. // Connect to L1 and L2 providers. Perform these last since they are the most expensive.
ctx := context.Background() l1Client, err := opclient.DialEthClientWithTimeout(opclient.DefaultDialTimeout, l, cfg.L1EthRpc)
l1Client, err := opclient.DialEthClientWithTimeout(ctx, cfg.L1EthRpc, opclient.DefaultDialTimeout)
if err != nil { if err != nil {
return nil, err return nil, err
} }
rollupClient, err := opclient.DialRollupClientWithTimeout(ctx, cfg.RollupRpc, opclient.DefaultDialTimeout) rollupClient, err := opclient.DialRollupClientWithTimeout(opclient.DefaultDialTimeout, l, cfg.RollupRpc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
...@@ -6,10 +6,6 @@ import ( ...@@ -6,10 +6,6 @@ import (
"time" "time"
) )
// Operation represents an operation that will be retried
// based on some backoff strategy if it fails.
type Operation func() error
// ErrFailedPermanently is an error raised by Do when the // ErrFailedPermanently is an error raised by Do when the
// underlying Operation has been retried maxAttempts times. // underlying Operation has been retried maxAttempts times.
type ErrFailedPermanently struct { type ErrFailedPermanently struct {
...@@ -21,16 +17,27 @@ func (e *ErrFailedPermanently) Error() string { ...@@ -21,16 +17,27 @@ func (e *ErrFailedPermanently) Error() string {
return fmt.Sprintf("operation failed permanently after %d attempts: %v", e.attempts, e.LastErr) return fmt.Sprintf("operation failed permanently after %d attempts: %v", e.attempts, e.LastErr)
} }
type pair[T, U any] struct {
a T
b U
}
func Do2[T, U any](ctx context.Context, maxAttempts int, strategy Strategy, op func() (T, U, error)) (T, U, error) {
f := func() (pair[T, U], error) {
a, b, err := op()
return pair[T, U]{a, b}, err
}
res, err := Do(ctx, maxAttempts, strategy, f)
return res.a, res.b, err
}
// Do performs the provided Operation up to maxAttempts times // Do performs the provided Operation up to maxAttempts times
// with delays in between each retry according to the provided // with delays in between each retry according to the provided
// Strategy. // Strategy.
func Do(maxAttempts int, strategy Strategy, op Operation) error { func Do[T any](ctx context.Context, maxAttempts int, strategy Strategy, op func() (T, error)) (T, error) {
return DoCtx(context.Background(), maxAttempts, strategy, op) var empty T
}
func DoCtx(ctx context.Context, maxAttempts int, strategy Strategy, op Operation) error {
if maxAttempts < 1 { if maxAttempts < 1 {
return fmt.Errorf("need at least 1 attempt to run op, but have %d max attempts", maxAttempts) return empty, fmt.Errorf("need at least 1 attempt to run op, but have %d max attempts", maxAttempts)
} }
var attempt int var attempt int
...@@ -43,16 +50,16 @@ func DoCtx(ctx context.Context, maxAttempts int, strategy Strategy, op Operation ...@@ -43,16 +50,16 @@ func DoCtx(ctx context.Context, maxAttempts int, strategy Strategy, op Operation
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return ctx.Err() return empty, ctx.Err()
case <-reattemptCh: case <-reattemptCh:
attempt++ attempt++
err := op() ret, err := op()
if err == nil { if err == nil {
return nil return ret, nil
} }
if attempt == maxAttempts { if attempt == maxAttempts {
return &ErrFailedPermanently{ return empty, &ErrFailedPermanently{
attempts: maxAttempts, attempts: maxAttempts,
LastErr: err, LastErr: err,
} }
......
package backoff package backoff
import ( import (
"context"
"errors" "errors"
"testing" "testing"
"time" "time"
...@@ -14,20 +15,19 @@ func TestDo(t *testing.T) { ...@@ -14,20 +15,19 @@ func TestDo(t *testing.T) {
start := time.Now() start := time.Now()
var i int var i int
require.NoError(t, Do(2, strategy, func() error { _, err := Do(context.Background(), 2, strategy, func() (int, error) {
if i == 1 { if i == 1 {
return nil return 0, nil
} }
i++ i++
return dummyErr return 0, dummyErr
})) })
require.NoError(t, err)
require.True(t, time.Since(start) > 10*time.Millisecond) require.True(t, time.Since(start) > 10*time.Millisecond)
start = time.Now() start = time.Now()
// add one because the first attempt counts // add one because the first attempt counts
err := Do(3, strategy, func() error { _, err = Do(context.Background(), 3, strategy, func() (int, error) {
return dummyErr return 0, dummyErr
}) })
require.Equal(t, dummyErr, err.(*ErrFailedPermanently).LastErr) require.Equal(t, dummyErr, err.(*ErrFailedPermanently).LastErr)
require.True(t, time.Since(start) > 20*time.Millisecond) require.True(t, time.Since(start) > 20*time.Millisecond)
......
package client
import (
"context"
"fmt"
"net"
"net/url"
"time"
"github.com/ethereum-optimism/optimism/op-node/client"
"github.com/ethereum-optimism/optimism/op-node/sources"
"github.com/ethereum-optimism/optimism/op-service/backoff"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
)
// DefaultDialTimeout is a default timeout for dialing a client.
const DefaultDialTimeout = 1 * time.Minute
const defaultRetryCount = 30
const defaultRetryTime = 2 * time.Second
// DialEthClientWithTimeout attempts to dial the L1 provider using the provided
// URL. If the dial doesn't complete within defaultDialTimeout seconds, this
// method will return an error.
func DialEthClientWithTimeout(timeout time.Duration, log log.Logger, url string) (*ethclient.Client, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
c, err := dialRPCClientWithBackoff(ctx, log, url)
if err != nil {
return nil, err
}
return ethclient.NewClient(c), nil
}
// DialRollupClientWithTimeout attempts to dial the RPC provider using the provided URL.
// If the dial doesn't complete within timeout seconds, this method will return an error.
func DialRollupClientWithTimeout(timeout time.Duration, log log.Logger, url string) (*sources.RollupClient, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
rpcCl, err := dialRPCClientWithBackoff(ctx, log, url)
if err != nil {
return nil, err
}
return sources.NewRollupClient(client.NewBaseRPCClient(rpcCl)), nil
}
// Dials a JSON-RPC endpoint repeatedly, with a backoff, until a client connection is established. Auth is optional.
func dialRPCClientWithBackoff(ctx context.Context, log log.Logger, addr string) (*rpc.Client, error) {
bOff := backoff.Fixed(defaultRetryTime)
return backoff.Do(ctx, defaultRetryCount, bOff, func() (*rpc.Client, error) {
if !IsURLAvailable(addr) {
log.Warn("failed to dial address, but may connect later", "addr", addr)
return nil, fmt.Errorf("address unavailable (%s)", addr)
}
client, err := rpc.DialOptions(ctx, addr)
if err != nil {
return nil, fmt.Errorf("failed to dial address (%s): %w", addr, err)
}
return client, nil
})
}
func IsURLAvailable(address string) bool {
u, err := url.Parse(address)
if err != nil {
return false
}
conn, err := net.DialTimeout("tcp", u.Host, 5*time.Second)
if err != nil {
return false
}
conn.Close()
return true
}
package client
import (
"fmt"
"net"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
func TestIsURLAvailable(t *testing.T) {
listener, err := net.Listen("tcp4", ":0")
require.NoError(t, err)
defer listener.Close()
a := listener.Addr().String()
parts := strings.Split(a, ":")
addr := fmt.Sprintf("http://localhost:%s", parts[1])
require.True(t, IsURLAvailable(addr))
require.False(t, IsURLAvailable("http://localhost:0"))
}
package client
import (
"context"
"time"
"github.com/ethereum/go-ethereum/ethclient"
)
// DialEthClientWithTimeout attempts to dial the L1 provider using the provided
// URL. If the dial doesn't complete within defaultDialTimeout seconds, this
// method will return an error.
func DialEthClientWithTimeout(ctx context.Context, url string, timeout time.Duration) (*ethclient.Client, error) {
ctxt, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
return ethclient.DialContext(ctxt, url)
}
package client
import (
"context"
"time"
"github.com/ethereum-optimism/optimism/op-node/client"
"github.com/ethereum-optimism/optimism/op-node/sources"
"github.com/ethereum/go-ethereum/rpc"
)
// DialRollupClientWithTimeout attempts to dial the RPC provider using the provided
// URL. If the dial doesn't complete within defaultDialTimeout seconds, this
// method will return an error.
func DialRollupClientWithTimeout(ctx context.Context, url string, timeout time.Duration) (*sources.RollupClient, error) {
ctxt, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
rpcCl, err := rpc.DialContext(ctxt, url)
if err != nil {
return nil, err
}
return sources.NewRollupClient(client.NewBaseRPCClient(rpcCl)), nil
}
package client
import (
"time"
)
// DefaultDialTimeout is a default timeout for dialing a client.
const DefaultDialTimeout = 5 * time.Second
...@@ -11,10 +11,11 @@ RUN apt-get update && \ ...@@ -11,10 +11,11 @@ RUN apt-get update && \
chmod +x ./rustup.sh && \ chmod +x ./rustup.sh && \
./rustup.sh -y ./rustup.sh -y
COPY ./.foundryrc ./.foundryrc COPY ./.abigenrc ./.abigenrc
# 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
RUN git clone https://github.com/foundry-rs/foundry.git ./foundry \ RUN git clone https://github.com/foundry-rs/foundry.git ./foundry \
&& cd foundry && git checkout $(cat ../.foundryrc) && cd foundry && git checkout $(cat ../.foundryrc)
...@@ -39,7 +40,6 @@ ENV DEBIAN_FRONTEND=noninteractive ...@@ -39,7 +40,6 @@ ENV DEBIAN_FRONTEND=noninteractive
COPY --from=foundry-build /opt/foundry/target/release/forge /usr/local/bin/forge COPY --from=foundry-build /opt/foundry/target/release/forge /usr/local/bin/forge
COPY --from=foundry-build /opt/foundry/target/release/cast /usr/local/bin/cast COPY --from=foundry-build /opt/foundry/target/release/cast /usr/local/bin/cast
COPY --from=foundry-build /opt/foundry/target/release/anvil /usr/local/bin/anvil COPY --from=foundry-build /opt/foundry/target/release/anvil /usr/local/bin/anvil
COPY --from=geth /usr/local/bin/abigen /usr/local/bin/abigen
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
RUN apt-get update && \ RUN apt-get update && \
...@@ -56,6 +56,9 @@ RUN apt-get update && \ ...@@ -56,6 +56,9 @@ RUN apt-get update && \
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.53.3 && \ curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.53.3 && \
curl -fLSs https://raw.githubusercontent.com/CircleCI-Public/circleci-cli/master/install.sh | bash curl -fLSs https://raw.githubusercontent.com/CircleCI-Public/circleci-cli/master/install.sh | bash
# Install the specific version of abigen from .abigenrc
RUN go install github.com/ethereum/go-ethereum/cmd/abigen@$(cat .abigenrc)
# We need to isntall yarn because a bespoke dependency installed from github https://codeload.github.com/Saw-mon-and-Natalie/clones-with-immutable-arg needs it # We need to isntall yarn because a bespoke dependency installed from github https://codeload.github.com/Saw-mon-and-Natalie/clones-with-immutable-arg needs it
# it is installed from github which means it's postpack script which uses yarn is ran when unpacked into node_modules # it is installed from github which means it's postpack script which uses yarn is ran when unpacked into node_modules
RUN echo "downloading pnpm and yarn" && npm i -g pnpm && npm i -g yarn@1 && pnpm --version && yarn --version RUN echo "downloading pnpm and yarn" && npm i -g pnpm && npm i -g yarn@1 && pnpm --version && yarn --version
......
...@@ -6,9 +6,17 @@ ...@@ -6,9 +6,17 @@
if [[ ! -e "$1" ]]; then exit 1; fi if [[ ! -e "$1" ]]; then exit 1; fi
if [[ ! -e "$2" ]]; then exit 1; fi if [[ ! -e "$2" ]]; then exit 1; fi
FILE_1_AGE=$(date +%s%N --reference "$1") if uname | grep -q "Darwin"; then
FILE_2_AGE=$(date +%s%N --reference "$2") MOD_TIME_FMT="-f %m"
if (("$FILE_1_AGE" > "$FILE_2_AGE")); then else
MOD_TIME_FMT="-c %Y"
fi
FILE_1_AGE=$(stat $MOD_TIME_FMT "$1")
FILE_2_AGE=$(stat $MOD_TIME_FMT "$2")
if [ "$FILE_1_AGE" -gt "$FILE_2_AGE" ]; then
exit 0 exit 0
fi fi
exit 1 exit 1
\ No newline at end of file
This diff is collapsed.
...@@ -89,6 +89,7 @@ ...@@ -89,6 +89,7 @@
| batcherHash | bytes32 | 103 | 0 | 32 | src/L1/SystemConfig.sol:SystemConfig | | batcherHash | bytes32 | 103 | 0 | 32 | src/L1/SystemConfig.sol:SystemConfig |
| gasLimit | uint64 | 104 | 0 | 8 | src/L1/SystemConfig.sol:SystemConfig | | gasLimit | uint64 | 104 | 0 | 8 | src/L1/SystemConfig.sol:SystemConfig |
| _resourceConfig | struct ResourceMetering.ResourceConfig | 105 | 0 | 32 | src/L1/SystemConfig.sol:SystemConfig | | _resourceConfig | struct ResourceMetering.ResourceConfig | 105 | 0 | 32 | src/L1/SystemConfig.sol:SystemConfig |
| startBlock | uint256 | 106 | 0 | 32 | src/L1/SystemConfig.sol:SystemConfig |
======================= =======================
➡ src/legacy/DeployerWhitelist.sol:DeployerWhitelist ➡ src/legacy/DeployerWhitelist.sol:DeployerWhitelist
......
...@@ -44,6 +44,7 @@ ...@@ -44,6 +44,7 @@
"l1GenesisBlockTimestamp": "0x64c811bf", "l1GenesisBlockTimestamp": "0x64c811bf",
"l2GenesisRegolithTimeOffset": "0x0", "l2GenesisRegolithTimeOffset": "0x0",
"faultGameAbsolutePrestate": "0x41c7ae758795765c6664a5d39bf63841c71ff191e9189522bad8ebff5d4eca98", "faultGameAbsolutePrestate": "0x41c7ae758795765c6664a5d39bf63841c71ff191e9189522bad8ebff5d4eca98",
"faultGameMaxDepth": 4, "faultGameMaxDepth": 31,
"faultGameMaxDuration": 300 "faultGameMaxDuration": 300,
"systemConfigStartBlock": 0
} }
# `SystemConfig` Invariants # `SystemConfig` Invariants
## The gas limit of the `SystemConfig` contract can never be lower than the hard-coded lower bound. ## The gas limit of the `SystemConfig` contract can never be lower than the hard-coded lower bound.
**Test:** [`SystemConfig.t.sol#L44`](../test/invariants/SystemConfig.t.sol#L44) **Test:** [`SystemConfig.t.sol#L65`](../test/invariants/SystemConfig.t.sol#L65)
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
], ],
"scripts": { "scripts": {
"bindings": "pnpm bindings:ts && pnpm bindings:go", "bindings": "pnpm bindings:ts && pnpm bindings:go",
"bindings:ts": "pnpm generate:addresses && nx generate @eth-optimism/contracts-ts", "bindings:ts": "nx generate @eth-optimism/contracts-ts",
"bindings:go": "cd ../../op-bindings && make", "bindings:go": "cd ../../op-bindings && make",
"build": "forge build", "build": "forge build",
"prebuild": "./scripts/verify-foundry-install.sh", "prebuild": "./scripts/verify-foundry-install.sh",
...@@ -44,6 +44,6 @@ ...@@ -44,6 +44,6 @@
"solhint": "^3.4.1", "solhint": "^3.4.1",
"solhint-plugin-prettier": "^0.0.5", "solhint-plugin-prettier": "^0.0.5",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^4.9.3" "typescript": "^5.1.6"
} }
} }
...@@ -385,32 +385,30 @@ contract Deploy is Deployer { ...@@ -385,32 +385,30 @@ contract Deploy is Deployer {
/// @notice Deploy the SystemConfig /// @notice Deploy the SystemConfig
function deploySystemConfig() broadcast() public returns (address) { function deploySystemConfig() broadcast() public returns (address) {
bytes32 batcherHash = bytes32(uint256(uint160(cfg.batchSenderAddress()))); SystemConfig config = new SystemConfig();
SystemConfig config = new SystemConfig({
_owner: cfg.finalSystemOwner(),
_overhead: cfg.gasPriceOracleOverhead(),
_scalar: cfg.gasPriceOracleScalar(),
_batcherHash: batcherHash,
_gasLimit: uint64(cfg.l2GenesisBlockGasLimit()),
_unsafeBlockSigner: cfg.p2pSequencerAddress(),
_config: Constants.DEFAULT_RESOURCE_CONFIG()
});
require(config.owner() == cfg.finalSystemOwner()); require(config.owner() == address(0xdEaD));
require(config.overhead() == cfg.gasPriceOracleOverhead()); require(config.overhead() == 0);
require(config.scalar() == cfg.gasPriceOracleScalar()); require(config.scalar() == 0);
require(config.unsafeBlockSigner() == cfg.p2pSequencerAddress()); require(config.unsafeBlockSigner() == address(0));
require(config.batcherHash() == batcherHash); require(config.batcherHash() == bytes32(0));
require(config.gasLimit() == 1);
ResourceMetering.ResourceConfig memory rconfig = Constants.DEFAULT_RESOURCE_CONFIG();
ResourceMetering.ResourceConfig memory resourceConfig = config.resourceConfig(); ResourceMetering.ResourceConfig memory resourceConfig = config.resourceConfig();
require(resourceConfig.maxResourceLimit == rconfig.maxResourceLimit); require(resourceConfig.maxResourceLimit == 1);
require(resourceConfig.elasticityMultiplier == rconfig.elasticityMultiplier); require(resourceConfig.elasticityMultiplier == 1);
require(resourceConfig.baseFeeMaxChangeDenominator == rconfig.baseFeeMaxChangeDenominator); require(resourceConfig.baseFeeMaxChangeDenominator == 2);
require(resourceConfig.systemTxMaxGas == rconfig.systemTxMaxGas); require(resourceConfig.systemTxMaxGas == 0);
require(resourceConfig.minimumBaseFee == rconfig.minimumBaseFee); require(resourceConfig.minimumBaseFee == 0);
require(resourceConfig.maximumBaseFee == rconfig.maximumBaseFee); require(resourceConfig.maximumBaseFee == 0);
require(config.l1ERC721Bridge() == address(0));
require(config.l1StandardBridge() == address(0));
require(config.l2OutputOracle() == address(0));
require(config.optimismPortal() == address(0));
require(config.l1CrossDomainMessenger() == address(0));
require(config.optimismMintableERC20Factory() == address(0));
require(config.startBlock() == 0);
save("SystemConfig", address(config)); save("SystemConfig", address(config));
console.log("SystemConfig deployed at %s", address(config)); console.log("SystemConfig deployed at %s", address(config));
...@@ -498,7 +496,17 @@ contract Deploy is Deployer { ...@@ -498,7 +496,17 @@ contract Deploy is Deployer {
batcherHash, batcherHash,
uint64(cfg.l2GenesisBlockGasLimit()), uint64(cfg.l2GenesisBlockGasLimit()),
cfg.p2pSequencerAddress(), cfg.p2pSequencerAddress(),
Constants.DEFAULT_RESOURCE_CONFIG() Constants.DEFAULT_RESOURCE_CONFIG(),
cfg.systemConfigStartBlock(),
cfg.batchInboxAddress(),
SystemConfig.Addresses({
l1CrossDomainMessenger: mustGetAddress("L1CrossDomainMessengerProxy"),
l1ERC721Bridge: mustGetAddress("L1ERC721BridgeProxy"),
l1StandardBridge: mustGetAddress("L1StandardBridgeProxy"),
l2OutputOracle: mustGetAddress("L2OutputOracleProxy"),
optimismPortal: mustGetAddress("OptimismPortalProxy"),
optimismMintableERC20Factory: mustGetAddress("OptimismMintableERC20FactoryProxy")
})
) )
) )
}); });
...@@ -522,6 +530,12 @@ contract Deploy is Deployer { ...@@ -522,6 +530,12 @@ contract Deploy is Deployer {
require(resourceConfig.minimumBaseFee == rconfig.minimumBaseFee); require(resourceConfig.minimumBaseFee == rconfig.minimumBaseFee);
require(resourceConfig.maximumBaseFee == rconfig.maximumBaseFee); require(resourceConfig.maximumBaseFee == rconfig.maximumBaseFee);
require(config.l1ERC721Bridge() == mustGetAddress("L1ERC721BridgeProxy"));
require(config.l1StandardBridge() == mustGetAddress("L1StandardBridgeProxy"));
require(config.l2OutputOracle() == mustGetAddress("L2OutputOracleProxy"));
require(config.optimismPortal() == mustGetAddress("OptimismPortalProxy"));
require(config.l1CrossDomainMessenger() == mustGetAddress("L1CrossDomainMessengerProxy"));
require(config.startBlock() == cfg.systemConfigStartBlock());
} }
/// @notice Initialize the L1StandardBridge /// @notice Initialize the L1StandardBridge
......
...@@ -47,6 +47,7 @@ contract DeployConfig is Script { ...@@ -47,6 +47,7 @@ contract DeployConfig is Script {
uint256 public faultGameAbsolutePrestate; uint256 public faultGameAbsolutePrestate;
uint256 public faultGameMaxDepth; uint256 public faultGameMaxDepth;
uint256 public faultGameMaxDuration; uint256 public faultGameMaxDuration;
uint256 public systemConfigStartBlock;
constructor(string memory _path) { constructor(string memory _path) {
console.log("DeployConfig: reading file %s", _path); console.log("DeployConfig: reading file %s", _path);
...@@ -87,6 +88,7 @@ contract DeployConfig is Script { ...@@ -87,6 +88,7 @@ contract DeployConfig is Script {
gasPriceOracleScalar = stdJson.readUint(_json, "$.gasPriceOracleScalar"); gasPriceOracleScalar = stdJson.readUint(_json, "$.gasPriceOracleScalar");
eip1559Denominator = stdJson.readUint(_json, "$.eip1559Denominator"); eip1559Denominator = stdJson.readUint(_json, "$.eip1559Denominator");
eip1559Elasticity = stdJson.readUint(_json, "$.eip1559Elasticity"); eip1559Elasticity = stdJson.readUint(_json, "$.eip1559Elasticity");
systemConfigStartBlock = stdJson.readUint(_json, "$.systemConfigStartBlock");
if (block.chainid == Chains.LocalDevnet || block.chainid == Chains.GethDevnet) { if (block.chainid == Chains.LocalDevnet || block.chainid == Chains.GethDevnet) {
faultGameAbsolutePrestate = stdJson.readUint(_json, "$.faultGameAbsolutePrestate"); faultGameAbsolutePrestate = stdJson.readUint(_json, "$.faultGameAbsolutePrestate");
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"src/L1/L1StandardBridge.sol": "0xa35dc0ab143043063c3bff73c8b065e401c23296a2017258ce8a87f4a4da9416", "src/L1/L1StandardBridge.sol": "0xa35dc0ab143043063c3bff73c8b065e401c23296a2017258ce8a87f4a4da9416",
"src/L1/L2OutputOracle.sol": "0x8f32ccb4c5cb63a459a0e79ee412177dc03fd279fdaaf1dac69e8c714902e857", "src/L1/L2OutputOracle.sol": "0x8f32ccb4c5cb63a459a0e79ee412177dc03fd279fdaaf1dac69e8c714902e857",
"src/L1/OptimismPortal.sol": "0xeaa47a63e8a3bcfdb7dfd3e6c8608369e34e362d9de82f3acf13cbc27c070bf7", "src/L1/OptimismPortal.sol": "0xeaa47a63e8a3bcfdb7dfd3e6c8608369e34e362d9de82f3acf13cbc27c070bf7",
"src/L1/SystemConfig.sol": "0x8e2b5103d2eb93b74af2e2f96a4505e637cdc3c44d80cf5ec2eca70060e1deff", "src/L1/SystemConfig.sol": "0x689a3339e9167e62fac105eaae0390b39fc7dfee21b19c5f78636d98ee97148e",
"src/L2/BaseFeeVault.sol": "0xa596e60762f16192cfa86459fcb9f4da9d8f756106eedac643a1ffeafbbfcc5f", "src/L2/BaseFeeVault.sol": "0xa596e60762f16192cfa86459fcb9f4da9d8f756106eedac643a1ffeafbbfcc5f",
"src/L2/GasPriceOracle.sol": "0xc735a8bf01ad8bca194345748537bfd9924909c0342bc133c4a31e2fb8cb9882", "src/L2/GasPriceOracle.sol": "0xc735a8bf01ad8bca194345748537bfd9924909c0342bc133c4a31e2fb8cb9882",
"src/L2/L1Block.sol": "0x7fbfc8b4da630386636c665570321fdec287b0867dbe0f91c2e7cd5b7697c220", "src/L2/L1Block.sol": "0x7fbfc8b4da630386636c665570321fdec287b0867dbe0f91c2e7cd5b7697c220",
......
...@@ -202,17 +202,38 @@ contract Portal_Initializer is L2OutputOracle_Initializer { ...@@ -202,17 +202,38 @@ contract Portal_Initializer is L2OutputOracle_Initializer {
function setUp() public virtual override { function setUp() public virtual override {
super.setUp(); super.setUp();
ResourceMetering.ResourceConfig memory config = Constants.DEFAULT_RESOURCE_CONFIG(); Proxy systemConfigProxy = new Proxy(multisig);
systemConfig = new SystemConfig({ SystemConfig systemConfigImpl = new SystemConfig();
_owner: address(1),
_overhead: 0, vm.prank(multisig);
_scalar: 10000, systemConfigProxy.upgradeToAndCall(
_batcherHash: bytes32(0), address(systemConfigImpl),
_gasLimit: 30_000_000, abi.encodeCall(
_unsafeBlockSigner: address(0), SystemConfig.initialize,
_config: config (
}); address(1), //_owner,
0, //_overhead,
10000, //_scalar,
bytes32(0), //_batcherHash,
30_000_000, //_gasLimit,
address(0), //_unsafeBlockSigner,
Constants.DEFAULT_RESOURCE_CONFIG(), //_config,
0, //_startBlock
address(0xff), // _batchInbox
SystemConfig.Addresses({ // _addresses
l1CrossDomainMessenger: address(0),
l1ERC721Bridge: address(0),
l1StandardBridge: address(0),
l2OutputOracle: address(oracle),
optimismPortal: address(op),
optimismMintableERC20Factory: address(0)
})
)
)
);
systemConfig = SystemConfig(address(systemConfigProxy));
opImpl = new OptimismPortal(); opImpl = new OptimismPortal();
......
...@@ -3,24 +3,45 @@ pragma solidity 0.8.15; ...@@ -3,24 +3,45 @@ pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
import { SystemConfig } from "../../src/L1/SystemConfig.sol"; import { SystemConfig } from "../../src/L1/SystemConfig.sol";
import { Proxy } from "../../src/universal/Proxy.sol";
import { ResourceMetering } from "../../src/L1/ResourceMetering.sol"; import { ResourceMetering } from "../../src/L1/ResourceMetering.sol";
import { Constants } from "../../src/libraries/Constants.sol"; import { Constants } from "../../src/libraries/Constants.sol";
contract SystemConfig_GasLimitLowerBound_Invariant is Test { contract SystemConfig_GasLimitLowerBound_Invariant is Test {
SystemConfig public config; SystemConfig public config;
function setUp() public { function setUp() external {
ResourceMetering.ResourceConfig memory cfg = Constants.DEFAULT_RESOURCE_CONFIG(); Proxy proxy = new Proxy(msg.sender);
SystemConfig configImpl = new SystemConfig();
config = new SystemConfig({ vm.prank(msg.sender);
_owner: address(0xbeef), proxy.upgradeToAndCall(
_overhead: 2100, address(configImpl),
_scalar: 1000000, abi.encodeCall(
_batcherHash: bytes32(hex"abcd"), configImpl.initialize,
_gasLimit: 30_000_000, (
_unsafeBlockSigner: address(1), address(0xbeef), // owner
_config: cfg 2100, // overhead
}); 1000000, // scalar
bytes32(hex"abcd"), // batcher hash
30_000_000, // gas limit
address(1), // unsafe block signer
Constants.DEFAULT_RESOURCE_CONFIG(), // resource config
0, //_startBlock
address(0), // _batchInbox
SystemConfig.Addresses({ // _addrs
l1CrossDomainMessenger: address(0),
l1ERC721Bridge: address(0),
l1StandardBridge: address(0),
l2OutputOracle: address(0),
optimismPortal: address(0),
optimismMintableERC20Factory: address(0)
})
)
)
);
config = SystemConfig(address(proxy));
// Set the target contract to the `config` // Set the target contract to the `config`
targetContract(address(config)); targetContract(address(config));
......
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
"@testing-library/jest-dom": "^5.17.0", "@testing-library/jest-dom": "^5.17.0",
"@testing-library/react-hooks": "^8.0.1", "@testing-library/react-hooks": "^8.0.1",
"@types/glob": "^8.1.0", "@types/glob": "^8.1.0",
"@vitest/coverage-istanbul": "^0.33.0", "@vitest/coverage-istanbul": "^0.34.1",
"@wagmi/cli": "^1.3.0", "@wagmi/cli": "^1.3.0",
"@wagmi/core": "^1.3.8", "@wagmi/core": "^1.3.8",
"abitype": "^0.9.3", "abitype": "^0.9.3",
......
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
"@eth-optimism/contracts-ts": "workspace:^", "@eth-optimism/contracts-ts": "workspace:^",
"@testing-library/jest-dom": "^5.17.0", "@testing-library/jest-dom": "^5.17.0",
"@testing-library/react-hooks": "^8.0.1", "@testing-library/react-hooks": "^8.0.1",
"@vitest/coverage-istanbul": "^0.33.0", "@vitest/coverage-istanbul": "^0.34.1",
"abitype": "^0.9.3", "abitype": "^0.9.3",
"isomorphic-fetch": "^3.0.0", "isomorphic-fetch": "^3.0.0",
"jest-dom": "link:@types/@testing-library/jest-dom", "jest-dom": "link:@types/@testing-library/jest-dom",
......
...@@ -42,22 +42,22 @@ ...@@ -42,22 +42,22 @@
"@nomiclabs/hardhat-ethers": "^2.0.2", "@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-waffle": "^2.0.1", "@nomiclabs/hardhat-waffle": "^2.0.1",
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
"ethereum-waffle": "^3.4.0", "ethereum-waffle": "^4.0.10",
"ethers": "^5.7.0", "ethers": "^5.7.0",
"hardhat": "^2.9.6", "hardhat": "^2.9.6",
"hardhat-deploy": "^0.11.4", "hardhat-deploy": "^0.11.4",
"isomorphic-fetch": "^3.0.0",
"mocha": "^10.0.0",
"nyc": "^15.1.0", "nyc": "^15.1.0",
"typedoc": "^0.22.13", "typedoc": "^0.22.13",
"mocha": "^10.0.0",
"vitest": "^0.28.3",
"zod": "^3.11.6",
"viem": "^0.3.30", "viem": "^0.3.30",
"isomorphic-fetch": "^3.0.0" "vitest": "^0.28.3",
"zod": "^3.11.6"
}, },
"dependencies": { "dependencies": {
"@eth-optimism/contracts": "0.6.0", "@eth-optimism/contracts": "0.6.0",
"@eth-optimism/core-utils": "0.12.2",
"@eth-optimism/contracts-bedrock": "0.16.0", "@eth-optimism/contracts-bedrock": "0.16.0",
"@eth-optimism/core-utils": "0.12.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"merkletreejs": "^0.2.27", "merkletreejs": "^0.2.27",
"rlp": "^2.2.7" "rlp": "^2.2.7"
......
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