Commit c38ce096 authored by Park Changwan's avatar Park Changwan Committed by GitHub

op-challenger: Asterisc Support with Refactoring (#10094)

* op-challenger: Refactor cannon trace provider

Move gzip method to ioutil

* op-challenger: Expose cannon tracer methods

* op-challenger: asterisc implementation

Basic unit tests mirrored from cannon.

* op-challenger: asterisc flags and configs

* op-challenger: asterisc metric helper

* op-challenger: asterisc registration and add types

* op-dispute-mon: enable asterisc type

* Fix typo

* Fix l2RPC when registering asterisc

* Deduplicate preimageOpts for testing

* Fix comments

* Style issue fix

* Deduplicate network flag for testing

* Remove redundant prefix at filename

* Add config test for using both cannon and asterisc

* Refactor asterisc/cannon provider using utils

* Remove redundant prefix at filename

* Fix comments

* Style issue fix

* Refactor asterisc/cannon provider using utils

* Remove unused methods

* op-challenger: add l2-rpc flag and deprecate cannon-l2 flag
parent 48771c85
......@@ -45,7 +45,7 @@ make op-challenger op-program cannon
--cannon-bin ./cannon/bin/cannon \
--cannon-server ./op-program/bin/op-program \
--cannon-prestate <PRESTATE> \
--cannon-l2 <L2_URL> \
--l2-rpc <L2_URL> \
--private-key <PRIVATE_KEY>
```
......
......@@ -44,7 +44,7 @@ DISPUTE_GAME_FACTORY=$(jq -r .DisputeGameFactoryProxy .devnet/addresses.json)
--cannon-bin ./cannon/bin/cannon \
--cannon-server ./op-program/bin/op-program \
--cannon-prestate ./op-program/bin/prestate.json \
--cannon-l2 http://localhost:9545 \
--l2-rpc http://localhost:9545 \
--mnemonic "test test test test test test test test test test test junk" \
--hd-path "m/44'/60'/0'/0/8" \
--num-confirmations 1
......
This diff is collapsed.
......@@ -19,7 +19,7 @@ var (
ErrMissingTraceType = errors.New("no supported trace types specified")
ErrMissingDatadir = errors.New("missing datadir")
ErrMaxConcurrencyZero = errors.New("max concurrency must not be 0")
ErrMissingCannonL2 = errors.New("missing cannon L2")
ErrMissingL2Rpc = errors.New("missing L2 rpc url")
ErrMissingCannonBin = errors.New("missing cannon bin")
ErrMissingCannonServer = errors.New("missing cannon server")
ErrMissingCannonAbsolutePreState = errors.New("missing cannon absolute pre-state")
......@@ -34,6 +34,17 @@ var (
ErrCannonNetworkAndL2Genesis = errors.New("only specify one of network or l2 genesis path")
ErrCannonNetworkUnknown = errors.New("unknown cannon network")
ErrMissingRollupRpc = errors.New("missing rollup rpc url")
ErrMissingAsteriscBin = errors.New("missing asterisc bin")
ErrMissingAsteriscServer = errors.New("missing asterisc server")
ErrMissingAsteriscAbsolutePreState = errors.New("missing asterisc absolute pre-state")
ErrMissingAsteriscSnapshotFreq = errors.New("missing asterisc snapshot freq")
ErrMissingAsteriscInfoFreq = errors.New("missing asterisc info freq")
ErrMissingAsteriscRollupConfig = errors.New("missing asterisc network or rollup config path")
ErrMissingAsteriscL2Genesis = errors.New("missing asterisc network or l2 genesis path")
ErrAsteriscNetworkAndRollupConfig = errors.New("only specify one of network or rollup config path")
ErrAsteriscNetworkAndL2Genesis = errors.New("only specify one of network or l2 genesis path")
ErrAsteriscNetworkUnknown = errors.New("unknown asterisc network")
)
type TraceType string
......@@ -41,10 +52,11 @@ type TraceType string
const (
TraceTypeAlphabet TraceType = "alphabet"
TraceTypeCannon TraceType = "cannon"
TraceTypeAsterisc TraceType = "asterisc"
TraceTypePermissioned TraceType = "permissioned"
)
var TraceTypes = []TraceType{TraceTypeAlphabet, TraceTypeCannon, TraceTypePermissioned}
var TraceTypes = []TraceType{TraceTypeAlphabet, TraceTypeCannon, TraceTypePermissioned, TraceTypeAsterisc}
func (t TraceType) String() string {
return string(t)
......@@ -74,9 +86,11 @@ func ValidTraceType(value TraceType) bool {
}
const (
DefaultPollInterval = time.Second * 12
DefaultCannonSnapshotFreq = uint(1_000_000_000)
DefaultCannonInfoFreq = uint(10_000_000)
DefaultPollInterval = time.Second * 12
DefaultCannonSnapshotFreq = uint(1_000_000_000)
DefaultCannonInfoFreq = uint(10_000_000)
DefaultAsteriscSnapshotFreq = uint(1_000_000_000)
DefaultAsteriscInfoFreq = uint(10_000_000)
// DefaultGameWindow is the default maximum time duration in the past
// that the challenger will look for games to progress.
// The default value is 15 days, which is an 8 day resolution buffer
......@@ -105,8 +119,9 @@ type Config struct {
TraceTypes []TraceType // Type of traces supported
// Specific to the output cannon trace type
RollupRpc string
RollupRpc string // L2 Rollup RPC Url
L2Rpc string // L2 RPC Url
// Specific to the cannon trace provider
CannonBin string // Path to the cannon executable to run when generating trace data
......@@ -115,9 +130,18 @@ type Config struct {
CannonNetwork string
CannonRollupConfigPath string
CannonL2GenesisPath string
CannonL2 string // L2 RPC Url
CannonSnapshotFreq uint // Frequency of snapshots to create when executing cannon (in VM instructions)
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
AsteriscBin string // Path to the asterisc executable to run when generating trace data
AsteriscServer string // Path to the op-program executable that provides the pre-image oracle server
AsteriscAbsolutePreState string // File to load the absolute pre-state for Asterisc traces from
AsteriscNetwork string
AsteriscRollupConfigPath string
AsteriscL2GenesisPath string
AsteriscSnapshotFreq uint // Frequency of snapshots to create when executing asterisc (in VM instructions)
AsteriscInfoFreq uint // Frequency of asterisc progress log messages (in VM instructions)
MaxPendingTx uint64 // Maximum number of pending transactions (0 == no limit)
......@@ -150,9 +174,11 @@ func NewConfig(
Datadir: datadir,
CannonSnapshotFreq: DefaultCannonSnapshotFreq,
CannonInfoFreq: DefaultCannonInfoFreq,
GameWindow: DefaultGameWindow,
CannonSnapshotFreq: DefaultCannonSnapshotFreq,
CannonInfoFreq: DefaultCannonInfoFreq,
AsteriscSnapshotFreq: DefaultAsteriscSnapshotFreq,
AsteriscInfoFreq: DefaultAsteriscInfoFreq,
GameWindow: DefaultGameWindow,
}
}
......@@ -210,8 +236,8 @@ func (c Config) Check() error {
if c.CannonAbsolutePreState == "" {
return ErrMissingCannonAbsolutePreState
}
if c.CannonL2 == "" {
return ErrMissingCannonL2
if c.L2Rpc == "" {
return ErrMissingL2Rpc
}
if c.CannonSnapshotFreq == 0 {
return ErrMissingCannonSnapshotFreq
......@@ -220,6 +246,44 @@ func (c Config) Check() error {
return ErrMissingCannonInfoFreq
}
}
if c.TraceTypeEnabled(TraceTypeAsterisc) {
if c.AsteriscBin == "" {
return ErrMissingAsteriscBin
}
if c.AsteriscServer == "" {
return ErrMissingAsteriscServer
}
if c.AsteriscNetwork == "" {
if c.AsteriscRollupConfigPath == "" {
return ErrMissingAsteriscRollupConfig
}
if c.AsteriscL2GenesisPath == "" {
return ErrMissingAsteriscL2Genesis
}
} else {
if c.AsteriscRollupConfigPath != "" {
return ErrAsteriscNetworkAndRollupConfig
}
if c.AsteriscL2GenesisPath != "" {
return ErrAsteriscNetworkAndL2Genesis
}
if ch := chaincfg.ChainByName(c.AsteriscNetwork); ch == nil {
return fmt.Errorf("%w: %v", ErrAsteriscNetworkUnknown, c.AsteriscNetwork)
}
}
if c.AsteriscAbsolutePreState == "" {
return ErrMissingAsteriscAbsolutePreState
}
if c.L2Rpc == "" {
return ErrMissingL2Rpc
}
if c.AsteriscSnapshotFreq == 0 {
return ErrMissingAsteriscSnapshotFreq
}
if c.AsteriscInfoFreq == 0 {
return ErrMissingAsteriscInfoFreq
}
}
if err := c.TxMgrConfig.Check(); err != nil {
return err
}
......
......@@ -20,20 +20,40 @@ var (
validCannonNetwork = "mainnet"
validCannonAbsolutPreState = "pre.json"
validDatadir = "/tmp/data"
validCannonL2 = "http://localhost:9545"
validL2Rpc = "http://localhost:9545"
validRollupRpc = "http://localhost:8555"
validAsteriscBin = "./bin/asterisc"
validAsteriscOpProgramBin = "./bin/op-program"
validAsteriscNetwork = "mainnet"
validAsteriscAbsolutPreState = "pre.json"
)
var cannonTraceTypes = []TraceType{TraceTypeCannon, TraceTypePermissioned}
func applyValidConfigForCannon(cfg *Config) {
cfg.CannonBin = validCannonBin
cfg.CannonServer = validCannonOpProgramBin
cfg.CannonAbsolutePreState = validCannonAbsolutPreState
cfg.CannonNetwork = validCannonNetwork
cfg.L2Rpc = validL2Rpc
}
func applyValidConfigForAsterisc(cfg *Config) {
cfg.AsteriscBin = validAsteriscBin
cfg.AsteriscServer = validAsteriscOpProgramBin
cfg.AsteriscAbsolutePreState = validAsteriscAbsolutPreState
cfg.AsteriscNetwork = validAsteriscNetwork
cfg.L2Rpc = validL2Rpc
}
func validConfig(traceType TraceType) Config {
cfg := NewConfig(validGameFactoryAddress, validL1EthRpc, validL1BeaconUrl, validDatadir, traceType)
if traceType == TraceTypeCannon || traceType == TraceTypePermissioned {
cfg.CannonBin = validCannonBin
cfg.CannonServer = validCannonOpProgramBin
cfg.CannonAbsolutePreState = validCannonAbsolutPreState
cfg.CannonL2 = validCannonL2
cfg.CannonNetwork = validCannonNetwork
applyValidConfigForCannon(&cfg)
}
if traceType == TraceTypeAsterisc {
applyValidConfigForAsterisc(&cfg)
}
cfg.RollupRpc = validRollupRpc
return cfg
......@@ -110,10 +130,10 @@ func TestCannonRequiredArgs(t *testing.T) {
require.ErrorIs(t, config.Check(), ErrMissingCannonAbsolutePreState)
})
t.Run(fmt.Sprintf("TestCannonL2Required-%v", traceType), func(t *testing.T) {
t.Run(fmt.Sprintf("TestL2RpcRequired-%v", traceType), func(t *testing.T) {
config := validConfig(traceType)
config.CannonL2 = ""
require.ErrorIs(t, config.Check(), ErrMissingCannonL2)
config.L2Rpc = ""
require.ErrorIs(t, config.Check(), ErrMissingL2Rpc)
})
t.Run(fmt.Sprintf("TestCannonSnapshotFreq-%v", traceType), func(t *testing.T) {
......@@ -209,7 +229,7 @@ func TestRollupRpcRequired(t *testing.T) {
}
}
func TestRequireConfigForMultipleTraceTypes(t *testing.T) {
func TestRequireConfigForMultipleTraceTypesForCannon(t *testing.T) {
cfg := validConfig(TraceTypeCannon)
cfg.TraceTypes = []TraceType{TraceTypeCannon, TraceTypeAlphabet}
// Set all required options and check its valid
......@@ -217,11 +237,56 @@ func TestRequireConfigForMultipleTraceTypes(t *testing.T) {
require.NoError(t, cfg.Check())
// Require cannon specific args
cfg.CannonL2 = ""
require.ErrorIs(t, cfg.Check(), ErrMissingCannonL2)
cfg.CannonL2 = validCannonL2
cfg.CannonAbsolutePreState = ""
require.ErrorIs(t, cfg.Check(), ErrMissingCannonAbsolutePreState)
cfg.CannonAbsolutePreState = validCannonAbsolutPreState
// Require output cannon specific args
cfg.RollupRpc = ""
require.ErrorIs(t, cfg.Check(), ErrMissingRollupRpc)
}
func TestRequireConfigForMultipleTraceTypesForAsterisc(t *testing.T) {
cfg := validConfig(TraceTypeAsterisc)
cfg.TraceTypes = []TraceType{TraceTypeAsterisc, TraceTypeAlphabet}
// Set all required options and check its valid
cfg.RollupRpc = validRollupRpc
require.NoError(t, cfg.Check())
// Require asterisc specific args
cfg.AsteriscAbsolutePreState = ""
require.ErrorIs(t, cfg.Check(), ErrMissingAsteriscAbsolutePreState)
cfg.AsteriscAbsolutePreState = validAsteriscAbsolutPreState
// Require output asterisc specific args
cfg.RollupRpc = ""
require.ErrorIs(t, cfg.Check(), ErrMissingRollupRpc)
}
func TestRequireConfigForMultipleTraceTypesForCannonAndAsterisc(t *testing.T) {
cfg := validConfig(TraceTypeCannon)
applyValidConfigForAsterisc(&cfg)
cfg.TraceTypes = []TraceType{TraceTypeCannon, TraceTypeAsterisc, TraceTypeAlphabet}
// Set all required options and check its valid
cfg.RollupRpc = validRollupRpc
require.NoError(t, cfg.Check())
// Require cannon specific args
cfg.CannonBin = ""
require.ErrorIs(t, cfg.Check(), ErrMissingCannonBin)
cfg.CannonBin = validCannonBin
// Require asterisc specific args
cfg.AsteriscAbsolutePreState = ""
require.ErrorIs(t, cfg.Check(), ErrMissingAsteriscAbsolutePreState)
cfg.AsteriscAbsolutePreState = validAsteriscAbsolutPreState
// Require cannon specific args
cfg.AsteriscServer = ""
require.ErrorIs(t, cfg.Check(), ErrMissingAsteriscServer)
cfg.AsteriscServer = validAsteriscOpProgramBin
// Check final config is valid
require.NoError(t, cfg.Check())
}
......@@ -71,6 +71,11 @@ var (
EnvVars: prefixEnvVars("MAX_CONCURRENCY"),
Value: uint(runtime.NumCPU()),
}
L2RpcFlag = &cli.StringFlag{
Name: "l2-rpc",
Usage: "L2 Address of L2 JSON-RPC endpoint to use (eth and debug namespace required) (cannon/asterisc trace type only)",
EnvVars: prefixEnvVars("L2_RPC"),
}
MaxPendingTransactionsFlag = &cli.Uint64Flag{
Name: "max-pending-tx",
Usage: "The maximum number of pending transactions. 0 for no limit.",
......@@ -123,7 +128,7 @@ var (
}
CannonL2Flag = &cli.StringFlag{
Name: "cannon-l2",
Usage: "L2 Address of L2 JSON-RPC endpoint to use (eth and debug namespace required) (cannon trace type only)",
Usage: fmt.Sprintf("Deprecated: Use %v instead", L2RpcFlag.Name),
EnvVars: prefixEnvVars("CANNON_L2"),
}
CannonSnapshotFreqFlag = &cli.UintFlag{
......@@ -138,6 +143,51 @@ var (
EnvVars: prefixEnvVars("CANNON_INFO_FREQ"),
Value: config.DefaultCannonInfoFreq,
}
AsteriscNetworkFlag = &cli.StringFlag{
Name: "asterisc-network",
Usage: fmt.Sprintf(
"Predefined network selection. Available networks: %s (asterisc trace type only)",
strings.Join(chaincfg.AvailableNetworks(), ", "),
),
EnvVars: prefixEnvVars("ASTERISC_NETWORK"),
}
AsteriscRollupConfigFlag = &cli.StringFlag{
Name: "asterisc-rollup-config",
Usage: "Rollup chain parameters (asterisc trace type only)",
EnvVars: prefixEnvVars("ASTERISC_ROLLUP_CONFIG"),
}
AsteriscL2GenesisFlag = &cli.StringFlag{
Name: "asterisc-l2-genesis",
Usage: "Path to the op-geth genesis file (asterisc trace type only)",
EnvVars: prefixEnvVars("ASTERISC_L2_GENESIS"),
}
AsteriscBinFlag = &cli.StringFlag{
Name: "asterisc-bin",
Usage: "Path to asterisc executable to use when generating trace data (asterisc trace type only)",
EnvVars: prefixEnvVars("ASTERISC_BIN"),
}
AsteriscServerFlag = &cli.StringFlag{
Name: "asterisc-server",
Usage: "Path to executable to use as pre-image oracle server when generating trace data (asterisc trace type only)",
EnvVars: prefixEnvVars("ASTERISC_SERVER"),
}
AsteriscPreStateFlag = &cli.StringFlag{
Name: "asterisc-prestate",
Usage: "Path to absolute prestate to use when generating trace data (asterisc trace type only)",
EnvVars: prefixEnvVars("ASTERISC_PRESTATE"),
}
AsteriscSnapshotFreqFlag = &cli.UintFlag{
Name: "asterisc-snapshot-freq",
Usage: "Frequency of asterisc snapshots to generate in VM steps (asterisc trace type only)",
EnvVars: prefixEnvVars("ASTERISC_SNAPSHOT_FREQ"),
Value: config.DefaultAsteriscSnapshotFreq,
}
AsteriscInfoFreqFlag = &cli.UintFlag{
Name: "asterisc-info-freq",
Usage: "Frequency of asterisc info log messages to generate in VM steps (asterisc trace type only)",
EnvVars: prefixEnvVars("ASTERISC_INFO_FREQ"),
Value: config.DefaultAsteriscInfoFreq,
}
GameWindowFlag = &cli.DurationFlag{
Name: "game-window",
Usage: "The time window which the challenger will look for games to progress and claim bonds. " +
......@@ -171,6 +221,7 @@ var requiredFlags = []cli.Flag{
var optionalFlags = []cli.Flag{
TraceTypeFlag,
MaxConcurrencyFlag,
L2RpcFlag,
MaxPendingTransactionsFlag,
HTTPPollInterval,
AdditionalBondClaimants,
......@@ -184,6 +235,14 @@ var optionalFlags = []cli.Flag{
CannonL2Flag,
CannonSnapshotFreqFlag,
CannonInfoFreqFlag,
AsteriscNetworkFlag,
AsteriscRollupConfigFlag,
AsteriscL2GenesisFlag,
AsteriscBinFlag,
AsteriscServerFlag,
AsteriscPreStateFlag,
AsteriscSnapshotFreqFlag,
AsteriscInfoFreqFlag,
GameWindowFlag,
SelectiveClaimResolutionFlag,
UnsafeAllowInvalidPrestate,
......@@ -221,8 +280,36 @@ func CheckCannonFlags(ctx *cli.Context) error {
if !ctx.IsSet(CannonPreStateFlag.Name) {
return fmt.Errorf("flag %s is required", CannonPreStateFlag.Name)
}
if !ctx.IsSet(CannonL2Flag.Name) {
return fmt.Errorf("flag %s is required", CannonL2Flag.Name)
// CannonL2Flag is checked because it is an alias with L2RpcFlag
if !ctx.IsSet(CannonL2Flag.Name) && !ctx.IsSet(L2RpcFlag.Name) {
return fmt.Errorf("flag %s is required", L2RpcFlag.Name)
}
return nil
}
func CheckAsteriscFlags(ctx *cli.Context) error {
if !ctx.IsSet(AsteriscNetworkFlag.Name) &&
!(ctx.IsSet(AsteriscRollupConfigFlag.Name) && ctx.IsSet(AsteriscL2GenesisFlag.Name)) {
return fmt.Errorf("flag %v or %v and %v is required",
AsteriscNetworkFlag.Name, AsteriscRollupConfigFlag.Name, AsteriscL2GenesisFlag.Name)
}
if ctx.IsSet(AsteriscNetworkFlag.Name) &&
(ctx.IsSet(AsteriscRollupConfigFlag.Name) || ctx.IsSet(AsteriscL2GenesisFlag.Name)) {
return fmt.Errorf("flag %v can not be used with %v and %v",
AsteriscNetworkFlag.Name, AsteriscRollupConfigFlag.Name, AsteriscL2GenesisFlag.Name)
}
if !ctx.IsSet(AsteriscBinFlag.Name) {
return fmt.Errorf("flag %s is required", AsteriscBinFlag.Name)
}
if !ctx.IsSet(AsteriscServerFlag.Name) {
return fmt.Errorf("flag %s is required", AsteriscServerFlag.Name)
}
if !ctx.IsSet(AsteriscPreStateFlag.Name) {
return fmt.Errorf("flag %s is required", AsteriscPreStateFlag.Name)
}
// CannonL2Flag is checked because it is an alias with L2RpcFlag
if !ctx.IsSet(CannonL2Flag.Name) && !ctx.IsSet(L2RpcFlag.Name) {
return fmt.Errorf("flag %s is required", L2RpcFlag.Name)
}
return nil
}
......@@ -239,6 +326,10 @@ func CheckRequired(ctx *cli.Context, traceTypes []config.TraceType) error {
if err := CheckCannonFlags(ctx); err != nil {
return err
}
case config.TraceTypeAsterisc:
if err := CheckAsteriscFlags(ctx); err != nil {
return err
}
case config.TraceTypeAlphabet:
default:
return fmt.Errorf("invalid trace type. must be one of %v", config.TraceTypes)
......@@ -261,6 +352,20 @@ func parseTraceTypes(ctx *cli.Context) ([]config.TraceType, error) {
return traceTypes, nil
}
func getL2Rpc(ctx *cli.Context) (string, error) {
if ctx.IsSet(CannonL2Flag.Name) && ctx.IsSet(L2RpcFlag.Name) {
return "", fmt.Errorf("flag %v and %v must not be both set", CannonL2Flag.Name, L2RpcFlag.Name)
}
l2Rpc := ""
if ctx.IsSet(CannonL2Flag.Name) {
l2Rpc = ctx.String(CannonL2Flag.Name)
}
if ctx.IsSet(L2RpcFlag.Name) {
l2Rpc = ctx.String(L2RpcFlag.Name)
}
return l2Rpc, nil
}
// NewConfigFromCLI parses the Config from the provided flags or environment variables.
func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
traceTypes, err := parseTraceTypes(ctx)
......@@ -303,6 +408,10 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
claimants = append(claimants, claimant)
}
}
l2Rpc, err := getL2Rpc(ctx)
if err != nil {
return nil, err
}
return &config.Config{
// Required Flags
L1EthRpc: ctx.String(L1EthRpcFlag.Name),
......@@ -312,6 +421,7 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
GameAllowlist: allowedGames,
GameWindow: ctx.Duration(GameWindowFlag.Name),
MaxConcurrency: maxConcurrency,
L2Rpc: l2Rpc,
MaxPendingTx: ctx.Uint64(MaxPendingTransactionsFlag.Name),
PollInterval: ctx.Duration(HTTPPollInterval.Name),
AdditionalBondClaimants: claimants,
......@@ -323,9 +433,16 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
CannonServer: ctx.String(CannonServerFlag.Name),
CannonAbsolutePreState: ctx.String(CannonPreStateFlag.Name),
Datadir: ctx.String(DatadirFlag.Name),
CannonL2: ctx.String(CannonL2Flag.Name),
CannonSnapshotFreq: ctx.Uint(CannonSnapshotFreqFlag.Name),
CannonInfoFreq: ctx.Uint(CannonInfoFreqFlag.Name),
AsteriscNetwork: ctx.String(AsteriscNetworkFlag.Name),
AsteriscRollupConfigPath: ctx.String(AsteriscRollupConfigFlag.Name),
AsteriscL2GenesisPath: ctx.String(AsteriscL2GenesisFlag.Name),
AsteriscBin: ctx.String(AsteriscBinFlag.Name),
AsteriscServer: ctx.String(AsteriscServerFlag.Name),
AsteriscAbsolutePreState: ctx.String(AsteriscPreStateFlag.Name),
AsteriscSnapshotFreq: ctx.Uint(AsteriscSnapshotFreqFlag.Name),
AsteriscInfoFreq: ctx.Uint(AsteriscInfoFreqFlag.Name),
TxMgrConfig: txMgrConfig,
MetricsConfig: metricsConfig,
PprofConfig: pprofConfig,
......
......@@ -8,8 +8,10 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/claims"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/alphabet"
"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/outputs"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/scheduler"
......@@ -58,10 +60,10 @@ func RegisterGameTypes(
) (CloseFunc, error) {
var closer CloseFunc
var l2Client *ethclient.Client
if cfg.TraceTypeEnabled(config.TraceTypeCannon) || cfg.TraceTypeEnabled(config.TraceTypePermissioned) {
l2, err := ethclient.DialContext(ctx, cfg.CannonL2)
if cfg.TraceTypeEnabled(config.TraceTypeCannon) || cfg.TraceTypeEnabled(config.TraceTypePermissioned) || cfg.TraceTypeEnabled(config.TraceTypeAsterisc) {
l2, err := ethclient.DialContext(ctx, cfg.L2Rpc)
if err != nil {
return nil, fmt.Errorf("dial l2 client %v: %w", cfg.CannonL2, err)
return nil, fmt.Errorf("dial l2 client %v: %w", cfg.L2Rpc, err)
}
l2Client = l2
closer = l2Client.Close
......@@ -78,6 +80,11 @@ func RegisterGameTypes(
return nil, fmt.Errorf("failed to register permissioned cannon game type: %w", err)
}
}
if cfg.TraceTypeEnabled(config.TraceTypeAsterisc) {
if err := registerAsterisc(faultTypes.AsteriscGameType, registry, oracles, ctx, systemClock, l1Clock, logger, m, cfg, syncValidator, rollupClient, txSender, gameFactory, caller, l2Client, l1HeaderSource, selective, claimants); err != nil {
return nil, fmt.Errorf("failed to register asterisc game type: %w", err)
}
}
if cfg.TraceTypeEnabled(config.TraceTypeAlphabet) {
if err := registerAlphabet(registry, oracles, ctx, systemClock, l1Clock, logger, m, syncValidator, rollupClient, txSender, gameFactory, caller, l1HeaderSource, selective, claimants); err != nil {
return nil, fmt.Errorf("failed to register alphabet game type: %w", err)
......@@ -167,6 +174,74 @@ func registerOracle(ctx context.Context, m metrics.Metricer, oracles OracleRegis
return nil
}
func registerAsterisc(
gameType uint32,
registry Registry,
oracles OracleRegistry,
ctx context.Context,
systemClock clock.Clock,
l1Clock faultTypes.ClockReader,
logger log.Logger,
m metrics.Metricer,
cfg *config.Config,
syncValidator SyncValidator,
rollupClient outputs.OutputRollupClient,
txSender TxSender,
gameFactory *contracts.DisputeGameFactoryContract,
caller *batching.MultiCaller,
l2Client utils.L2HeaderSource,
l1HeaderSource L1HeaderSource,
selective bool,
claimants []common.Address,
) error {
asteriscPrestateProvider := asterisc.NewPrestateProvider(cfg.AsteriscAbsolutePreState)
playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
contract, err := contracts.NewFaultDisputeGameContract(m, game.Proxy, caller)
if err != nil {
return nil, err
}
oracle, err := contract.GetOracle(ctx)
if err != nil {
return nil, fmt.Errorf("failed to load oracle for game %v: %w", game.Proxy, err)
}
oracles.RegisterOracle(oracle)
prestateBlock, poststateBlock, err := contract.GetBlockRange(ctx)
if err != nil {
return nil, err
}
splitDepth, err := contract.GetSplitDepth(ctx)
if err != nil {
return nil, fmt.Errorf("failed to load split depth: %w", err)
}
l1HeadID, err := loadL1Head(contract, ctx, l1HeaderSource)
if err != nil {
return nil, err
}
prestateProvider := outputs.NewPrestateProvider(rollupClient, prestateBlock)
creator := func(ctx context.Context, logger log.Logger, gameDepth faultTypes.Depth, dir string) (faultTypes.TraceAccessor, error) {
accessor, err := outputs.NewOutputAsteriscTraceAccessor(logger, m, cfg, l2Client, prestateProvider, rollupClient, dir, l1HeadID, splitDepth, prestateBlock, poststateBlock)
if err != nil {
return nil, err
}
return accessor, nil
}
prestateValidator := NewPrestateValidator("asterisc", contract.GetAbsolutePrestateHash, asteriscPrestateProvider)
genesisValidator := NewPrestateValidator("output root", contract.GetStartingRootHash, prestateProvider)
return NewGamePlayer(ctx, systemClock, l1Clock, logger, m, dir, game.Proxy, txSender, contract, syncValidator, []Validator{prestateValidator, genesisValidator}, creator, l1HeaderSource, selective, claimants)
}
err := registerOracle(ctx, m, oracles, gameFactory, caller, gameType)
if err != nil {
return err
}
registry.RegisterGameType(gameType, playerCreator)
contractCreator := func(game types.GameMetadata) (claims.BondContract, error) {
return contracts.NewFaultDisputeGameContract(m, game.Proxy, caller)
}
registry.RegisterBondContract(gameType, contractCreator)
return nil
}
func registerCannon(
gameType uint32,
registry Registry,
......@@ -182,7 +257,7 @@ func registerCannon(
txSender TxSender,
gameFactory *contracts.DisputeGameFactoryContract,
caller *batching.MultiCaller,
l2Client cannon.L2HeaderSource,
l2Client utils.L2HeaderSource,
l1HeaderSource L1HeaderSource,
selective bool,
claimants []common.Address,
......
package asterisc
import (
"context"
"fmt"
"math"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
"github.com/ethereum/go-ethereum/log"
)
type Executor struct {
logger log.Logger
metrics AsteriscMetricer
l1 string
l1Beacon string
l2 string
inputs utils.LocalGameInputs
asterisc string
server string
network string
rollupConfig string
l2Genesis string
absolutePreState string
snapshotFreq uint
infoFreq uint
selectSnapshot utils.SnapshotSelect
cmdExecutor utils.CmdExecutor
}
func NewExecutor(logger log.Logger, m AsteriscMetricer, cfg *config.Config, inputs utils.LocalGameInputs) *Executor {
return &Executor{
logger: logger,
metrics: m,
l1: cfg.L1EthRpc,
l1Beacon: cfg.L1Beacon,
l2: cfg.L2Rpc,
inputs: inputs,
asterisc: cfg.AsteriscBin,
server: cfg.AsteriscServer,
network: cfg.AsteriscNetwork,
rollupConfig: cfg.AsteriscRollupConfigPath,
l2Genesis: cfg.AsteriscL2GenesisPath,
absolutePreState: cfg.AsteriscAbsolutePreState,
snapshotFreq: cfg.AsteriscSnapshotFreq,
infoFreq: cfg.AsteriscInfoFreq,
selectSnapshot: utils.FindStartingSnapshot,
cmdExecutor: utils.RunCmd,
}
}
// GenerateProof executes asterisc to generate a proof at the specified trace index.
// The proof is stored at the specified directory.
func (e *Executor) GenerateProof(ctx context.Context, dir string, i uint64) error {
return e.generateProof(ctx, dir, i, i)
}
// generateProof executes asterisc from the specified starting trace index until the end trace index.
// The proof is stored at the specified directory.
func (e *Executor) generateProof(ctx context.Context, dir string, begin uint64, end uint64, extraAsteriscArgs ...string) error {
snapshotDir := filepath.Join(dir, utils.SnapsDir)
start, err := e.selectSnapshot(e.logger, snapshotDir, e.absolutePreState, begin)
if err != nil {
return fmt.Errorf("find starting snapshot: %w", err)
}
proofDir := filepath.Join(dir, proofsDir)
dataDir := utils.PreimageDir(dir)
lastGeneratedState := filepath.Join(dir, utils.FinalState)
args := []string{
"run",
"--input", start,
"--output", lastGeneratedState,
"--meta", "",
"--info-at", "%" + strconv.FormatUint(uint64(e.infoFreq), 10),
"--proof-at", "=" + strconv.FormatUint(end, 10),
"--proof-fmt", filepath.Join(proofDir, "%d.json.gz"),
"--snapshot-at", "%" + strconv.FormatUint(uint64(e.snapshotFreq), 10),
"--snapshot-fmt", filepath.Join(snapshotDir, "%d.json.gz"),
}
if end < math.MaxUint64 {
args = append(args, "--stop-at", "="+strconv.FormatUint(end+1, 10))
}
args = append(args, extraAsteriscArgs...)
args = append(args,
"--",
e.server, "--server",
"--l1", e.l1,
"--l1.beacon", e.l1Beacon,
"--l2", e.l2,
"--datadir", dataDir,
"--l1.head", e.inputs.L1Head.Hex(),
"--l2.head", e.inputs.L2Head.Hex(),
"--l2.outputroot", e.inputs.L2OutputRoot.Hex(),
"--l2.claim", e.inputs.L2Claim.Hex(),
"--l2.blocknumber", e.inputs.L2BlockNumber.Text(10),
)
if e.network != "" {
args = append(args, "--network", e.network)
}
if e.rollupConfig != "" {
args = append(args, "--rollup.config", e.rollupConfig)
}
if e.l2Genesis != "" {
args = append(args, "--l2.genesis", e.l2Genesis)
}
if err := os.MkdirAll(snapshotDir, 0755); err != nil {
return fmt.Errorf("could not create snapshot directory %v: %w", snapshotDir, err)
}
if err := os.MkdirAll(dataDir, 0755); err != nil {
return fmt.Errorf("could not create preimage cache directory %v: %w", dataDir, err)
}
if err := os.MkdirAll(proofDir, 0755); err != nil {
return fmt.Errorf("could not create proofs directory %v: %w", proofDir, err)
}
e.logger.Info("Generating trace", "proof", end, "cmd", e.asterisc, "args", strings.Join(args, ", "))
execStart := time.Now()
err = e.cmdExecutor(ctx, e.logger.New("proof", end), e.asterisc, args...)
e.metrics.RecordAsteriscExecutionTime(time.Since(execStart).Seconds())
return err
}
package asterisc
import (
"context"
"math"
"math/big"
"path/filepath"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
func TestGenerateProof(t *testing.T) {
input := "starting.json"
tempDir := t.TempDir()
dir := filepath.Join(tempDir, "gameDir")
cfg := config.NewConfig(common.Address{0xbb}, "http://localhost:8888", "http://localhost:9000", tempDir, config.TraceTypeAsterisc)
cfg.L2Rpc = "http://localhost:9999"
cfg.AsteriscAbsolutePreState = "pre.json"
cfg.AsteriscBin = "./bin/asterisc"
cfg.AsteriscServer = "./bin/op-program"
cfg.AsteriscSnapshotFreq = 500
cfg.AsteriscInfoFreq = 900
inputs := utils.LocalGameInputs{
L1Head: common.Hash{0x11},
L2Head: common.Hash{0x22},
L2OutputRoot: common.Hash{0x33},
L2Claim: common.Hash{0x44},
L2BlockNumber: big.NewInt(3333),
}
captureExec := func(t *testing.T, cfg config.Config, proofAt uint64) (string, string, map[string]string) {
m := &asteriscDurationMetrics{}
executor := NewExecutor(testlog.Logger(t, log.LevelInfo), m, &cfg, inputs)
executor.selectSnapshot = func(logger log.Logger, dir string, absolutePreState string, i uint64) (string, error) {
return input, nil
}
var binary string
var subcommand string
args := make(map[string]string)
executor.cmdExecutor = func(ctx context.Context, l log.Logger, b string, a ...string) error {
binary = b
subcommand = a[0]
for i := 1; i < len(a); {
if a[i] == "--" {
// Skip over the divider between asterisc and server program
i += 1
continue
}
args[a[i]] = a[i+1]
i += 2
}
return nil
}
err := executor.GenerateProof(context.Background(), dir, proofAt)
require.NoError(t, err)
require.Equal(t, 1, m.executionTimeRecordCount, "Should record asterisc execution time")
return binary, subcommand, args
}
t.Run("Network", func(t *testing.T) {
cfg.AsteriscNetwork = "mainnet"
cfg.AsteriscRollupConfigPath = ""
cfg.AsteriscL2GenesisPath = ""
binary, subcommand, args := captureExec(t, cfg, 150_000_000)
require.DirExists(t, filepath.Join(dir, utils.PreimagesDir))
require.DirExists(t, filepath.Join(dir, proofsDir))
require.DirExists(t, filepath.Join(dir, utils.SnapsDir))
require.Equal(t, cfg.AsteriscBin, binary)
require.Equal(t, "run", subcommand)
require.Equal(t, input, args["--input"])
require.Contains(t, args, "--meta")
require.Equal(t, "", args["--meta"])
require.Equal(t, filepath.Join(dir, utils.FinalState), args["--output"])
require.Equal(t, "=150000000", args["--proof-at"])
require.Equal(t, "=150000001", args["--stop-at"])
require.Equal(t, "%500", args["--snapshot-at"])
require.Equal(t, "%900", args["--info-at"])
// Slight quirk of how we pair off args
// The server binary winds up as the key and the first arg --server as the value which has no value
// Then everything else pairs off correctly again
require.Equal(t, "--server", args[cfg.AsteriscServer])
require.Equal(t, cfg.L1EthRpc, args["--l1"])
require.Equal(t, cfg.L1Beacon, args["--l1.beacon"])
require.Equal(t, cfg.L2Rpc, args["--l2"])
require.Equal(t, filepath.Join(dir, utils.PreimagesDir), args["--datadir"])
require.Equal(t, filepath.Join(dir, proofsDir, "%d.json.gz"), args["--proof-fmt"])
require.Equal(t, filepath.Join(dir, utils.SnapsDir, "%d.json.gz"), args["--snapshot-fmt"])
require.Equal(t, cfg.AsteriscNetwork, args["--network"])
require.NotContains(t, args, "--rollup.config")
require.NotContains(t, args, "--l2.genesis")
// Local game inputs
require.Equal(t, inputs.L1Head.Hex(), args["--l1.head"])
require.Equal(t, inputs.L2Head.Hex(), args["--l2.head"])
require.Equal(t, inputs.L2OutputRoot.Hex(), args["--l2.outputroot"])
require.Equal(t, inputs.L2Claim.Hex(), args["--l2.claim"])
require.Equal(t, "3333", args["--l2.blocknumber"])
})
t.Run("RollupAndGenesis", func(t *testing.T) {
cfg.AsteriscNetwork = ""
cfg.AsteriscRollupConfigPath = "rollup.json"
cfg.AsteriscL2GenesisPath = "genesis.json"
_, _, args := captureExec(t, cfg, 150_000_000)
require.NotContains(t, args, "--network")
require.Equal(t, cfg.AsteriscRollupConfigPath, args["--rollup.config"])
require.Equal(t, cfg.AsteriscL2GenesisPath, args["--l2.genesis"])
})
t.Run("NoStopAtWhenProofIsMaxUInt", func(t *testing.T) {
cfg.AsteriscNetwork = "mainnet"
cfg.AsteriscRollupConfigPath = "rollup.json"
cfg.AsteriscL2GenesisPath = "genesis.json"
_, _, args := captureExec(t, cfg, math.MaxUint64)
// 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 asterisc to execute until the program exits.
require.NotContains(t, args, "--stop-at")
})
}
type asteriscDurationMetrics struct {
metrics.NoopMetricsImpl
executionTimeRecordCount int
}
func (c *asteriscDurationMetrics) RecordAsteriscExecutionTime(_ float64) {
c.executionTimeRecordCount++
}
package asterisc
import (
"context"
"fmt"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum/go-ethereum/common"
)
var _ types.PrestateProvider = (*AsteriscPreStateProvider)(nil)
type AsteriscPreStateProvider struct {
prestate string
prestateCommitment common.Hash
}
func NewPrestateProvider(prestate string) *AsteriscPreStateProvider {
return &AsteriscPreStateProvider{prestate: prestate}
}
func (p *AsteriscPreStateProvider) absolutePreState() (*VMState, error) {
state, err := parseState(p.prestate)
if err != nil {
return nil, fmt.Errorf("cannot load absolute pre-state: %w", err)
}
return state, nil
}
func (p *AsteriscPreStateProvider) AbsolutePreStateCommitment(_ context.Context) (common.Hash, error) {
if p.prestateCommitment != (common.Hash{}) {
return p.prestateCommitment, nil
}
state, err := p.absolutePreState()
if err != nil {
return common.Hash{}, fmt.Errorf("cannot load absolute pre-state: %w", err)
}
p.prestateCommitment = state.StateHash
return state.StateHash, nil
}
package asterisc
import (
"context"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
)
func newAsteriscPrestateProvider(dataDir string, prestate string) *AsteriscPreStateProvider {
return &AsteriscPreStateProvider{
prestate: filepath.Join(dataDir, prestate),
}
}
func TestAbsolutePreStateCommitment(t *testing.T) {
dataDir := t.TempDir()
prestate := "state.json"
t.Run("StateUnavailable", func(t *testing.T) {
provider := newAsteriscPrestateProvider("/dir/does/not/exist", prestate)
_, err := provider.AbsolutePreStateCommitment(context.Background())
require.ErrorIs(t, err, os.ErrNotExist)
})
t.Run("InvalidStateFile", func(t *testing.T) {
setupPreState(t, dataDir, "invalid.json")
provider := newAsteriscPrestateProvider(dataDir, prestate)
_, err := provider.AbsolutePreStateCommitment(context.Background())
require.ErrorContains(t, err, "invalid asterisc VM state")
})
t.Run("CacheAbsolutePreState", func(t *testing.T) {
setupPreState(t, dataDir, prestate)
provider := newAsteriscPrestateProvider(dataDir, prestate)
first, err := provider.AbsolutePreStateCommitment(context.Background())
require.NoError(t, err)
// Remove the prestate from disk
require.NoError(t, os.Remove(provider.prestate))
// Value should still be available from cache
cached, err := provider.AbsolutePreStateCommitment(context.Background())
require.NoError(t, err)
require.Equal(t, first, cached)
})
}
func setupPreState(t *testing.T, dataDir string, filename string) {
srcDir := filepath.Join("test_data")
path := filepath.Join(srcDir, filename)
file, err := testData.ReadFile(path)
require.NoErrorf(t, err, "reading %v", path)
err = os.WriteFile(filepath.Join(dataDir, "state.json"), file, 0o644)
require.NoErrorf(t, err, "writing %v", path)
}
package asterisc
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"math"
"os"
"path/filepath"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-program/host/kvstore"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
const (
proofsDir = "proofs"
diskStateCache = "state.json.gz"
)
type AsteriscMetricer interface {
RecordAsteriscExecutionTime(t float64)
}
type AsteriscTraceProvider struct {
logger log.Logger
dir string
prestate string
generator utils.ProofGenerator
gameDepth types.Depth
preimageLoader *utils.PreimageLoader
// 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(logger log.Logger, m AsteriscMetricer, cfg *config.Config, localInputs utils.LocalGameInputs, dir string, gameDepth types.Depth) *AsteriscTraceProvider {
return &AsteriscTraceProvider{
logger: logger,
dir: dir,
prestate: cfg.AsteriscAbsolutePreState,
generator: NewExecutor(logger, m, cfg, localInputs),
gameDepth: gameDepth,
preimageLoader: utils.NewPreimageLoader(kvstore.NewDiskKV(utils.PreimageDir(dir)).Get),
}
}
func (p *AsteriscTraceProvider) Get(ctx context.Context, pos types.Position) (common.Hash, error) {
traceIndex := pos.TraceIndex(p.gameDepth)
if !traceIndex.IsUint64() {
return common.Hash{}, errors.New("trace index out of bounds")
}
proof, err := p.loadProof(ctx, traceIndex.Uint64())
if err != nil {
return common.Hash{}, err
}
value := proof.ClaimValue
if value == (common.Hash{}) {
return common.Hash{}, errors.New("proof missing post hash")
}
return value, nil
}
func (p *AsteriscTraceProvider) GetStepData(ctx context.Context, pos types.Position) ([]byte, []byte, *types.PreimageOracleData, error) {
traceIndex := pos.TraceIndex(p.gameDepth)
if !traceIndex.IsUint64() {
return nil, nil, nil, errors.New("trace index out of bounds")
}
proof, err := p.loadProof(ctx, traceIndex.Uint64())
if err != nil {
return nil, nil, nil, err
}
value := ([]byte)(proof.StateData)
if len(value) == 0 {
return nil, nil, nil, errors.New("proof missing state data")
}
data := ([]byte)(proof.ProofData)
if data == nil {
return nil, nil, nil, errors.New("proof missing proof data")
}
oracleData, err := p.preimageLoader.LoadPreimage(proof)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to load preimage: %w", err)
}
return value, data, oracleData, nil
}
func (p *AsteriscTraceProvider) absolutePreState() (*VMState, error) {
state, err := parseState(p.prestate)
if err != nil {
return nil, fmt.Errorf("cannot load absolute pre-state: %w", err)
}
return state, nil
}
func (p *AsteriscTraceProvider) 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)
}
return state.StateHash, nil
}
// 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.
func (p *AsteriscTraceProvider) loadProof(ctx context.Context, i uint64) (*utils.ProofData, error) {
// Attempt to read the last step from disk cache
if p.lastStep == 0 {
step, err := utils.ReadLastStep(p.dir)
if err != nil {
p.logger.Warn("Failed to read last step from disk cache", "err", err)
} else {
p.lastStep = step
}
}
// If the last step is tracked, set i to the last step to generate or load the final proof
if p.lastStep != 0 && i > p.lastStep {
i = p.lastStep
}
path := filepath.Join(p.dir, proofsDir, fmt.Sprintf("%d.json.gz", i))
file, err := ioutil.OpenDecompressed(path)
if errors.Is(err, os.ErrNotExist) {
if err := p.generator.GenerateProof(ctx, p.dir, i); err != nil {
return nil, fmt.Errorf("generate asterisc trace with proof at %v: %w", i, err)
}
// Try opening the file again now and it should exist.
file, err = ioutil.OpenDecompressed(path)
if errors.Is(err, os.ErrNotExist) {
// Expected proof wasn't generated, check if we reached the end of execution
state, err := p.finalState()
if err != nil {
return nil, 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
// Extend the trace out to the full length using a no-op instruction that doesn't change any state
// No execution is done, so no proof-data or oracle values are required.
proof := &utils.ProofData{
ClaimValue: state.StateHash,
StateData: state.Witness,
ProofData: []byte{},
OracleKey: nil,
OracleValue: nil,
OracleOffset: 0,
}
if err := utils.WriteLastStep(p.dir, proof, p.lastStep); err != nil {
p.logger.Warn("Failed to write last step to disk cache", "step", p.lastStep)
}
return proof, nil
} else {
return 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 {
return nil, fmt.Errorf("cannot open proof file (%v): %w", path, err)
}
defer file.Close()
var proof utils.ProofData
err = json.NewDecoder(file).Decode(&proof)
if err != nil {
return nil, fmt.Errorf("failed to read proof (%v): %w", path, err)
}
return &proof, nil
}
func (c *AsteriscTraceProvider) finalState() (*VMState, error) {
state, err := parseState(filepath.Join(c.dir, utils.FinalState))
if err != nil {
return nil, fmt.Errorf("cannot read final state: %w", err)
}
return state, nil
}
// AsteriscTraceProviderForTest is a AsteriscTraceProvider that can find the step referencing the preimage read
// Only to be used for testing
type AsteriscTraceProviderForTest struct {
*AsteriscTraceProvider
}
func NewTraceProviderForTest(logger log.Logger, m AsteriscMetricer, cfg *config.Config, localInputs utils.LocalGameInputs, dir string, gameDepth types.Depth) *AsteriscTraceProviderForTest {
p := &AsteriscTraceProvider{
logger: logger,
dir: dir,
prestate: cfg.AsteriscAbsolutePreState,
generator: NewExecutor(logger, m, cfg, localInputs),
gameDepth: gameDepth,
preimageLoader: utils.NewPreimageLoader(kvstore.NewDiskKV(utils.PreimageDir(dir)).Get),
}
return &AsteriscTraceProviderForTest{p}
}
func (p *AsteriscTraceProviderForTest) FindStep(ctx context.Context, start uint64, preimage utils.PreimageOpt) (uint64, error) {
// Run asterisc to find the step that meets the preimage conditions
if err := p.generator.(*Executor).generateProof(ctx, p.dir, start, math.MaxUint64, preimage()...); err != nil {
return 0, fmt.Errorf("generate asterisc trace (until preimage read): %w", err)
}
// Load the step from the state asterisc finished with
state, err := p.finalState()
if err != nil {
return 0, fmt.Errorf("failed to load final state: %w", err)
}
// Check we didn't get to the end of the trace without finding the preimage read we were looking for
if state.Exited {
return 0, fmt.Errorf("preimage read not found: %w", io.EOF)
}
// The state is the post-state so the step we want to execute to read the preimage is step - 1.
return state.Step - 1, nil
}
This diff is collapsed.
package asterisc
import (
"encoding/json"
"fmt"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
)
var asteriscWitnessLen = 362
// The state struct will be read from json.
// other fields included in json are specific to FPVM implementation, and not required for trace provider.
type VMState struct {
PC uint64 `json:"pc"`
Exited bool `json:"exited"`
Step uint64 `json:"step"`
Witness []byte `json:"witness"`
StateHash [32]byte `json:"stateHash"`
}
func (state *VMState) validateStateHash() error {
exitCode := state.StateHash[0]
if exitCode >= 4 {
return fmt.Errorf("invalid stateHash: unknown exitCode %d", exitCode)
}
if (state.Exited && exitCode == mipsevm.VMStatusUnfinished) || (!state.Exited && exitCode != mipsevm.VMStatusUnfinished) {
return fmt.Errorf("invalid stateHash: invalid exitCode %d", exitCode)
}
return nil
}
func (state *VMState) validateWitness() error {
witnessLen := len(state.Witness)
if witnessLen != asteriscWitnessLen {
return fmt.Errorf("invalid witness: Length must be 362 but got %d", witnessLen)
}
return nil
}
// validateState performs verification of state; it is not perfect.
// It does not recalculate whether witness nor stateHash is correctly set from state.
func (state *VMState) validateState() error {
if err := state.validateStateHash(); err != nil {
return err
}
if err := state.validateWitness(); err != nil {
return err
}
return nil
}
// parseState parses state from json and goes on state validation
func parseState(path string) (*VMState, error) {
file, err := ioutil.OpenDecompressed(path)
if err != nil {
return nil, fmt.Errorf("cannot open state file (%v): %w", path, err)
}
defer file.Close()
var state VMState
if err := json.NewDecoder(file).Decode(&state); err != nil {
return nil, fmt.Errorf("invalid asterisc VM state (%v): %w", path, err)
}
if err := state.validateState(); err != nil {
return nil, fmt.Errorf("invalid asterisc VM state (%v): %w", path, err)
}
return &state, nil
}
package asterisc
import (
"compress/gzip"
_ "embed"
"encoding/json"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
)
//go:embed test_data/state.json
var testState []byte
func TestLoadState(t *testing.T) {
t.Run("Uncompressed", func(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "state.json")
require.NoError(t, os.WriteFile(path, testState, 0644))
state, err := parseState(path)
require.NoError(t, err)
var expected VMState
require.NoError(t, json.Unmarshal(testState, &expected))
require.Equal(t, &expected, state)
})
t.Run("Gzipped", func(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "state.json.gz")
f, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
require.NoError(t, err)
defer f.Close()
writer := gzip.NewWriter(f)
_, err = writer.Write(testState)
require.NoError(t, err)
require.NoError(t, writer.Close())
state, err := parseState(path)
require.NoError(t, err)
var expected VMState
require.NoError(t, json.Unmarshal(testState, &expected))
require.Equal(t, &expected, state)
})
t.Run("InvalidStateWitness", func(t *testing.T) {
invalidWitnessLen := asteriscWitnessLen - 1
state := &VMState{
Step: 10,
Exited: true,
Witness: make([]byte, invalidWitnessLen),
}
err := state.validateState()
require.ErrorContains(t, err, "invalid witness")
})
t.Run("InvalidStateHash", func(t *testing.T) {
state := &VMState{
Step: 10,
Exited: true,
Witness: make([]byte, asteriscWitnessLen),
}
// Unknown exit code
state.StateHash[0] = 37
err := state.validateState()
require.ErrorContains(t, err, "invalid stateHash: unknown exitCode")
// Exited but ExitCode is VMStatusUnfinished
state.StateHash[0] = 3
err = state.validateState()
require.ErrorContains(t, err, "invalid stateHash: invalid exitCode")
// Not Exited but ExitCode is not VMStatusUnfinished
state.Exited = false
for exitCode := 0; exitCode < 3; exitCode++ {
state.StateHash[0] = byte(exitCode)
err = state.validateState()
require.ErrorContains(t, err, "invalid stateHash: invalid exitCode")
}
})
}
{
"step": 0,
"pre": "0x03abd5c535c08bae7c4ad48fcae39b65f9c25239f65b4376c58638d262c97381",
"post": "0x034689707b571db46b32c9e433def18e648f4e1fa9e5abd4012e7913031bfc10",
"state-data": "0x354cfaf28a5b60c3f64f22f9f171b64aa067f90c6de6c96f725f44c5cf9f8ac1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080e080000000000000000000000007f0000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"proof-data": "0x000000000000000003350100930581006f00800100000000970f000067800f01000000000000000097c2ffff938282676780020000000000032581009308e0050e1893682c323d6695396f1122b3cb562af8c65cab19978c9246434fda0536c90ca1cfabf684ebce3ad9fbd54000a2b258f8d0e447c1bb6f7e97de47aadfc12cd7b6f466bfd024daa905886c5f638f4692d843709e6c1c0d9eb2e251c626d53d15e04b59735fe0781bc4357a4243fbc28e6981902a8c2669a2d6456f7a964423db5d1585da978861f8b84067654b29490275c82b54083ee09c82eb7aa9ae693911226bb8297ad82c0963ae943f22d0c6086f4f14437e4d1c87ceb17e68caf5eaec77f14b46225b417d2191ca7b49564c896836a95ad4e9c383bd1c8ff9d8e888c64fb3836daa9535e58372e9646b7b144219980a4389aca5da241c3ec11fbc9297bd7a94ac671ccec288604c23a0072b0c1ed069198959cacdc2574aff65b7eceffc391e21778a1775deceb3ec0990836df98d98a4f3f0dc854587230fbf59e4daa60e8240d74caf90f7e2cd014c1d5d707b2e44269d9a9caf133882fe1ebb2f4237f6282abe89639b357e9231418d0c41373229ae9edfa6815bec484cb79772c9e2a7d80912123558f79b539bb45d435f2a4446970f1e2123494740285cec3491b0a41a9fd7403bdc8cd239a87508039a77b48ee39a951a8bd196b583de2b93444aafd456d0cd92050fa6a816d5183c1d75e96df540c8ac3bb8638b971f0cf3fb5b4a321487a1c8992b921de110f3d5bbb87369b25fe743ad7e789ca52d9f9fe62ccb103b78fe65eaa2cd47895022c590639c8f0c6a3999d8a5c71ed94d355815851b479f8d93eae90822294c96b39724b33491f8497b0bf7e1b995b37e4d759ff8a7958d194da6e00c475a6ddcf6efcb5fb4bb383c9b273da18d01e000dbe9c65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2f4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd95a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e3774df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652cdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618db8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea32293237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d7358448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a927ae5ba08d7291c96c8cbddcc148bf48a6d68c7974b94356f53754ef6171d757bf558bebd2ceec7f3c5dce04a4782f88c2c6036ae78ee206d0bc5289d20461a2e21908c2968c0699040a6fd866a577a99a9d2ec88745c815fd4a472c789244daae824d72ddc272aab68a8c3022e36f10454437c1886f3ff9927b64f232df414f27e429a4bef3083bc31a671d046ea5c1f5b8c3094d72868d9dfdc12c7334ac5f743cc5c365a9a6a15c1f240ac25880c7a9d1de290696cb766074a1d83d9278164adcf616c3bfabf63999a01966c998b7bb572774035a63ead49da73b5987f34775786645d0c5dd7c04a2f8a75dcae085213652f5bce3ea8b9b9bedd1cab3c5e9b88b152c9b8a7b79637d35911848b0c41e7cc7cca2ab4fe9a15f9c38bb4bb9390c4e2d8ce834ffd7a6cd85d7113d4521abb857774845c4291e6f6d010d97e3185bc799d83e3bb31501b3da786680df30fbc18eb41cbce611e8c0e9c72f69571ca10d3ef857d04d9c03ead7c6317d797a090fa1271ad9c7addfbcb412e9643d4fb33b1809c42623f474055fa9400a2027a7a885c8dfa4efe20666b4ee27d7529c134d7f28d53f175f6bf4b62faa2110d5b76f0f770c15e628181c1fcc18f970a9c34d24b2fc8c50ca9c07a7156ef4e5ff4bdf002eda0b11c1d359d0b59a54680704dbb9db631457879b27e0dfdbe50158fd9cf9b4cf77605c4ac4c95bd65fc9f6f9295a686647cb999090819cda700820c282c613cedcd218540bbc6f37b01c6567c4a1ea624f092a3a5cca2d6f0f0db231972fce627f0ecca0dee60f17551c5f8fdaeb5ab560b2ceb781cdb339361a0fbee1b9dffad59115138c8d6a70dda9ccc1bf0bbdd7fee15764845db875f6432559ff8dbc9055324431bc34e5b93d15da307317849eccd90c0c7b98870b9317c15a5959dcfb84c76dcc908c4fe6ba92126339bf06e458f6646df5e83ba7c3d35bc263b3222c8e9040068847749ca8e8f95045e4342aeb521eb3a5587ec268ed3aa6faf32b62b0bc41a9d549521f406fc3ec7d4dabb75e0d3e144d7cc882372d13746b6dcd481b1b229bcaec9f7422cdfb84e35c5d92171376cae5c86300822d729cd3a8479583bef09527027dba5f11263c5cbbeb3834b7a5c1cba9aa5fee0c95ec3f17a33ec3d8047fff799187f5ae2040bbe913c226c34c9fbe4389dd728984257a816892b3cae3e43191dd291f0eb50000000000000000420000000000000035000000000000000000000000000000060000000000000000100000000000001900000000000000480000000000001050edbc06b4bfc3ee108b66f7a8f772ca4d90e1a085f4a8398505920f7465bb44b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d3021ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a193440eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f839867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756afcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf8923490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99cc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8beccda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d22733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981fe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0b46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0c65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2f4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd95a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e3774df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652cdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618db8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea32293237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d7358448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a927ae5ba08d7291c96c8cbddcc148bf48a6d68c7974b94356f53754ef6171d757bf558bebd2ceec7f3c5dce04a4782f88c2c6036ae78ee206d0bc5289d20461a2e21908c2968c0699040a6fd866a577a99a9d2ec88745c815fd4a472c789244daae824d72ddc272aab68a8c3022e36f10454437c1886f3ff9927b64f232df414f27e429a4bef3083bc31a671d046ea5c1f5b8c3094d72868d9dfdc12c7334ac5f743cc5c365a9a6a15c1f240ac25880c7a9d1de290696cb766074a1d83d9278164adcf616c3bfabf63999a01966c998b7bb572774035a63ead49da73b5987f34775786645d0c5dd7c04a2f8a75dcae085213652f5bce3ea8b9b9bedd1cab3c5e9b88b152c9b8a7b79637d35911848b0c41e7cc7cca2ab4fe9a15f9c38bb4bb9390c4e2d8ce834ffd7a6cd85d7113d4521abb857774845c4291e6f6d010d97e3185bc799d83e3bb31501b3da786680df30fbc18eb41cbce611e8c0e9c72f69571ca10d3ef857d04d9c03ead7c6317d797a090fa1271ad9c7addfbcb412e9643d4fb33b1809c42623f474055fa9400a2027a7a885c8dfa4efe20666b4ee27d7529c134d7f28d53f175f6bf4b62faa2110d5b76f0f770c15e628181c1fcc18f970a9c34d24b2fc8c50ca9c07a7156ef4e5ff4bdf002eda0b11c1d359d0b59a54680704dbb9db631457879b27e0dfdbe50158fd9cf9b4cf77605c4ac4c95bd65fc9f6f9295a686647cb999090819cda700820c282c613cedcd218540bbc6f37b01c6567c4a1ea624f092a3a5cca2d6f0f0db231972fce627f0ecca0dee60f17551c5f8fdaeb5ab560b2ceb781cdb339361a0fbee1b9dffad59115138c8d6a70dda9ccc1bf0bbdd7fee15764845db875f6432559ff8dbc9055324431bc34e5b93d15da307317849eccd90c0c7b98870b9317c15a5959dcfb84c76dcc908c4fe6ba92126339bf06e458f6646df5e83ba7c3d35bc263b3222c8e9040068847749ca8e8f95045e4342aeb521eb3a5587ec268ed3aa6faf32b62b0bc41a9d549521f406fc30f3e39c5412c30550d1d07fb07ff0e546fbeea1988f6658f04a9b19693e5b99d84e35c5d92171376cae5c86300822d729cd3a8479583bef09527027dba5f11263c5cbbeb3834b7a5c1cba9aa5fee0c95ec3f17a33ec3d8047fff799187f5ae2040bbe913c226c34c9fbe4389dd728984257a816892b3cae3e43191dd291f0eb5"
}
{
"foo": 0,
"bar": "0x71f9eb93ff904e5c03c3425228ef75766db0c906ad239df9a7a7f0d9c6a89705",
"step": 0,
"pre": "0x03abd5c535c08bae7c4ad48fcae39b65f9c25239f65b4376c58638d262c97381",
"post": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"state-data": "0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
"proof-data": "0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
}
{
"pc": 0,
"exited": false,
"step": 0,
"witness": "wOSi8Cm62dDmKt1OGwxlLrSznk6zE4ghp7evP1rfrXYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIGCAAAAAAAAAAAAAAAAB/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"stateHash": [
3,
33,
111,
220,
74,
123,
253,
76,
113,
96,
250,
148,
109,
27,
254,
69,
29,
19,
255,
50,
218,
73,
102,
9,
254,
24,
53,
82,
130,
185,
16,
198
]
}
......@@ -2,40 +2,26 @@ package cannon
import (
"context"
"errors"
"fmt"
"math"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
"github.com/ethereum-optimism/optimism/op-challenger/config"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
"github.com/ethereum/go-ethereum/log"
)
const (
snapsDir = "snapshots"
preimagesDir = "preimages"
finalState = "final.json.gz"
)
var snapshotNameRegexp = regexp.MustCompile(`^[0-9]+\.json.gz$`)
type snapshotSelect func(logger log.Logger, dir string, absolutePreState string, i uint64) (string, error)
type cmdExecutor func(ctx context.Context, l log.Logger, binary string, args ...string) error
type Executor struct {
logger log.Logger
metrics CannonMetricer
l1 string
l1Beacon string
l2 string
inputs LocalGameInputs
inputs utils.LocalGameInputs
cannon string
server string
network string
......@@ -44,17 +30,17 @@ type Executor struct {
absolutePreState string
snapshotFreq uint
infoFreq uint
selectSnapshot snapshotSelect
cmdExecutor cmdExecutor
selectSnapshot utils.SnapshotSelect
cmdExecutor utils.CmdExecutor
}
func NewExecutor(logger log.Logger, m CannonMetricer, cfg *config.Config, inputs LocalGameInputs) *Executor {
func NewExecutor(logger log.Logger, m CannonMetricer, cfg *config.Config, inputs utils.LocalGameInputs) *Executor {
return &Executor{
logger: logger,
metrics: m,
l1: cfg.L1EthRpc,
l1Beacon: cfg.L1Beacon,
l2: cfg.CannonL2,
l2: cfg.L2Rpc,
inputs: inputs,
cannon: cfg.CannonBin,
server: cfg.CannonServer,
......@@ -64,8 +50,8 @@ func NewExecutor(logger log.Logger, m CannonMetricer, cfg *config.Config, inputs
absolutePreState: cfg.CannonAbsolutePreState,
snapshotFreq: cfg.CannonSnapshotFreq,
infoFreq: cfg.CannonInfoFreq,
selectSnapshot: findStartingSnapshot,
cmdExecutor: runCmd,
selectSnapshot: utils.FindStartingSnapshot,
cmdExecutor: utils.RunCmd,
}
}
......@@ -75,18 +61,17 @@ func (e *Executor) GenerateProof(ctx context.Context, dir string, i uint64) erro
return e.generateProof(ctx, dir, i, i)
}
// generateProofOrUntilPreimageRead executes cannon to generate a proof at the specified trace index,
// or until a non-local preimage read is encountered if untilPreimageRead is true.
// generateProof executes cannon from the specified starting trace index until the end trace index.
// The proof is stored at the specified directory.
func (e *Executor) generateProof(ctx context.Context, dir string, begin uint64, end uint64, extraCannonArgs ...string) error {
snapshotDir := filepath.Join(dir, snapsDir)
snapshotDir := filepath.Join(dir, utils.SnapsDir)
start, err := e.selectSnapshot(e.logger, snapshotDir, e.absolutePreState, begin)
if err != nil {
return fmt.Errorf("find starting snapshot: %w", err)
}
proofDir := filepath.Join(dir, proofsDir)
dataDir := preimageDir(dir)
lastGeneratedState := filepath.Join(dir, finalState)
proofDir := filepath.Join(dir, utils.ProofsDir)
dataDir := utils.PreimageDir(dir)
lastGeneratedState := filepath.Join(dir, utils.FinalState)
args := []string{
"run",
"--input", start,
......@@ -140,58 +125,3 @@ func (e *Executor) generateProof(ctx context.Context, dir string, begin uint64,
e.metrics.RecordCannonExecutionTime(time.Since(execStart).Seconds())
return err
}
func preimageDir(dir string) string {
return filepath.Join(dir, preimagesDir)
}
func runCmd(ctx context.Context, l log.Logger, binary string, args ...string) error {
cmd := exec.CommandContext(ctx, binary, args...)
stdOut := oplog.NewWriter(l, log.LevelInfo)
defer stdOut.Close()
// Keep stdErr at info level because cannon uses stderr for progress messages
stdErr := oplog.NewWriter(l, log.LevelInfo)
defer stdErr.Close()
cmd.Stdout = stdOut
cmd.Stderr = stdErr
return cmd.Run()
}
// findStartingSnapshot finds the closest snapshot before the specified traceIndex in snapDir.
// If no suitable snapshot can be found it returns absolutePreState.
func findStartingSnapshot(logger log.Logger, snapDir string, absolutePreState string, traceIndex uint64) (string, error) {
// Find the closest snapshot to start from
entries, err := os.ReadDir(snapDir)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return absolutePreState, nil
}
return "", fmt.Errorf("list snapshots in %v: %w", snapDir, err)
}
bestSnap := uint64(0)
for _, entry := range entries {
if entry.IsDir() {
logger.Warn("Unexpected directory in snapshots dir", "parent", snapDir, "child", entry.Name())
continue
}
name := entry.Name()
if !snapshotNameRegexp.MatchString(name) {
logger.Warn("Unexpected file in snapshots dir", "parent", snapDir, "child", entry.Name())
continue
}
index, err := strconv.ParseUint(name[0:len(name)-len(".json.gz")], 10, 64)
if err != nil {
logger.Error("Unable to parse trace index of snapshot file", "parent", snapDir, "child", entry.Name())
continue
}
if index > bestSnap && index < traceIndex {
bestSnap = index
}
}
if bestSnap == 0 {
return absolutePreState, nil
}
startFrom := fmt.Sprintf("%v/%v.json.gz", snapDir, bestSnap)
return startFrom, nil
}
......@@ -11,6 +11,7 @@ import (
"time"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/common"
......@@ -25,14 +26,14 @@ func TestGenerateProof(t *testing.T) {
tempDir := t.TempDir()
dir := filepath.Join(tempDir, "gameDir")
cfg := config.NewConfig(common.Address{0xbb}, "http://localhost:8888", "http://localhost:9000", tempDir, config.TraceTypeCannon)
cfg.L2Rpc = "http://localhost:9999"
cfg.CannonAbsolutePreState = "pre.json"
cfg.CannonBin = "./bin/cannon"
cfg.CannonServer = "./bin/op-program"
cfg.CannonL2 = "http://localhost:9999"
cfg.CannonSnapshotFreq = 500
cfg.CannonInfoFreq = 900
inputs := LocalGameInputs{
inputs := utils.LocalGameInputs{
L1Head: common.Hash{0x11},
L2Head: common.Hash{0x22},
L2OutputRoot: common.Hash{0x33},
......@@ -73,15 +74,15 @@ func TestGenerateProof(t *testing.T) {
cfg.CannonRollupConfigPath = ""
cfg.CannonL2GenesisPath = ""
binary, subcommand, args := captureExec(t, cfg, 150_000_000)
require.DirExists(t, filepath.Join(dir, preimagesDir))
require.DirExists(t, filepath.Join(dir, proofsDir))
require.DirExists(t, filepath.Join(dir, snapsDir))
require.DirExists(t, filepath.Join(dir, utils.PreimagesDir))
require.DirExists(t, filepath.Join(dir, utils.ProofsDir))
require.DirExists(t, filepath.Join(dir, utils.SnapsDir))
require.Equal(t, cfg.CannonBin, binary)
require.Equal(t, "run", subcommand)
require.Equal(t, input, args["--input"])
require.Contains(t, args, "--meta")
require.Equal(t, "", args["--meta"])
require.Equal(t, filepath.Join(dir, finalState), args["--output"])
require.Equal(t, filepath.Join(dir, utils.FinalState), args["--output"])
require.Equal(t, "=150000000", args["--proof-at"])
require.Equal(t, "=150000001", args["--stop-at"])
require.Equal(t, "%500", args["--snapshot-at"])
......@@ -92,10 +93,10 @@ func TestGenerateProof(t *testing.T) {
require.Equal(t, "--server", args[cfg.CannonServer])
require.Equal(t, cfg.L1EthRpc, args["--l1"])
require.Equal(t, cfg.L1Beacon, args["--l1.beacon"])
require.Equal(t, cfg.CannonL2, args["--l2"])
require.Equal(t, filepath.Join(dir, preimagesDir), args["--datadir"])
require.Equal(t, filepath.Join(dir, proofsDir, "%d.json.gz"), args["--proof-fmt"])
require.Equal(t, filepath.Join(dir, snapsDir, "%d.json.gz"), args["--snapshot-fmt"])
require.Equal(t, cfg.L2Rpc, args["--l2"])
require.Equal(t, filepath.Join(dir, utils.PreimagesDir), args["--datadir"])
require.Equal(t, filepath.Join(dir, utils.ProofsDir, "%d.json.gz"), args["--proof-fmt"])
require.Equal(t, filepath.Join(dir, utils.SnapsDir, "%d.json.gz"), args["--snapshot-fmt"])
require.Equal(t, cfg.CannonNetwork, args["--network"])
require.NotContains(t, args, "--rollup.config")
require.NotContains(t, args, "--l2.genesis")
......@@ -137,7 +138,7 @@ func TestRunCmdLogsOutput(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
logger, logs := testlog.CaptureLogger(t, log.LevelInfo)
err := runCmd(ctx, logger, bin, "Hello World")
err := utils.RunCmd(ctx, logger, bin, "Hello World")
require.NoError(t, err)
levelFilter := testlog.NewLevelFilter(log.LevelInfo)
msgFilter := testlog.NewMessageFilter("Hello World")
......@@ -157,25 +158,25 @@ func TestFindStartingSnapshot(t *testing.T) {
t.Run("UsePrestateWhenSnapshotsDirDoesNotExist", func(t *testing.T) {
dir := t.TempDir()
snapshot, err := findStartingSnapshot(logger, filepath.Join(dir, "doesNotExist"), execTestCannonPrestate, 1200)
snapshot, err := utils.FindStartingSnapshot(logger, filepath.Join(dir, "doesNotExist"), execTestCannonPrestate, 1200)
require.NoError(t, err)
require.Equal(t, execTestCannonPrestate, snapshot)
})
t.Run("UsePrestateWhenSnapshotsDirEmpty", func(t *testing.T) {
dir := withSnapshots(t)
snapshot, err := findStartingSnapshot(logger, dir, execTestCannonPrestate, 1200)
snapshot, err := utils.FindStartingSnapshot(logger, dir, execTestCannonPrestate, 1200)
require.NoError(t, err)
require.Equal(t, execTestCannonPrestate, snapshot)
})
t.Run("UsePrestateWhenNoSnapshotBeforeTraceIndex", func(t *testing.T) {
dir := withSnapshots(t, "100.json", "200.json")
snapshot, err := findStartingSnapshot(logger, dir, execTestCannonPrestate, 99)
snapshot, err := utils.FindStartingSnapshot(logger, dir, execTestCannonPrestate, 99)
require.NoError(t, err)
require.Equal(t, execTestCannonPrestate, snapshot)
snapshot, err = findStartingSnapshot(logger, dir, execTestCannonPrestate, 100)
snapshot, err = utils.FindStartingSnapshot(logger, dir, execTestCannonPrestate, 100)
require.NoError(t, err)
require.Equal(t, execTestCannonPrestate, snapshot)
})
......@@ -183,19 +184,19 @@ func TestFindStartingSnapshot(t *testing.T) {
t.Run("UseClosestAvailableSnapshot", func(t *testing.T) {
dir := withSnapshots(t, "100.json.gz", "123.json.gz", "250.json.gz")
snapshot, err := findStartingSnapshot(logger, dir, execTestCannonPrestate, 101)
snapshot, err := utils.FindStartingSnapshot(logger, dir, execTestCannonPrestate, 101)
require.NoError(t, err)
require.Equal(t, filepath.Join(dir, "100.json.gz"), snapshot)
snapshot, err = findStartingSnapshot(logger, dir, execTestCannonPrestate, 123)
snapshot, err = utils.FindStartingSnapshot(logger, dir, execTestCannonPrestate, 123)
require.NoError(t, err)
require.Equal(t, filepath.Join(dir, "100.json.gz"), snapshot)
snapshot, err = findStartingSnapshot(logger, dir, execTestCannonPrestate, 124)
snapshot, err = utils.FindStartingSnapshot(logger, dir, execTestCannonPrestate, 124)
require.NoError(t, err)
require.Equal(t, filepath.Join(dir, "123.json.gz"), snapshot)
snapshot, err = findStartingSnapshot(logger, dir, execTestCannonPrestate, 256)
snapshot, err = utils.FindStartingSnapshot(logger, dir, execTestCannonPrestate, 256)
require.NoError(t, err)
require.Equal(t, filepath.Join(dir, "250.json.gz"), snapshot)
})
......@@ -203,14 +204,14 @@ func TestFindStartingSnapshot(t *testing.T) {
t.Run("IgnoreDirectories", func(t *testing.T) {
dir := withSnapshots(t, "100.json.gz")
require.NoError(t, os.Mkdir(filepath.Join(dir, "120.json.gz"), 0o777))
snapshot, err := findStartingSnapshot(logger, dir, execTestCannonPrestate, 150)
snapshot, err := utils.FindStartingSnapshot(logger, dir, execTestCannonPrestate, 150)
require.NoError(t, err)
require.Equal(t, filepath.Join(dir, "100.json.gz"), snapshot)
})
t.Run("IgnoreUnexpectedFiles", func(t *testing.T) {
dir := withSnapshots(t, ".file", "100.json.gz", "foo", "bar.json.gz")
snapshot, err := findStartingSnapshot(logger, dir, execTestCannonPrestate, 150)
snapshot, err := utils.FindStartingSnapshot(logger, dir, execTestCannonPrestate, 150)
require.NoError(t, err)
require.Equal(t, filepath.Join(dir, "100.json.gz"), snapshot)
})
......
......@@ -60,7 +60,7 @@ func TestAbsolutePreStateCommitment(t *testing.T) {
})
t.Run("CacheAbsolutePreState", func(t *testing.T) {
setupPreState(t, dataDir, "state.json")
setupPreState(t, dataDir, prestate)
provider := newCannonPrestateProvider(dataDir, prestate)
first, err := provider.AbsolutePreStateCommitment(context.Background())
require.NoError(t, err)
......
......@@ -9,11 +9,10 @@ import (
"math"
"os"
"path/filepath"
"strconv"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum-optimism/optimism/op-program/host/kvstore"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum/go-ethereum/common"
......@@ -23,57 +22,34 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
)
const (
proofsDir = "proofs"
diskStateCache = "state.json.gz"
)
type proofData struct {
ClaimValue common.Hash `json:"post"`
StateData hexutil.Bytes `json:"state-data"`
ProofData hexutil.Bytes `json:"proof-data"`
OracleKey hexutil.Bytes `json:"oracle-key,omitempty"`
OracleValue hexutil.Bytes `json:"oracle-value,omitempty"`
OracleOffset uint32 `json:"oracle-offset,omitempty"`
}
type CannonMetricer interface {
RecordCannonExecutionTime(t float64)
}
type ProofGenerator interface {
// GenerateProof executes cannon to generate a proof at the specified trace index in dataDir.
GenerateProof(ctx context.Context, dataDir string, proofAt uint64) error
}
type CannonTraceProvider struct {
logger log.Logger
dir string
prestate string
generator ProofGenerator
generator utils.ProofGenerator
gameDepth types.Depth
preimageLoader *preimageLoader
preimageLoader *utils.PreimageLoader
// 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(logger log.Logger, m CannonMetricer, cfg *config.Config, localInputs LocalGameInputs, dir string, gameDepth types.Depth) *CannonTraceProvider {
func NewTraceProvider(logger log.Logger, m CannonMetricer, cfg *config.Config, localInputs utils.LocalGameInputs, dir string, gameDepth types.Depth) *CannonTraceProvider {
return &CannonTraceProvider{
logger: logger,
dir: dir,
prestate: cfg.CannonAbsolutePreState,
generator: NewExecutor(logger, m, cfg, localInputs),
gameDepth: gameDepth,
preimageLoader: newPreimageLoader(kvstore.NewDiskKV(preimageDir(dir)).Get),
preimageLoader: utils.NewPreimageLoader(kvstore.NewDiskKV(utils.PreimageDir(dir)).Get),
}
}
func (p *CannonTraceProvider) SetMaxDepth(gameDepth types.Depth) {
p.gameDepth = gameDepth
}
func (p *CannonTraceProvider) Get(ctx context.Context, pos types.Position) (common.Hash, error) {
traceIndex := pos.TraceIndex(p.gameDepth)
if !traceIndex.IsUint64() {
......@@ -137,10 +113,10 @@ func (p *CannonTraceProvider) AbsolutePreStateCommitment(_ context.Context) (com
// 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.
func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofData, error) {
func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*utils.ProofData, error) {
// Attempt to read the last step from disk cache
if p.lastStep == 0 {
step, err := readLastStep(p.dir)
step, err := utils.ReadLastStep(p.dir)
if err != nil {
p.logger.Warn("Failed to read last step from disk cache", "err", err)
} else {
......@@ -151,7 +127,7 @@ func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofDa
if p.lastStep != 0 && i > p.lastStep {
i = p.lastStep
}
path := filepath.Join(p.dir, proofsDir, fmt.Sprintf("%d.json.gz", i))
path := filepath.Join(p.dir, utils.ProofsDir, fmt.Sprintf("%d.json.gz", i))
file, err := ioutil.OpenDecompressed(path)
if errors.Is(err, os.ErrNotExist) {
if err := p.generator.GenerateProof(ctx, p.dir, i); err != nil {
......@@ -177,7 +153,7 @@ func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofDa
if err != nil {
return nil, fmt.Errorf("cannot hash witness: %w", err)
}
proof := &proofData{
proof := &utils.ProofData{
ClaimValue: witnessHash,
StateData: hexutil.Bytes(witness),
ProofData: []byte{},
......@@ -185,7 +161,7 @@ func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofDa
OracleValue: nil,
OracleOffset: 0,
}
if err := writeLastStep(p.dir, proof, p.lastStep); err != nil {
if err := utils.WriteLastStep(p.dir, proof, p.lastStep); err != nil {
p.logger.Warn("Failed to write last step to disk cache", "step", p.lastStep)
}
return proof, nil
......@@ -198,7 +174,7 @@ func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofDa
return nil, fmt.Errorf("cannot open proof file (%v): %w", path, err)
}
defer file.Close()
var proof proofData
var proof utils.ProofData
err = json.NewDecoder(file).Decode(&proof)
if err != nil {
return nil, fmt.Errorf("failed to read proof (%v): %w", path, err)
......@@ -207,94 +183,32 @@ func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofDa
}
func (c *CannonTraceProvider) finalState() (*mipsevm.State, error) {
state, err := parseState(filepath.Join(c.dir, finalState))
state, err := parseState(filepath.Join(c.dir, utils.FinalState))
if err != nil {
return nil, fmt.Errorf("cannot read final state: %w", err)
}
return state, nil
}
type diskStateCacheObj struct {
Step uint64 `json:"step"`
}
// readLastStep reads the tracked last step from disk.
func readLastStep(dir string) (uint64, error) {
state := diskStateCacheObj{}
file, err := ioutil.OpenDecompressed(filepath.Join(dir, diskStateCache))
if err != nil {
return 0, err
}
defer file.Close()
err = json.NewDecoder(file).Decode(&state)
if err != nil {
return 0, err
}
return state.Step, nil
}
// writeLastStep writes the last step and proof to disk as a persistent cache.
func writeLastStep(dir string, proof *proofData, step uint64) error {
state := diskStateCacheObj{Step: step}
lastStepFile := filepath.Join(dir, diskStateCache)
if err := ioutil.WriteCompressedJson(lastStepFile, state); err != nil {
return fmt.Errorf("failed to write last step to %v: %w", lastStepFile, err)
}
if err := ioutil.WriteCompressedJson(filepath.Join(dir, proofsDir, fmt.Sprintf("%d.json.gz", step)), proof); err != nil {
return fmt.Errorf("failed to write proof: %w", err)
}
return nil
}
// CannonTraceProviderForTest is a CannonTraceProvider that can find the step referencing the preimage read
// Only to be used for testing
type CannonTraceProviderForTest struct {
*CannonTraceProvider
}
type preimageOpts []string
type PreimageOpt func() preimageOpts
func PreimageLoad(key preimage.Key, offset uint32) PreimageOpt {
return func() preimageOpts {
return []string{"--stop-at-preimage", fmt.Sprintf("%v@%v", common.Hash(key.PreimageKey()).Hex(), offset)}
}
}
func FirstPreimageLoadOfType(preimageType string) PreimageOpt {
return func() preimageOpts {
return []string{"--stop-at-preimage-type", preimageType}
}
}
func FirstKeccakPreimageLoad() PreimageOpt {
return FirstPreimageLoadOfType("keccak")
}
func FirstPrecompilePreimageLoad() PreimageOpt {
return FirstPreimageLoadOfType("precompile")
}
func PreimageLargerThan(size int) PreimageOpt {
return func() preimageOpts {
return []string{"--stop-at-preimage-larger-than", strconv.Itoa(size)}
}
}
func NewTraceProviderForTest(logger log.Logger, m CannonMetricer, cfg *config.Config, localInputs LocalGameInputs, dir string, gameDepth types.Depth) *CannonTraceProviderForTest {
func NewTraceProviderForTest(logger log.Logger, m CannonMetricer, cfg *config.Config, localInputs utils.LocalGameInputs, dir string, gameDepth types.Depth) *CannonTraceProviderForTest {
p := &CannonTraceProvider{
logger: logger,
dir: dir,
prestate: cfg.CannonAbsolutePreState,
generator: NewExecutor(logger, m, cfg, localInputs),
gameDepth: gameDepth,
preimageLoader: newPreimageLoader(kvstore.NewDiskKV(preimageDir(dir)).Get),
preimageLoader: utils.NewPreimageLoader(kvstore.NewDiskKV(utils.PreimageDir(dir)).Get),
}
return &CannonTraceProviderForTest{p}
}
func (p *CannonTraceProviderForTest) FindStep(ctx context.Context, start uint64, preimage PreimageOpt) (uint64, error) {
func (p *CannonTraceProviderForTest) FindStep(ctx context.Context, start uint64, preimage utils.PreimageOpt) (uint64, error) {
// Run cannon to find the step that meets the preimage conditions
if err := p.generator.(*Executor).generateProof(ctx, p.dir, start, math.MaxUint64, preimage()...); err != nil {
return 0, fmt.Errorf("generate cannon trace (until preimage read): %w", err)
......
......@@ -13,6 +13,7 @@ import (
"testing"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum-optimism/optimism/op-service/testlog"
......@@ -110,7 +111,7 @@ func TestGetStepData(t *testing.T) {
Step: 10,
Exited: true,
}
generator.proof = &proofData{
generator.proof = &utils.ProofData{
ClaimValue: common.Hash{0xaa},
StateData: []byte{0xbb},
ProofData: []byte{0xcc},
......@@ -136,7 +137,7 @@ func TestGetStepData(t *testing.T) {
Step: 10,
Exited: true,
}
generator.proof = &proofData{
generator.proof = &utils.ProofData{
ClaimValue: common.Hash{0xaa},
StateData: []byte{0xbb},
ProofData: []byte{0xcc},
......@@ -162,7 +163,7 @@ func TestGetStepData(t *testing.T) {
Step: 10,
Exited: true,
}
initGenerator.proof = &proofData{
initGenerator.proof = &utils.ProofData{
ClaimValue: common.Hash{0xaa},
StateData: []byte{0xbb},
ProofData: []byte{0xcc},
......@@ -180,7 +181,7 @@ func TestGetStepData(t *testing.T) {
Step: 10,
Exited: true,
}
generator.proof = &proofData{
generator.proof = &utils.ProofData{
ClaimValue: common.Hash{0xaa},
StateData: []byte{0xbb},
ProofData: []byte{0xcc},
......@@ -221,12 +222,13 @@ func setupTestData(t *testing.T) (string, string) {
entries, err := testData.ReadDir(srcDir)
require.NoError(t, err)
dataDir := t.TempDir()
require.NoError(t, os.Mkdir(filepath.Join(dataDir, proofsDir), 0o777))
require.NoError(t, os.Mkdir(filepath.Join(dataDir, utils.ProofsDir), 0o777))
for _, entry := range entries {
path := filepath.Join(srcDir, entry.Name())
file, err := testData.ReadFile(path)
require.NoErrorf(t, err, "reading %v", path)
err = writeGzip(filepath.Join(dataDir, proofsDir, entry.Name()+".gz"), file)
proofFile := filepath.Join(dataDir, utils.ProofsDir, entry.Name()+".gz")
err = ioutil.WriteCompressedBytes(proofFile, file, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0o644)
require.NoErrorf(t, err, "writing %v", path)
}
return dataDir, "state.json"
......@@ -246,36 +248,30 @@ func setupWithTestData(t *testing.T, dataDir string, prestate string) (*CannonTr
type stubGenerator struct {
generated []int // Using int makes assertions easier
finalState *mipsevm.State
proof *proofData
proof *utils.ProofData
}
func (e *stubGenerator) GenerateProof(ctx context.Context, dir string, i uint64) error {
e.generated = append(e.generated, int(i))
var proofFile string
var data []byte
var err error
if e.finalState != nil && e.finalState.Step <= i {
// Requesting a trace index past the end of the trace
data, err := json.Marshal(e.finalState)
proofFile = filepath.Join(dir, utils.FinalState)
data, err = json.Marshal(e.finalState)
if err != nil {
return err
}
return writeGzip(filepath.Join(dir, finalState), data)
return ioutil.WriteCompressedBytes(proofFile, data, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0o644)
}
if e.proof != nil {
proofFile := filepath.Join(dir, proofsDir, fmt.Sprintf("%d.json.gz", i))
data, err := json.Marshal(e.proof)
proofFile = filepath.Join(dir, utils.ProofsDir, fmt.Sprintf("%d.json.gz", i))
data, err = json.Marshal(e.proof)
if err != nil {
return err
}
return writeGzip(proofFile, data)
return ioutil.WriteCompressedBytes(proofFile, data, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0o644)
}
return nil
}
func writeGzip(path string, data []byte) error {
writer, err := ioutil.OpenCompressed(path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0o644)
if err != nil {
return err
}
defer writer.Close()
_, err = writer.Write(data)
return err
}
package outputs
import (
"context"
"fmt"
"path/filepath"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/asterisc"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/split"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
func NewOutputAsteriscTraceAccessor(
logger log.Logger,
m metrics.Metricer,
cfg *config.Config,
l2Client utils.L2HeaderSource,
prestateProvider types.PrestateProvider,
rollupClient OutputRollupClient,
dir string,
l1Head eth.BlockID,
splitDepth types.Depth,
prestateBlock uint64,
poststateBlock uint64,
) (*trace.Accessor, error) {
outputProvider := NewTraceProvider(logger, prestateProvider, rollupClient, l1Head, splitDepth, prestateBlock, poststateBlock)
asteriscCreator := func(ctx context.Context, localContext common.Hash, depth types.Depth, agreed contracts.Proposal, claimed contracts.Proposal) (types.TraceProvider, error) {
logger := logger.New("pre", agreed.OutputRoot, "post", claimed.OutputRoot, "localContext", localContext)
subdir := filepath.Join(dir, localContext.Hex())
localInputs, err := utils.FetchLocalInputsFromProposals(ctx, l1Head.Hash, l2Client, agreed, claimed)
if err != nil {
return nil, fmt.Errorf("failed to fetch asterisc local inputs: %w", err)
}
provider := asterisc.NewTraceProvider(logger, m, cfg, localInputs, subdir, depth)
return provider, nil
}
cache := NewProviderCache(m, "output_asterisc_provider", asteriscCreator)
selector := split.NewSplitProviderSelector(outputProvider, splitDepth, OutputRootSplitAdapter(outputProvider, cache.GetOrCreate))
return trace.NewAccessor(selector), nil
}
......@@ -10,6 +10,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/split"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-service/eth"
......@@ -21,7 +22,7 @@ func NewOutputCannonTraceAccessor(
logger log.Logger,
m metrics.Metricer,
cfg *config.Config,
l2Client cannon.L2HeaderSource,
l2Client utils.L2HeaderSource,
prestateProvider types.PrestateProvider,
rollupClient OutputRollupClient,
dir string,
......@@ -34,7 +35,7 @@ func NewOutputCannonTraceAccessor(
cannonCreator := func(ctx context.Context, localContext common.Hash, depth types.Depth, agreed contracts.Proposal, claimed contracts.Proposal) (types.TraceProvider, error) {
logger := logger.New("pre", agreed.OutputRoot, "post", claimed.OutputRoot, "localContext", localContext)
subdir := filepath.Join(dir, localContext.Hex())
localInputs, err := cannon.FetchLocalInputsFromProposals(ctx, l1Head.Hash, l2Client, agreed, claimed)
localInputs, err := utils.FetchLocalInputsFromProposals(ctx, l1Head.Hash, l2Client, agreed, claimed)
if err != nil {
return nil, fmt.Errorf("failed to fetch cannon local inputs: %w", err)
}
......
package utils
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum/go-ethereum/log"
)
type SnapshotSelect func(logger log.Logger, dir string, absolutePreState string, i uint64) (string, error)
type CmdExecutor func(ctx context.Context, l log.Logger, binary string, args ...string) error
const (
SnapsDir = "snapshots"
PreimagesDir = "preimages"
FinalState = "final.json.gz"
)
var snapshotNameRegexp = regexp.MustCompile(`^[0-9]+\.json.gz$`)
func PreimageDir(dir string) string {
return filepath.Join(dir, PreimagesDir)
}
func RunCmd(ctx context.Context, l log.Logger, binary string, args ...string) error {
cmd := exec.CommandContext(ctx, binary, args...)
stdOut := oplog.NewWriter(l, log.LevelInfo)
defer stdOut.Close()
// Keep stdErr at info level because FPVM uses stderr for progress messages
stdErr := oplog.NewWriter(l, log.LevelInfo)
defer stdErr.Close()
cmd.Stdout = stdOut
cmd.Stderr = stdErr
return cmd.Run()
}
// FindStartingSnapshot finds the closest snapshot before the specified traceIndex in snapDir.
// If no suitable snapshot can be found it returns absolutePreState.
func FindStartingSnapshot(logger log.Logger, snapDir string, absolutePreState string, traceIndex uint64) (string, error) {
// Find the closest snapshot to start from
entries, err := os.ReadDir(snapDir)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return absolutePreState, nil
}
return "", fmt.Errorf("list snapshots in %v: %w", snapDir, err)
}
bestSnap := uint64(0)
for _, entry := range entries {
if entry.IsDir() {
logger.Warn("Unexpected directory in snapshots dir", "parent", snapDir, "child", entry.Name())
continue
}
name := entry.Name()
if !snapshotNameRegexp.MatchString(name) {
logger.Warn("Unexpected file in snapshots dir", "parent", snapDir, "child", entry.Name())
continue
}
index, err := strconv.ParseUint(name[0:len(name)-len(".json.gz")], 10, 64)
if err != nil {
logger.Error("Unable to parse trace index of snapshot file", "parent", snapDir, "child", entry.Name())
continue
}
if index > bestSnap && index < traceIndex {
bestSnap = index
}
}
if bestSnap == 0 {
return absolutePreState, nil
}
startFrom := fmt.Sprintf("%v/%v.json.gz", snapDir, bestSnap)
return startFrom, nil
}
package cannon
package utils
import (
"bytes"
......@@ -29,17 +29,17 @@ var (
type preimageSource func(key common.Hash) ([]byte, error)
type preimageLoader struct {
type PreimageLoader struct {
getPreimage preimageSource
}
func newPreimageLoader(getPreimage preimageSource) *preimageLoader {
return &preimageLoader{
func NewPreimageLoader(getPreimage preimageSource) *PreimageLoader {
return &PreimageLoader{
getPreimage: getPreimage,
}
}
func (l *preimageLoader) LoadPreimage(proof *proofData) (*types.PreimageOracleData, error) {
func (l *PreimageLoader) LoadPreimage(proof *ProofData) (*types.PreimageOracleData, error) {
if len(proof.OracleKey) == 0 {
return nil, nil
}
......@@ -53,7 +53,7 @@ func (l *preimageLoader) LoadPreimage(proof *proofData) (*types.PreimageOracleDa
}
}
func (l *preimageLoader) loadBlobPreimage(proof *proofData) (*types.PreimageOracleData, error) {
func (l *PreimageLoader) loadBlobPreimage(proof *ProofData) (*types.PreimageOracleData, error) {
// The key for a blob field element is a keccak hash of commitment++fieldElementIndex.
// First retrieve the preimage of the key as a keccak hash so we have the commitment and required field element
inputsKey := preimage.Keccak256Key(proof.OracleKey).PreimageKey()
......@@ -102,7 +102,7 @@ func (l *preimageLoader) loadBlobPreimage(proof *proofData) (*types.PreimageOrac
return types.NewPreimageOracleBlobData(proof.OracleKey, claimWithLength, proof.OracleOffset, requiredFieldElement, commitment, kzgProof[:]), nil
}
func (l *preimageLoader) loadPrecompilePreimage(proof *proofData) (*types.PreimageOracleData, error) {
func (l *PreimageLoader) loadPrecompilePreimage(proof *ProofData) (*types.PreimageOracleData, error) {
inputKey := preimage.Keccak256Key(proof.OracleKey).PreimageKey()
input, err := l.getPreimage(inputKey)
if err != nil {
......
package cannon
package utils
import (
"crypto/sha256"
......@@ -20,15 +20,15 @@ import (
)
func TestPreimageLoader_NoPreimage(t *testing.T) {
loader := newPreimageLoader(kvstore.NewMemKV().Get)
actual, err := loader.LoadPreimage(&proofData{})
loader := NewPreimageLoader(kvstore.NewMemKV().Get)
actual, err := loader.LoadPreimage(&ProofData{})
require.NoError(t, err)
require.Nil(t, actual)
}
func TestPreimageLoader_LocalPreimage(t *testing.T) {
loader := newPreimageLoader(kvstore.NewMemKV().Get)
proof := &proofData{
loader := NewPreimageLoader(kvstore.NewMemKV().Get)
proof := &ProofData{
OracleKey: common.Hash{byte(preimage.LocalKeyType), 0xaa, 0xbb}.Bytes(),
OracleValue: nil,
OracleOffset: 4,
......@@ -48,8 +48,8 @@ func TestPreimageLoader_SimpleTypes(t *testing.T) {
for _, keyType := range tests {
keyType := keyType
t.Run(fmt.Sprintf("type-%v", keyType), func(t *testing.T) {
loader := newPreimageLoader(kvstore.NewMemKV().Get)
proof := &proofData{
loader := NewPreimageLoader(kvstore.NewMemKV().Get)
proof := &ProofData{
OracleKey: common.Hash{byte(keyType), 0xaa, 0xbb}.Bytes(),
OracleValue: []byte{1, 2, 3, 4, 5, 6},
OracleOffset: 3,
......@@ -82,7 +82,7 @@ func TestPreimageLoader_BlobPreimage(t *testing.T) {
binary.BigEndian.PutUint64(keyBuf[72:], fieldIndex)
key := preimage.BlobKey(crypto.Keccak256Hash(keyBuf)).PreimageKey()
proof := &proofData{
proof := &ProofData{
OracleKey: key[:],
OracleValue: elementDataWithLengthPrefix,
OracleOffset: 4,
......@@ -90,8 +90,8 @@ func TestPreimageLoader_BlobPreimage(t *testing.T) {
t.Run("NoKeyPreimage", func(t *testing.T) {
kv := kvstore.NewMemKV()
loader := newPreimageLoader(kv.Get)
proof := &proofData{
loader := NewPreimageLoader(kv.Get)
proof := &ProofData{
OracleKey: common.Hash{byte(preimage.BlobKeyType), 0xaf}.Bytes(),
OracleValue: proof.OracleValue,
OracleOffset: proof.OracleOffset,
......@@ -102,8 +102,8 @@ func TestPreimageLoader_BlobPreimage(t *testing.T) {
t.Run("InvalidKeyPreimage", func(t *testing.T) {
kv := kvstore.NewMemKV()
loader := newPreimageLoader(kv.Get)
proof := &proofData{
loader := NewPreimageLoader(kv.Get)
proof := &ProofData{
OracleKey: common.Hash{byte(preimage.BlobKeyType), 0xad}.Bytes(),
OracleValue: proof.OracleValue,
OracleOffset: proof.OracleOffset,
......@@ -115,8 +115,8 @@ func TestPreimageLoader_BlobPreimage(t *testing.T) {
t.Run("MissingBlobs", func(t *testing.T) {
kv := kvstore.NewMemKV()
loader := newPreimageLoader(kv.Get)
proof := &proofData{
loader := NewPreimageLoader(kv.Get)
proof := &ProofData{
OracleKey: common.Hash{byte(preimage.BlobKeyType), 0xae}.Bytes(),
OracleValue: proof.OracleValue,
OracleOffset: proof.OracleOffset,
......@@ -128,7 +128,7 @@ func TestPreimageLoader_BlobPreimage(t *testing.T) {
t.Run("Valid", func(t *testing.T) {
kv := kvstore.NewMemKV()
loader := newPreimageLoader(kv.Get)
loader := NewPreimageLoader(kv.Get)
storeBlob(t, kv, gokzg4844.KZGCommitment(commitment), blob)
actual, err := loader.LoadPreimage(proof)
require.NoError(t, err)
......@@ -155,19 +155,19 @@ func TestPreimageLoader_BlobPreimage(t *testing.T) {
func TestPreimageLoader_PrecompilePreimage(t *testing.T) {
input := []byte("test input")
key := preimage.PrecompileKey(crypto.Keccak256Hash(input)).PreimageKey()
proof := &proofData{
proof := &ProofData{
OracleKey: key[:],
}
t.Run("NoInputPreimage", func(t *testing.T) {
kv := kvstore.NewMemKV()
loader := newPreimageLoader(kv.Get)
loader := NewPreimageLoader(kv.Get)
_, err := loader.LoadPreimage(proof)
require.ErrorIs(t, err, kvstore.ErrNotFound)
})
t.Run("Valid", func(t *testing.T) {
kv := kvstore.NewMemKV()
loader := newPreimageLoader(kv.Get)
loader := NewPreimageLoader(kv.Get)
require.NoError(t, kv.Put(preimage.Keccak256Key(proof.OracleKey).PreimageKey(), input))
actual, err := loader.LoadPreimage(proof)
require.NoError(t, err)
......
package utils
import (
"context"
"encoding/json"
"fmt"
"path/filepath"
"strconv"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
const (
ProofsDir = "proofs"
diskStateCache = "state.json.gz"
)
type ProofData struct {
ClaimValue common.Hash `json:"post"`
StateData hexutil.Bytes `json:"state-data"`
ProofData hexutil.Bytes `json:"proof-data"`
OracleKey hexutil.Bytes `json:"oracle-key,omitempty"`
OracleValue hexutil.Bytes `json:"oracle-value,omitempty"`
OracleOffset uint32 `json:"oracle-offset,omitempty"`
}
type ProofGenerator interface {
// GenerateProof executes FPVM binary to generate a proof at the specified trace index in dataDir.
GenerateProof(ctx context.Context, dataDir string, proofAt uint64) error
}
type diskStateCacheObj struct {
Step uint64 `json:"step"`
}
// ReadLastStep reads the tracked last step from disk.
func ReadLastStep(dir string) (uint64, error) {
state := diskStateCacheObj{}
file, err := ioutil.OpenDecompressed(filepath.Join(dir, diskStateCache))
if err != nil {
return 0, err
}
defer file.Close()
err = json.NewDecoder(file).Decode(&state)
if err != nil {
return 0, err
}
return state.Step, nil
}
// WriteLastStep writes the last step and proof to disk as a persistent cache.
func WriteLastStep(dir string, proof *ProofData, step uint64) error {
state := diskStateCacheObj{Step: step}
lastStepFile := filepath.Join(dir, diskStateCache)
if err := ioutil.WriteCompressedJson(lastStepFile, state); err != nil {
return fmt.Errorf("failed to write last step to %v: %w", lastStepFile, err)
}
if err := ioutil.WriteCompressedJson(filepath.Join(dir, ProofsDir, fmt.Sprintf("%d.json.gz", step)), proof); err != nil {
return fmt.Errorf("failed to write proof: %w", err)
}
return nil
}
// below methods and definitions are only to be used for testing
type preimageOpts []string
type PreimageOpt func() preimageOpts
func PreimageLoad(key preimage.Key, offset uint32) PreimageOpt {
return func() preimageOpts {
return []string{"--stop-at-preimage", fmt.Sprintf("%v@%v", common.Hash(key.PreimageKey()).Hex(), offset)}
}
}
func FirstPreimageLoadOfType(preimageType string) PreimageOpt {
return func() preimageOpts {
return []string{"--stop-at-preimage-type", preimageType}
}
}
func FirstKeccakPreimageLoad() PreimageOpt {
return FirstPreimageLoadOfType("keccak")
}
func FirstPrecompilePreimageLoad() PreimageOpt {
return FirstPreimageLoadOfType("precompile")
}
func PreimageLargerThan(size int) PreimageOpt {
return func() preimageOpts {
return []string{"--stop-at-preimage-larger-than", strconv.Itoa(size)}
}
}
......@@ -18,6 +18,7 @@ var (
const (
CannonGameType uint32 = 0
PermissionedGameType uint32 = 1
AsteriscGameType uint32 = 2
AlphabetGameType uint32 = 255
)
......
......@@ -37,6 +37,7 @@ type Metricer interface {
RecordGameStep()
RecordGameMove()
RecordCannonExecutionTime(t float64)
RecordAsteriscExecutionTime(t float64)
RecordClaimResolutionTime(t float64)
RecordGameActTime(t float64)
......@@ -85,9 +86,10 @@ type Metrics struct {
moves prometheus.Counter
steps prometheus.Counter
cannonExecutionTime prometheus.Histogram
claimResolutionTime prometheus.Histogram
gameActTime prometheus.Histogram
claimResolutionTime prometheus.Histogram
gameActTime prometheus.Histogram
cannonExecutionTime prometheus.Histogram
asteriscExecutionTime prometheus.Histogram
trackedGames prometheus.GaugeVec
inflightGames prometheus.Gauge
......@@ -165,6 +167,14 @@ func NewMetrics() *Metrics {
[]float64{1.0, 2.0, 5.0, 10.0},
prometheus.ExponentialBuckets(30.0, 2.0, 14)...),
}),
asteriscExecutionTime: factory.NewHistogram(prometheus.HistogramOpts{
Namespace: Namespace,
Name: "asterisc_execution_time",
Help: "Time (in seconds) to execute asterisc",
Buckets: append(
[]float64{1.0, 10.0},
prometheus.ExponentialBuckets(30.0, 2.0, 14)...),
}),
bondClaimFailures: factory.NewCounter(prometheus.CounterOpts{
Namespace: Namespace,
Name: "claim_failures",
......@@ -261,6 +271,10 @@ func (m *Metrics) RecordCannonExecutionTime(t float64) {
m.cannonExecutionTime.Observe(t)
}
func (m *Metrics) RecordAsteriscExecutionTime(t float64) {
m.asteriscExecutionTime.Observe(t)
}
func (m *Metrics) RecordClaimResolutionTime(t float64) {
m.claimResolutionTime.Observe(t)
}
......
......@@ -36,9 +36,10 @@ func (*NoopMetricsImpl) RecordPreimageChallengeFailed() {}
func (*NoopMetricsImpl) RecordBondClaimFailed() {}
func (*NoopMetricsImpl) RecordBondClaimed(uint64) {}
func (*NoopMetricsImpl) RecordCannonExecutionTime(t float64) {}
func (*NoopMetricsImpl) RecordClaimResolutionTime(t float64) {}
func (*NoopMetricsImpl) RecordGameActTime(t float64) {}
func (*NoopMetricsImpl) RecordCannonExecutionTime(t float64) {}
func (*NoopMetricsImpl) RecordAsteriscExecutionTime(t float64) {}
func (*NoopMetricsImpl) RecordClaimResolutionTime(t float64) {}
func (*NoopMetricsImpl) RecordGameActTime(t float64) {}
func (*NoopMetricsImpl) RecordGamesStatus(inProgress, defenderWon, challengerWon int) {}
......
......@@ -50,7 +50,7 @@ func (g *GameCallerCreator) CreateContract(game gameTypes.GameMetadata) (GameCal
return fdg, nil
}
switch game.GameType {
case faultTypes.CannonGameType, faultTypes.AlphabetGameType:
case faultTypes.CannonGameType, faultTypes.AsteriscGameType, faultTypes.AlphabetGameType:
fdg, err := contracts.NewFaultDisputeGameContract(g.m, game.Proxy, g.caller)
if err != nil {
return nil, fmt.Errorf("failed to create FaultDisputeGameContract: %w", err)
......
......@@ -29,14 +29,18 @@ func TestMetadataCreator_CreateContract(t *testing.T) {
name: "validCannonGameType",
game: types.GameMetadata{GameType: faultTypes.CannonGameType, Proxy: fdgAddr},
},
{
name: "validAsteriscGameType",
game: types.GameMetadata{GameType: faultTypes.AsteriscGameType, Proxy: fdgAddr},
},
{
name: "validAlphabetGameType",
game: types.GameMetadata{GameType: faultTypes.AlphabetGameType, Proxy: fdgAddr},
},
{
name: "InvalidGameType",
game: types.GameMetadata{GameType: 2, Proxy: fdgAddr},
expectedErr: fmt.Errorf("unsupported game type: 2"),
game: types.GameMetadata{GameType: 3, Proxy: fdgAddr},
expectedErr: fmt.Errorf("unsupported game type: 3"),
},
}
......
......@@ -93,7 +93,7 @@ func applyCannonConfig(
l2Endpoint string,
) {
require := require.New(t)
c.CannonL2 = l2Endpoint
c.L2Rpc = l2Endpoint
root := findMonorepoRoot(t)
c.CannonBin = root + "cannon/bin/cannon"
c.CannonServer = root + "op-program/bin/op-program"
......
......@@ -17,6 +17,7 @@ import (
"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/split"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
......@@ -130,7 +131,7 @@ func (g *OutputCannonGameHelper) CreateStepPreimageLoadCheck(ctx context.Context
// 2. Descending the execution game tree to reach the step that loads the preimage
// 3. Asserting that the preimage was indeed loaded by an honest challenger (assuming the preimage is not preloaded)
// This expects an odd execution game depth in order for the honest challenger to step on our leaf claim
func (g *OutputCannonGameHelper) ChallengeToPreimageLoad(ctx context.Context, outputRootClaim *ClaimHelper, challengerKey *ecdsa.PrivateKey, preimage cannon.PreimageOpt, preimageCheck PreimageLoadCheck, preloadPreimage bool) {
func (g *OutputCannonGameHelper) ChallengeToPreimageLoad(ctx context.Context, outputRootClaim *ClaimHelper, challengerKey *ecdsa.PrivateKey, preimage utils.PreimageOpt, preimageCheck PreimageLoadCheck, preloadPreimage bool) {
// Identifying the first state transition that loads a global preimage
provider, _ := g.createCannonTraceProvider(ctx, "sequencer", outputRootClaim, challenger.WithPrivKey(challengerKey))
targetTraceIndex, err := provider.FindStep(ctx, 0, preimage)
......@@ -229,7 +230,7 @@ func (g *OutputCannonGameHelper) VerifyPreimage(ctx context.Context, outputRootC
start := uint64(0)
found := false
for offset := uint32(0); ; offset += 4 {
preimageOpt := cannon.PreimageLoad(preimageKey, offset)
preimageOpt := utils.PreimageLoad(preimageKey, offset)
g.t.Logf("Searching for step with key %x and offset %v", preimageKey.PreimageKey(), offset)
targetTraceIndex, err := provider.FindStep(ctx, start, preimageOpt)
if errors.Is(err, io.EOF) {
......@@ -310,7 +311,7 @@ func (g *OutputCannonGameHelper) createCannonTraceProvider(ctx context.Context,
agreed, disputed, err := outputs.FetchProposals(ctx, outputProvider, pre, post)
g.require.NoError(err)
g.t.Logf("Using trace between blocks %v and %v\n", agreed.L2BlockNumber, disputed.L2BlockNumber)
localInputs, err := cannon.FetchLocalInputsFromProposals(ctx, l1Head.Hash, l2Client, agreed, disputed)
localInputs, err := utils.FetchLocalInputsFromProposals(ctx, l1Head.Hash, l2Client, agreed, disputed)
g.require.NoError(err, "Failed to fetch local inputs")
localContext = outputs.CreateLocalContext(pre, post)
dir := filepath.Join(cfg.Datadir, "cannon-trace")
......
......@@ -6,7 +6,7 @@ import (
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
......@@ -252,14 +252,14 @@ func TestOutputCannonStepWithLargePreimage(t *testing.T) {
// execution game. We then move to challenge it to induce a large preimage load.
sender := sys.Cfg.Secrets.Addresses().Alice
preimageLoadCheck := game.CreateStepLargePreimageLoadCheck(ctx, sender)
game.ChallengeToPreimageLoad(ctx, outputRootClaim, sys.Cfg.Secrets.Alice, cannon.PreimageLargerThan(preimage.MinPreimageSize), preimageLoadCheck, false)
game.ChallengeToPreimageLoad(ctx, outputRootClaim, sys.Cfg.Secrets.Alice, utils.PreimageLargerThan(preimage.MinPreimageSize), preimageLoadCheck, false)
// The above method already verified the image was uploaded and step called successfully
// So we don't waste time resolving the game - that's tested elsewhere.
}
func TestOutputCannonStepWithPreimage(t *testing.T) {
op_e2e.InitParallel(t, op_e2e.UsesCannon)
testPreimageStep := func(t *testing.T, preimageType cannon.PreimageOpt, preloadPreimage bool) {
testPreimageStep := func(t *testing.T, preimageType utils.PreimageOpt, preloadPreimage bool) {
op_e2e.InitParallel(t, op_e2e.UsesCannon)
ctx := context.Background()
......@@ -290,12 +290,12 @@ func TestOutputCannonStepWithPreimage(t *testing.T) {
for _, preimageType := range preimageConditions {
preimageType := preimageType
t.Run("non-existing preimage-"+preimageType, func(t *testing.T) {
testPreimageStep(t, cannon.FirstPreimageLoadOfType(preimageType), false)
testPreimageStep(t, utils.FirstPreimageLoadOfType(preimageType), false)
})
}
// Only test pre-existing images with one type to save runtime
t.Run("preimage already exists", func(t *testing.T) {
testPreimageStep(t, cannon.FirstKeccakPreimageLoad(), true)
testPreimageStep(t, utils.FirstKeccakPreimageLoad(), true)
})
}
......@@ -334,7 +334,7 @@ func TestOutputCannonStepWithKZGPointEvaluation(t *testing.T) {
// Now the honest challenger is positioned as the defender of the execution game
// We then move to challenge it to induce a preimage load
preimageLoadCheck := game.CreateStepPreimageLoadCheck(ctx)
game.ChallengeToPreimageLoad(ctx, outputRootClaim, sys.Cfg.Secrets.Alice, cannon.FirstPrecompilePreimageLoad(), preimageLoadCheck, preloadPreimage)
game.ChallengeToPreimageLoad(ctx, outputRootClaim, sys.Cfg.Secrets.Alice, utils.FirstPrecompilePreimageLoad(), preimageLoadCheck, preloadPreimage)
// The above method already verified the image was uploaded and step called successfully
// So we don't waste time resolving the game - that's tested elsewhere.
}
......
......@@ -11,6 +11,7 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
......@@ -122,7 +123,7 @@ func TestPrecompiles(t *testing.T) {
require.NoError(t, err, "get l1 head block")
l1Head := l1HeadBlock.Hash()
inputs := cannon.LocalGameInputs{
inputs := utils.LocalGameInputs{
L1Head: l1Head,
L2Head: l2Head,
L2Claim: common.Hash(l2Claim),
......@@ -134,7 +135,7 @@ func TestPrecompiles(t *testing.T) {
}
}
func runCannon(t *testing.T, ctx context.Context, sys *op_e2e.System, inputs cannon.LocalGameInputs, l2Node string) {
func runCannon(t *testing.T, ctx context.Context, sys *op_e2e.System, inputs utils.LocalGameInputs, l2Node string) {
l1Endpoint := sys.NodeEndpoint("l1")
l1Beacon := sys.L1BeaconEndpoint()
cannonOpts := challenger.WithCannon(t, sys.RollupCfg(), sys.L2Genesis(), sys.RollupEndpoint(l2Node), sys.NodeEndpoint(l2Node))
......
......@@ -36,6 +36,18 @@ func OpenCompressed(file string, flag int, perm os.FileMode) (io.WriteCloser, er
return CompressByFileType(file, out), nil
}
// WriteCompressedBytes writes a byte slice to the specified file.
// If the filename ends with .gz, a byte slice is compressed and written.
func WriteCompressedBytes(file string, data []byte, flag int, perm os.FileMode) error {
out, err := OpenCompressed(file, flag, perm)
if err != nil {
return err
}
defer out.Close()
_, err = out.Write(data)
return err
}
// WriteCompressedJson writes the object to the specified file as a compressed json object
// if the filename ends with .gz.
func WriteCompressedJson(file string, obj any) error {
......
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