Commit 29761a64 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-challenger: Support multiple prestates (#10282)

* op-challenger: Implement prestate sources

* op-challenger: Add canon-prestates-url flag to configure a source for prestates based on hash
parent 66a21a9a
...@@ -462,7 +462,7 @@ func TestCannonRequiredArgs(t *testing.T) { ...@@ -462,7 +462,7 @@ func TestCannonRequiredArgs(t *testing.T) {
}) })
t.Run("Required", func(t *testing.T) { t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag cannon-prestate is required", addRequiredArgsExcept(traceType, "--cannon-prestate")) verifyArgsInvalid(t, "flag cannon-prestates-url or cannon-prestate is required", addRequiredArgsExcept(traceType, "--cannon-prestate"))
}) })
t.Run("Valid", func(t *testing.T) { t.Run("Valid", func(t *testing.T) {
...@@ -471,6 +471,21 @@ func TestCannonRequiredArgs(t *testing.T) { ...@@ -471,6 +471,21 @@ func TestCannonRequiredArgs(t *testing.T) {
}) })
}) })
t.Run(fmt.Sprintf("TestCannonAbsolutePrestateBaseURL-%v", traceType), func(t *testing.T) {
t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--cannon-prestates-url"))
})
t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag cannon-prestates-url or cannon-prestate is required", addRequiredArgsExcept(traceType, "--cannon-prestate"))
})
t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--cannon-prestates-url", "--cannon-prestates-url=http://localhost/foo"))
require.Equal(t, "http://localhost/foo", cfg.CannonAbsolutePreStateBaseURL.String())
})
})
t.Run(fmt.Sprintf("TestL2Rpc-%v", traceType), func(t *testing.T) { t.Run(fmt.Sprintf("TestL2Rpc-%v", traceType), func(t *testing.T) {
t.Run("NotRequiredForAlphabetTraceLegacy", func(t *testing.T) { t.Run("NotRequiredForAlphabetTraceLegacy", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--cannon-l2")) configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--cannon-l2"))
......
...@@ -3,6 +3,7 @@ package config ...@@ -3,6 +3,7 @@ package config
import ( import (
"errors" "errors"
"fmt" "fmt"
"net/url"
"runtime" "runtime"
"slices" "slices"
"time" "time"
...@@ -16,24 +17,25 @@ import ( ...@@ -16,24 +17,25 @@ import (
) )
var ( var (
ErrMissingTraceType = errors.New("no supported trace types specified") ErrMissingTraceType = errors.New("no supported trace types specified")
ErrMissingDatadir = errors.New("missing datadir") ErrMissingDatadir = errors.New("missing datadir")
ErrMaxConcurrencyZero = errors.New("max concurrency must not be 0") ErrMaxConcurrencyZero = errors.New("max concurrency must not be 0")
ErrMissingL2Rpc = errors.New("missing L2 rpc url") ErrMissingL2Rpc = errors.New("missing L2 rpc url")
ErrMissingCannonBin = errors.New("missing cannon bin") ErrMissingCannonBin = errors.New("missing cannon bin")
ErrMissingCannonServer = errors.New("missing cannon server") ErrMissingCannonServer = errors.New("missing cannon server")
ErrMissingCannonAbsolutePreState = errors.New("missing cannon absolute pre-state") ErrMissingCannonAbsolutePreState = errors.New("missing cannon absolute pre-state")
ErrMissingL1EthRPC = errors.New("missing l1 eth rpc url") ErrCannonAbsolutePreStateAndBaseURL = errors.New("only specify one of cannon absolute pre-state and cannon absolute pre-state base URL")
ErrMissingL1Beacon = errors.New("missing l1 beacon url") ErrMissingL1EthRPC = errors.New("missing l1 eth rpc url")
ErrMissingGameFactoryAddress = errors.New("missing game factory address") ErrMissingL1Beacon = errors.New("missing l1 beacon url")
ErrMissingCannonSnapshotFreq = errors.New("missing cannon snapshot freq") ErrMissingGameFactoryAddress = errors.New("missing game factory address")
ErrMissingCannonInfoFreq = errors.New("missing cannon info freq") ErrMissingCannonSnapshotFreq = errors.New("missing cannon snapshot freq")
ErrMissingCannonRollupConfig = errors.New("missing cannon network or rollup config path") ErrMissingCannonInfoFreq = errors.New("missing cannon info freq")
ErrMissingCannonL2Genesis = errors.New("missing cannon network or l2 genesis path") ErrMissingCannonRollupConfig = errors.New("missing cannon network or rollup config path")
ErrCannonNetworkAndRollupConfig = errors.New("only specify one of network or rollup config path") ErrMissingCannonL2Genesis = errors.New("missing cannon network or l2 genesis path")
ErrCannonNetworkAndL2Genesis = errors.New("only specify one of network or l2 genesis path") ErrCannonNetworkAndRollupConfig = errors.New("only specify one of network or rollup config path")
ErrCannonNetworkUnknown = errors.New("unknown cannon network") ErrCannonNetworkAndL2Genesis = errors.New("only specify one of network or l2 genesis path")
ErrMissingRollupRpc = errors.New("missing rollup rpc url") ErrCannonNetworkUnknown = errors.New("unknown cannon network")
ErrMissingRollupRpc = errors.New("missing rollup rpc url")
ErrMissingAsteriscBin = errors.New("missing asterisc bin") ErrMissingAsteriscBin = errors.New("missing asterisc bin")
ErrMissingAsteriscServer = errors.New("missing asterisc server") ErrMissingAsteriscServer = errors.New("missing asterisc server")
...@@ -124,14 +126,15 @@ type Config struct { ...@@ -124,14 +126,15 @@ type Config struct {
L2Rpc string // L2 RPC Url L2Rpc string // L2 RPC Url
// Specific to the cannon trace provider // Specific to the cannon trace provider
CannonBin string // Path to the cannon executable to run when generating trace data CannonBin string // Path to the cannon executable to run when generating trace data
CannonServer string // Path to the op-program executable that provides the pre-image oracle server CannonServer string // Path to the op-program executable that provides the pre-image oracle server
CannonAbsolutePreState string // File to load the absolute pre-state for Cannon traces from CannonAbsolutePreState string // File to load the absolute pre-state for Cannon traces from
CannonNetwork string CannonAbsolutePreStateBaseURL *url.URL // Base URL to retrieve absolute pre-states for Cannon traces from
CannonRollupConfigPath string CannonNetwork string
CannonL2GenesisPath string CannonRollupConfigPath string
CannonSnapshotFreq uint // Frequency of snapshots to create when executing cannon (in VM instructions) CannonL2GenesisPath string
CannonInfoFreq uint // Frequency of cannon progress log messages (in VM instructions) CannonSnapshotFreq uint // Frequency of snapshots to create when executing cannon (in VM instructions)
CannonInfoFreq uint // Frequency of cannon progress log messages (in VM instructions)
// Specific to the asterisc trace provider // Specific to the asterisc trace provider
AsteriscBin string // Path to the asterisc executable to run when generating trace data AsteriscBin string // Path to the asterisc executable to run when generating trace data
...@@ -233,9 +236,12 @@ func (c Config) Check() error { ...@@ -233,9 +236,12 @@ func (c Config) Check() error {
return fmt.Errorf("%w: %v", ErrCannonNetworkUnknown, c.CannonNetwork) return fmt.Errorf("%w: %v", ErrCannonNetworkUnknown, c.CannonNetwork)
} }
} }
if c.CannonAbsolutePreState == "" { if c.CannonAbsolutePreState == "" && c.CannonAbsolutePreStateBaseURL == nil {
return ErrMissingCannonAbsolutePreState return ErrMissingCannonAbsolutePreState
} }
if c.CannonAbsolutePreState != "" && c.CannonAbsolutePreStateBaseURL != nil {
return ErrCannonAbsolutePreStateAndBaseURL
}
if c.L2Rpc == "" { if c.L2Rpc == "" {
return ErrMissingL2Rpc return ErrMissingL2Rpc
} }
......
...@@ -2,6 +2,7 @@ package config ...@@ -2,6 +2,7 @@ package config
import ( import (
"fmt" "fmt"
"net/url"
"runtime" "runtime"
"testing" "testing"
...@@ -12,16 +13,17 @@ import ( ...@@ -12,16 +13,17 @@ import (
) )
var ( var (
validL1EthRpc = "http://localhost:8545" validL1EthRpc = "http://localhost:8545"
validL1BeaconUrl = "http://localhost:9000" validL1BeaconUrl = "http://localhost:9000"
validGameFactoryAddress = common.Address{0x23} validGameFactoryAddress = common.Address{0x23}
validCannonBin = "./bin/cannon" validCannonBin = "./bin/cannon"
validCannonOpProgramBin = "./bin/op-program" validCannonOpProgramBin = "./bin/op-program"
validCannonNetwork = "mainnet" validCannonNetwork = "mainnet"
validCannonAbsolutPreState = "pre.json" validCannonAbsolutPreState = "pre.json"
validDatadir = "/tmp/data" validCannonAbsolutPreStateBaseURL, _ = url.Parse("http://localhost/foo/")
validL2Rpc = "http://localhost:9545" validDatadir = "/tmp/data"
validRollupRpc = "http://localhost:8555" validL2Rpc = "http://localhost:9545"
validRollupRpc = "http://localhost:8555"
validAsteriscBin = "./bin/asterisc" validAsteriscBin = "./bin/asterisc"
validAsteriscOpProgramBin = "./bin/op-program" validAsteriscOpProgramBin = "./bin/op-program"
...@@ -34,7 +36,7 @@ var cannonTraceTypes = []TraceType{TraceTypeCannon, TraceTypePermissioned} ...@@ -34,7 +36,7 @@ var cannonTraceTypes = []TraceType{TraceTypeCannon, TraceTypePermissioned}
func applyValidConfigForCannon(cfg *Config) { func applyValidConfigForCannon(cfg *Config) {
cfg.CannonBin = validCannonBin cfg.CannonBin = validCannonBin
cfg.CannonServer = validCannonOpProgramBin cfg.CannonServer = validCannonOpProgramBin
cfg.CannonAbsolutePreState = validCannonAbsolutPreState cfg.CannonAbsolutePreStateBaseURL = validCannonAbsolutPreStateBaseURL
cfg.CannonNetwork = validCannonNetwork cfg.CannonNetwork = validCannonNetwork
cfg.L2Rpc = validL2Rpc cfg.L2Rpc = validL2Rpc
} }
...@@ -124,12 +126,34 @@ func TestCannonRequiredArgs(t *testing.T) { ...@@ -124,12 +126,34 @@ func TestCannonRequiredArgs(t *testing.T) {
require.ErrorIs(t, config.Check(), ErrMissingCannonServer) require.ErrorIs(t, config.Check(), ErrMissingCannonServer)
}) })
t.Run(fmt.Sprintf("TestCannonAbsolutePreStateRequired-%v", traceType), func(t *testing.T) { t.Run(fmt.Sprintf("TestCannonAbsolutePreStateOrBaseURLRequired-%v", traceType), func(t *testing.T) {
config := validConfig(traceType) config := validConfig(traceType)
config.CannonAbsolutePreState = "" config.CannonAbsolutePreState = ""
config.CannonAbsolutePreStateBaseURL = nil
require.ErrorIs(t, config.Check(), ErrMissingCannonAbsolutePreState) require.ErrorIs(t, config.Check(), ErrMissingCannonAbsolutePreState)
}) })
t.Run(fmt.Sprintf("TestCannonAbsolutePreState-%v", traceType), func(t *testing.T) {
config := validConfig(traceType)
config.CannonAbsolutePreState = validCannonAbsolutPreState
config.CannonAbsolutePreStateBaseURL = nil
require.NoError(t, config.Check())
})
t.Run(fmt.Sprintf("TestCannonAbsolutePreStateBaseURL-%v", traceType), func(t *testing.T) {
config := validConfig(traceType)
config.CannonAbsolutePreState = ""
config.CannonAbsolutePreStateBaseURL = validCannonAbsolutPreStateBaseURL
require.NoError(t, config.Check())
})
t.Run(fmt.Sprintf("TestMustNotSupplyBothCannonAbsolutePreStateAndBaseURL-%v", traceType), func(t *testing.T) {
config := validConfig(traceType)
config.CannonAbsolutePreState = validCannonAbsolutPreState
config.CannonAbsolutePreStateBaseURL = validCannonAbsolutPreStateBaseURL
require.ErrorIs(t, config.Check(), ErrCannonAbsolutePreStateAndBaseURL)
})
t.Run(fmt.Sprintf("TestL2RpcRequired-%v", traceType), func(t *testing.T) { t.Run(fmt.Sprintf("TestL2RpcRequired-%v", traceType), func(t *testing.T) {
config := validConfig(traceType) config := validConfig(traceType)
config.L2Rpc = "" config.L2Rpc = ""
...@@ -238,6 +262,7 @@ func TestRequireConfigForMultipleTraceTypesForCannon(t *testing.T) { ...@@ -238,6 +262,7 @@ func TestRequireConfigForMultipleTraceTypesForCannon(t *testing.T) {
// Require cannon specific args // Require cannon specific args
cfg.CannonAbsolutePreState = "" cfg.CannonAbsolutePreState = ""
cfg.CannonAbsolutePreStateBaseURL = nil
require.ErrorIs(t, cfg.Check(), ErrMissingCannonAbsolutePreState) require.ErrorIs(t, cfg.Check(), ErrMissingCannonAbsolutePreState)
cfg.CannonAbsolutePreState = validCannonAbsolutPreState cfg.CannonAbsolutePreState = validCannonAbsolutPreState
......
...@@ -2,6 +2,7 @@ package flags ...@@ -2,6 +2,7 @@ package flags
import ( import (
"fmt" "fmt"
"net/url"
"runtime" "runtime"
"slices" "slices"
"strings" "strings"
...@@ -126,6 +127,12 @@ var ( ...@@ -126,6 +127,12 @@ var (
Usage: "Path to absolute prestate to use when generating trace data (cannon trace type only)", Usage: "Path to absolute prestate to use when generating trace data (cannon trace type only)",
EnvVars: prefixEnvVars("CANNON_PRESTATE"), EnvVars: prefixEnvVars("CANNON_PRESTATE"),
} }
CannonPreStatesURLFlag = &cli.StringFlag{
Name: "cannon-prestates-url",
Usage: "Base URL to absolute prestates to use when generating trace data. " +
"Prestates in this directory should be name as <commitment>.json (cannon trace type only)",
EnvVars: prefixEnvVars("CANNON_PRESTATES_URL"),
}
CannonL2Flag = &cli.StringFlag{ CannonL2Flag = &cli.StringFlag{
Name: "cannon-l2", Name: "cannon-l2",
Usage: fmt.Sprintf("Deprecated: Use %v instead", L2RpcFlag.Name), Usage: fmt.Sprintf("Deprecated: Use %v instead", L2RpcFlag.Name),
...@@ -232,6 +239,7 @@ var optionalFlags = []cli.Flag{ ...@@ -232,6 +239,7 @@ var optionalFlags = []cli.Flag{
CannonBinFlag, CannonBinFlag,
CannonServerFlag, CannonServerFlag,
CannonPreStateFlag, CannonPreStateFlag,
CannonPreStatesURLFlag,
CannonL2Flag, CannonL2Flag,
CannonSnapshotFreqFlag, CannonSnapshotFreqFlag,
CannonInfoFreqFlag, CannonInfoFreqFlag,
...@@ -277,8 +285,8 @@ func CheckCannonFlags(ctx *cli.Context) error { ...@@ -277,8 +285,8 @@ func CheckCannonFlags(ctx *cli.Context) error {
if !ctx.IsSet(CannonServerFlag.Name) { if !ctx.IsSet(CannonServerFlag.Name) {
return fmt.Errorf("flag %s is required", CannonServerFlag.Name) return fmt.Errorf("flag %s is required", CannonServerFlag.Name)
} }
if !ctx.IsSet(CannonPreStateFlag.Name) { if !ctx.IsSet(CannonPreStateFlag.Name) && !ctx.IsSet(CannonPreStatesURLFlag.Name) {
return fmt.Errorf("flag %s is required", CannonPreStateFlag.Name) return fmt.Errorf("flag %s or %s is required", CannonPreStatesURLFlag.Name, CannonPreStateFlag.Name)
} }
// CannonL2Flag is checked because it is an alias with L2RpcFlag // CannonL2Flag is checked because it is an alias with L2RpcFlag
if !ctx.IsSet(CannonL2Flag.Name) && !ctx.IsSet(L2RpcFlag.Name) { if !ctx.IsSet(CannonL2Flag.Name) && !ctx.IsSet(L2RpcFlag.Name) {
...@@ -408,45 +416,54 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) { ...@@ -408,45 +416,54 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
claimants = append(claimants, claimant) claimants = append(claimants, claimant)
} }
} }
var cannonPrestatesURL *url.URL
if ctx.IsSet(CannonPreStatesURLFlag.Name) {
parsed, err := url.Parse(ctx.String(CannonPreStatesURLFlag.Name))
if err != nil {
return nil, fmt.Errorf("invalid cannon pre states url (%v): %w", ctx.String(CannonPreStatesURLFlag.Name), err)
}
cannonPrestatesURL = parsed
}
l2Rpc, err := getL2Rpc(ctx) l2Rpc, err := getL2Rpc(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &config.Config{ return &config.Config{
// Required Flags // Required Flags
L1EthRpc: ctx.String(L1EthRpcFlag.Name), L1EthRpc: ctx.String(L1EthRpcFlag.Name),
L1Beacon: ctx.String(L1BeaconFlag.Name), L1Beacon: ctx.String(L1BeaconFlag.Name),
TraceTypes: traceTypes, TraceTypes: traceTypes,
GameFactoryAddress: gameFactoryAddress, GameFactoryAddress: gameFactoryAddress,
GameAllowlist: allowedGames, GameAllowlist: allowedGames,
GameWindow: ctx.Duration(GameWindowFlag.Name), GameWindow: ctx.Duration(GameWindowFlag.Name),
MaxConcurrency: maxConcurrency, MaxConcurrency: maxConcurrency,
L2Rpc: l2Rpc, L2Rpc: l2Rpc,
MaxPendingTx: ctx.Uint64(MaxPendingTransactionsFlag.Name), MaxPendingTx: ctx.Uint64(MaxPendingTransactionsFlag.Name),
PollInterval: ctx.Duration(HTTPPollInterval.Name), PollInterval: ctx.Duration(HTTPPollInterval.Name),
AdditionalBondClaimants: claimants, AdditionalBondClaimants: claimants,
RollupRpc: ctx.String(RollupRpcFlag.Name), RollupRpc: ctx.String(RollupRpcFlag.Name),
CannonNetwork: ctx.String(CannonNetworkFlag.Name), CannonNetwork: ctx.String(CannonNetworkFlag.Name),
CannonRollupConfigPath: ctx.String(CannonRollupConfigFlag.Name), CannonRollupConfigPath: ctx.String(CannonRollupConfigFlag.Name),
CannonL2GenesisPath: ctx.String(CannonL2GenesisFlag.Name), CannonL2GenesisPath: ctx.String(CannonL2GenesisFlag.Name),
CannonBin: ctx.String(CannonBinFlag.Name), CannonBin: ctx.String(CannonBinFlag.Name),
CannonServer: ctx.String(CannonServerFlag.Name), CannonServer: ctx.String(CannonServerFlag.Name),
CannonAbsolutePreState: ctx.String(CannonPreStateFlag.Name), CannonAbsolutePreState: ctx.String(CannonPreStateFlag.Name),
Datadir: ctx.String(DatadirFlag.Name), CannonAbsolutePreStateBaseURL: cannonPrestatesURL,
CannonSnapshotFreq: ctx.Uint(CannonSnapshotFreqFlag.Name), Datadir: ctx.String(DatadirFlag.Name),
CannonInfoFreq: ctx.Uint(CannonInfoFreqFlag.Name), CannonSnapshotFreq: ctx.Uint(CannonSnapshotFreqFlag.Name),
AsteriscNetwork: ctx.String(AsteriscNetworkFlag.Name), CannonInfoFreq: ctx.Uint(CannonInfoFreqFlag.Name),
AsteriscRollupConfigPath: ctx.String(AsteriscRollupConfigFlag.Name), AsteriscNetwork: ctx.String(AsteriscNetworkFlag.Name),
AsteriscL2GenesisPath: ctx.String(AsteriscL2GenesisFlag.Name), AsteriscRollupConfigPath: ctx.String(AsteriscRollupConfigFlag.Name),
AsteriscBin: ctx.String(AsteriscBinFlag.Name), AsteriscL2GenesisPath: ctx.String(AsteriscL2GenesisFlag.Name),
AsteriscServer: ctx.String(AsteriscServerFlag.Name), AsteriscBin: ctx.String(AsteriscBinFlag.Name),
AsteriscAbsolutePreState: ctx.String(AsteriscPreStateFlag.Name), AsteriscServer: ctx.String(AsteriscServerFlag.Name),
AsteriscSnapshotFreq: ctx.Uint(AsteriscSnapshotFreqFlag.Name), AsteriscAbsolutePreState: ctx.String(AsteriscPreStateFlag.Name),
AsteriscInfoFreq: ctx.Uint(AsteriscInfoFreqFlag.Name), AsteriscSnapshotFreq: ctx.Uint(AsteriscSnapshotFreqFlag.Name),
TxMgrConfig: txMgrConfig, AsteriscInfoFreq: ctx.Uint(AsteriscInfoFreqFlag.Name),
MetricsConfig: metricsConfig, TxMgrConfig: txMgrConfig,
PprofConfig: pprofConfig, MetricsConfig: metricsConfig,
SelectiveClaimResolution: ctx.Bool(SelectiveClaimResolutionFlag.Name), PprofConfig: pprofConfig,
AllowInvalidPrestate: ctx.Bool(UnsafeAllowInvalidPrestate.Name), SelectiveClaimResolution: ctx.Bool(SelectiveClaimResolutionFlag.Name),
AllowInvalidPrestate: ctx.Bool(UnsafeAllowInvalidPrestate.Name),
}, nil }, nil
} }
...@@ -3,6 +3,7 @@ package fault ...@@ -3,6 +3,7 @@ package fault
import ( import (
"context" "context"
"fmt" "fmt"
"path/filepath"
"github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/claims" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/claims"
...@@ -11,6 +12,7 @@ import ( ...@@ -11,6 +12,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/asterisc" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/asterisc"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/outputs" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/outputs"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/prestates"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types" keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
...@@ -36,6 +38,13 @@ type OracleRegistry interface { ...@@ -36,6 +38,13 @@ type OracleRegistry interface {
RegisterOracle(oracle keccakTypes.LargePreimageOracle) RegisterOracle(oracle keccakTypes.LargePreimageOracle)
} }
type PrestateSource interface {
// PrestatePath returns the path to the prestate file to use for the game.
// The provided prestateHash may be used to differentiate between different states but no guarantee is made that
// the returned prestate matches the supplied hash.
PrestatePath(prestateHash common.Hash) (string, error)
}
type RollupClient interface { type RollupClient interface {
outputs.OutputRollupClient outputs.OutputRollupClient
SyncStatusProvider SyncStatusProvider
...@@ -253,9 +262,25 @@ func registerCannon( ...@@ -253,9 +262,25 @@ func registerCannon(
selective bool, selective bool,
claimants []common.Address, claimants []common.Address,
) error { ) error {
cannonPrestateProvider := cannon.NewPrestateProvider(cfg.CannonAbsolutePreState) var prestateSource PrestateSource
if cfg.CannonAbsolutePreStateBaseURL != nil {
prestateSource = prestates.NewMultiPrestateProvider(cfg.CannonAbsolutePreStateBaseURL, filepath.Join(cfg.Datadir, "cannon-prestates"))
} else {
prestateSource = prestates.NewSinglePrestateSource(cfg.CannonAbsolutePreState)
}
playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) { playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
contract := contracts.NewFaultDisputeGameContract(m, game.Proxy, caller) contract := contracts.NewFaultDisputeGameContract(m, game.Proxy, caller)
requiredPrestatehash, err := contract.GetAbsolutePrestateHash(ctx)
if err != nil {
return nil, fmt.Errorf("failed to load prestate hash for game %v: %w", game.Proxy, err)
}
prestatePath, err := prestateSource.PrestatePath(requiredPrestatehash)
if err != nil {
return nil, fmt.Errorf("required prestate %v not available for game %v: %w", requiredPrestatehash, game.Proxy, err)
}
cannonPrestateProvider := cannon.NewPrestateProvider(prestatePath)
oracle, err := contract.GetOracle(ctx) oracle, err := contract.GetOracle(ctx)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to load oracle for game %v: %w", game.Proxy, err) return nil, fmt.Errorf("failed to load oracle for game %v: %w", game.Proxy, err)
......
...@@ -34,19 +34,22 @@ type CannonTraceProvider struct { ...@@ -34,19 +34,22 @@ type CannonTraceProvider struct {
gameDepth types.Depth gameDepth types.Depth
preimageLoader *utils.PreimageLoader preimageLoader *utils.PreimageLoader
types.PrestateProvider
// lastStep stores the last step in the actual trace if known. 0 indicates unknown. // 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. // Cached as an optimisation to avoid repeatedly attempting to execute beyond the end of the trace.
lastStep uint64 lastStep uint64
} }
func NewTraceProvider(logger log.Logger, m CannonMetricer, cfg *config.Config, localInputs utils.LocalGameInputs, dir string, gameDepth types.Depth) *CannonTraceProvider { func NewTraceProvider(logger log.Logger, m CannonMetricer, cfg *config.Config, prestateProvider types.PrestateProvider, localInputs utils.LocalGameInputs, dir string, gameDepth types.Depth) *CannonTraceProvider {
return &CannonTraceProvider{ return &CannonTraceProvider{
logger: logger, logger: logger,
dir: dir, dir: dir,
prestate: cfg.CannonAbsolutePreState, prestate: cfg.CannonAbsolutePreState,
generator: NewExecutor(logger, m, cfg, localInputs), generator: NewExecutor(logger, m, cfg, localInputs),
gameDepth: gameDepth, gameDepth: gameDepth,
preimageLoader: utils.NewPreimageLoader(kvstore.NewDiskKV(utils.PreimageDir(dir)).Get), preimageLoader: utils.NewPreimageLoader(kvstore.NewDiskKV(utils.PreimageDir(dir)).Get),
PrestateProvider: prestateProvider,
} }
} }
...@@ -91,26 +94,6 @@ func (p *CannonTraceProvider) GetStepData(ctx context.Context, pos types.Positio ...@@ -91,26 +94,6 @@ func (p *CannonTraceProvider) GetStepData(ctx context.Context, pos types.Positio
return value, data, oracleData, nil return value, data, oracleData, nil
} }
func (p *CannonTraceProvider) absolutePreState() ([]byte, error) {
state, err := parseState(p.prestate)
if err != nil {
return nil, fmt.Errorf("cannot load absolute pre-state: %w", err)
}
return state.EncodeWitness(), nil
}
func (p *CannonTraceProvider) AbsolutePreStateCommitment(_ context.Context) (common.Hash, error) {
state, err := p.absolutePreState()
if err != nil {
return common.Hash{}, fmt.Errorf("cannot load absolute pre-state: %w", err)
}
hash, err := mipsevm.StateWitness(state).StateHash()
if err != nil {
return common.Hash{}, fmt.Errorf("cannot hash absolute pre-state: %w", err)
}
return hash, nil
}
// loadProof will attempt to load or generate the proof data at the specified index // 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 it is extended with no-op instructions. // If the requested index is beyond the end of the actual trace it is extended with no-op instructions.
func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*utils.ProofData, error) { func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*utils.ProofData, error) {
......
...@@ -3,6 +3,7 @@ package cannon ...@@ -3,6 +3,7 @@ package cannon
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-service/ioutil" "github.com/ethereum-optimism/optimism/op-service/ioutil"
...@@ -13,11 +14,14 @@ func parseState(path string) (*mipsevm.State, error) { ...@@ -13,11 +14,14 @@ func parseState(path string) (*mipsevm.State, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot open state file (%v): %w", path, err) return nil, fmt.Errorf("cannot open state file (%v): %w", path, err)
} }
defer file.Close() return parseStateFromReader(file)
}
func parseStateFromReader(in io.ReadCloser) (*mipsevm.State, error) {
defer in.Close()
var state mipsevm.State var state mipsevm.State
err = json.NewDecoder(file).Decode(&state) if err := json.NewDecoder(in).Decode(&state); err != nil {
if err != nil { return nil, fmt.Errorf("invalid mipsevm state: %w", err)
return nil, fmt.Errorf("invalid mipsevm state (%v): %w", path, err)
} }
return &state, nil return &state, nil
} }
...@@ -39,7 +39,7 @@ func NewOutputCannonTraceAccessor( ...@@ -39,7 +39,7 @@ func NewOutputCannonTraceAccessor(
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to fetch cannon local inputs: %w", err) return nil, fmt.Errorf("failed to fetch cannon local inputs: %w", err)
} }
provider := cannon.NewTraceProvider(logger, m, cfg, localInputs, subdir, depth) provider := cannon.NewTraceProvider(logger, m, cfg, prestateProvider, localInputs, subdir, depth)
return provider, nil return provider, nil
} }
......
package prestates
import (
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum/go-ethereum/common"
)
var (
ErrPrestateUnavailable = errors.New("prestate unavailable")
)
type MultiPrestateProvider struct {
baseUrl *url.URL
dataDir string
}
func NewMultiPrestateProvider(baseUrl *url.URL, dataDir string) *MultiPrestateProvider {
return &MultiPrestateProvider{
baseUrl: baseUrl,
dataDir: dataDir,
}
}
func (m *MultiPrestateProvider) PrestatePath(hash common.Hash) (string, error) {
path := filepath.Join(m.dataDir, hash.Hex()+".json.gz")
if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
if err := m.fetchPrestate(hash, path); err != nil {
return "", fmt.Errorf("failed to fetch prestate: %w", err)
}
} else if err != nil {
return "", fmt.Errorf("error checking for existing prestate %v: %w", hash, err)
}
return path, nil
}
func (m *MultiPrestateProvider) fetchPrestate(hash common.Hash, dest string) error {
if err := os.MkdirAll(m.dataDir, 0755); err != nil {
return fmt.Errorf("error creating prestate dir: %w", err)
}
prestateUrl := m.baseUrl.JoinPath(hash.Hex() + ".json")
resp, err := http.Get(prestateUrl.String())
if err != nil {
return fmt.Errorf("failed to fetch prestate from %v: %w", prestateUrl, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("%w from url %v: status %v", ErrPrestateUnavailable, prestateUrl, resp.StatusCode)
}
out, err := ioutil.NewAtomicWriterCompressed(dest, 0o644)
if err != nil {
return fmt.Errorf("failed to open atomic writer for %v: %w", dest, err)
}
defer func() {
// If errors occur, try to clean up without renaming the file into its final destination as Close() would do
_ = out.Abort()
}()
if _, err := io.Copy(out, resp.Body); err != nil {
return fmt.Errorf("failed to write file %v: %w", dest, err)
}
if err := out.Close(); err != nil {
return fmt.Errorf("failed to close file %v: %w", dest, err)
}
return nil
}
package prestates
import (
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"testing"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func TestDownloadPrestate(t *testing.T) {
dir := t.TempDir()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(r.URL.Path))
}))
defer server.Close()
provider := NewMultiPrestateProvider(parseURL(t, server.URL), dir)
hash := common.Hash{0xaa}
path, err := provider.PrestatePath(hash)
require.NoError(t, err)
in, err := ioutil.OpenDecompressed(path)
require.NoError(t, err)
defer in.Close()
content, err := io.ReadAll(in)
require.NoError(t, err)
require.Equal(t, "/"+hash.Hex()+".json", string(content))
}
func TestCreateDirectory(t *testing.T) {
dir := t.TempDir()
dir = filepath.Join(dir, "test")
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(r.URL.Path))
}))
defer server.Close()
provider := NewMultiPrestateProvider(parseURL(t, server.URL), dir)
hash := common.Hash{0xaa}
path, err := provider.PrestatePath(hash)
require.NoError(t, err)
in, err := ioutil.OpenDecompressed(path)
require.NoError(t, err)
defer in.Close()
content, err := io.ReadAll(in)
require.NoError(t, err)
require.Equal(t, "/"+hash.Hex()+".json", string(content))
}
func TestExistingPrestate(t *testing.T) {
dir := t.TempDir()
provider := NewMultiPrestateProvider(parseURL(t, "http://127.0.0.1:1"), dir)
hash := common.Hash{0xaa}
expectedFile := filepath.Join(dir, hash.Hex()+".json.gz")
err := ioutil.WriteCompressedBytes(expectedFile, []byte("expected content"), os.O_WRONLY|os.O_CREATE, 0o644)
require.NoError(t, err)
path, err := provider.PrestatePath(hash)
require.NoError(t, err)
require.Equal(t, expectedFile, path)
in, err := ioutil.OpenDecompressed(path)
require.NoError(t, err)
defer in.Close()
content, err := io.ReadAll(in)
require.NoError(t, err)
require.Equal(t, "expected content", string(content))
}
func TestMissingPrestate(t *testing.T) {
dir := t.TempDir()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(404)
}))
defer server.Close()
provider := NewMultiPrestateProvider(parseURL(t, server.URL), dir)
hash := common.Hash{0xaa}
path, err := provider.PrestatePath(hash)
require.ErrorIs(t, err, ErrPrestateUnavailable)
_, err = os.Stat(path)
require.ErrorIs(t, err, os.ErrNotExist)
}
func parseURL(t *testing.T, str string) *url.URL {
parsed, err := url.Parse(str)
require.NoError(t, err)
return parsed
}
package prestates
import "github.com/ethereum/go-ethereum/common"
type SinglePrestateSource struct {
path string
}
func NewSinglePrestateSource(path string) *SinglePrestateSource {
return &SinglePrestateSource{path: path}
}
func (s *SinglePrestateSource) PrestatePath(_ common.Hash) (string, error) {
return s.path, nil
}
...@@ -6,7 +6,7 @@ import ( ...@@ -6,7 +6,7 @@ import (
"path/filepath" "path/filepath"
) )
type atomicWriter struct { type AtomicWriter struct {
dest string dest string
temp string temp string
out io.WriteCloser out io.WriteCloser
...@@ -16,7 +16,7 @@ type atomicWriter struct { ...@@ -16,7 +16,7 @@ type atomicWriter struct {
// The contents are initially written to a temporary file and only renamed into place when the writer is closed. // The contents are initially written to a temporary file and only renamed into place when the writer is closed.
// NOTE: It's vital to check if an error is returned from Close() as it may indicate the file could not be renamed // NOTE: It's vital to check if an error is returned from Close() as it may indicate the file could not be renamed
// If path ends in .gz the contents written will be gzipped. // If path ends in .gz the contents written will be gzipped.
func NewAtomicWriterCompressed(path string, perm os.FileMode) (io.WriteCloser, error) { func NewAtomicWriterCompressed(path string, perm os.FileMode) (*AtomicWriter, error) {
f, err := os.CreateTemp(filepath.Dir(path), filepath.Base(path)) f, err := os.CreateTemp(filepath.Dir(path), filepath.Base(path))
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -25,18 +25,26 @@ func NewAtomicWriterCompressed(path string, perm os.FileMode) (io.WriteCloser, e ...@@ -25,18 +25,26 @@ func NewAtomicWriterCompressed(path string, perm os.FileMode) (io.WriteCloser, e
_ = f.Close() _ = f.Close()
return nil, err return nil, err
} }
return &atomicWriter{ return &AtomicWriter{
dest: path, dest: path,
temp: f.Name(), temp: f.Name(),
out: CompressByFileType(path, f), out: CompressByFileType(path, f),
}, nil }, nil
} }
func (a *atomicWriter) Write(p []byte) (n int, err error) { func (a *AtomicWriter) Write(p []byte) (n int, err error) {
return a.out.Write(p) return a.out.Write(p)
} }
func (a *atomicWriter) Close() error { // Abort releases any open resources and cleans up temporary files without renaming them into place.
// Does nothing if the writer has already been closed.
func (a *AtomicWriter) Abort() error {
// Attempt to clean up the temp file even if Close fails.
defer os.Remove(a.temp)
return a.out.Close()
}
func (a *AtomicWriter) Close() error {
// Attempt to clean up the temp file even if it can't be renamed into place. // Attempt to clean up the temp file even if it can't be renamed into place.
defer os.Remove(a.temp) defer os.Remove(a.temp)
if err := a.out.Close(); err != nil { if err := a.out.Close(); err != nil {
......
...@@ -46,6 +46,30 @@ func TestAtomicWriter_MultipleClose(t *testing.T) { ...@@ -46,6 +46,30 @@ func TestAtomicWriter_MultipleClose(t *testing.T) {
require.ErrorIs(t, f.Close(), os.ErrClosed) require.ErrorIs(t, f.Close(), os.ErrClosed)
} }
func TestAtomicWriter_AbortBeforeClose(t *testing.T) {
dir := t.TempDir()
target := filepath.Join(dir, "target.txt")
f, err := NewAtomicWriterCompressed(target, 0755)
require.NoError(t, err)
require.NoError(t, f.Abort())
_, err = os.Stat(target)
require.ErrorIs(t, err, os.ErrNotExist, "should not create target file when aborted")
require.ErrorIs(t, f.Close(), os.ErrClosed)
}
func TestAtomicWriter_AbortAfterClose(t *testing.T) {
dir := t.TempDir()
target := filepath.Join(dir, "target.txt")
f, err := NewAtomicWriterCompressed(target, 0755)
require.NoError(t, err)
require.NoError(t, f.Close())
_, err = os.Stat(target)
require.NoError(t, err)
require.ErrorIs(t, f.Abort(), os.ErrClosed)
}
func TestAtomicWriter_ApplyGzip(t *testing.T) { func TestAtomicWriter_ApplyGzip(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
......
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