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 ...@@ -45,7 +45,7 @@ make op-challenger op-program cannon
--cannon-bin ./cannon/bin/cannon \ --cannon-bin ./cannon/bin/cannon \
--cannon-server ./op-program/bin/op-program \ --cannon-server ./op-program/bin/op-program \
--cannon-prestate <PRESTATE> \ --cannon-prestate <PRESTATE> \
--cannon-l2 <L2_URL> \ --l2-rpc <L2_URL> \
--private-key <PRIVATE_KEY> --private-key <PRIVATE_KEY>
``` ```
......
...@@ -44,7 +44,7 @@ DISPUTE_GAME_FACTORY=$(jq -r .DisputeGameFactoryProxy .devnet/addresses.json) ...@@ -44,7 +44,7 @@ DISPUTE_GAME_FACTORY=$(jq -r .DisputeGameFactoryProxy .devnet/addresses.json)
--cannon-bin ./cannon/bin/cannon \ --cannon-bin ./cannon/bin/cannon \
--cannon-server ./op-program/bin/op-program \ --cannon-server ./op-program/bin/op-program \
--cannon-prestate ./op-program/bin/prestate.json \ --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" \ --mnemonic "test test test test test test test test test test test junk" \
--hd-path "m/44'/60'/0'/0/8" \ --hd-path "m/44'/60'/0'/0/8" \
--num-confirmations 1 --num-confirmations 1
......
...@@ -22,13 +22,17 @@ var ( ...@@ -22,13 +22,17 @@ var (
l1Beacon = "http://example.com:9000" l1Beacon = "http://example.com:9000"
gameFactoryAddressValue = "0xbb00000000000000000000000000000000000000" gameFactoryAddressValue = "0xbb00000000000000000000000000000000000000"
cannonNetwork = "op-mainnet" cannonNetwork = "op-mainnet"
otherCannonNetwork = "op-sepolia" testNetwork = "op-sepolia"
l2Rpc = "http://example.com:9545"
cannonBin = "./bin/cannon" cannonBin = "./bin/cannon"
cannonServer = "./bin/op-program" cannonServer = "./bin/op-program"
cannonPreState = "./pre.json" cannonPreState = "./pre.json"
datadir = "./test_data" datadir = "./test_data"
cannonL2 = "http://example.com:9545"
rollupRpc = "http://example.com:8555" rollupRpc = "http://example.com:8555"
asteriscNetwork = "op-mainnet"
asteriscBin = "./bin/asterisc"
asteriscServer = "./bin/op-program"
asteriscPreState = "./pre.json"
) )
func TestLogLevel(t *testing.T) { func TestLogLevel(t *testing.T) {
...@@ -111,14 +115,18 @@ func TestMultipleTraceTypes(t *testing.T) { ...@@ -111,14 +115,18 @@ func TestMultipleTraceTypes(t *testing.T) {
t.Run("WithAllOptions", func(t *testing.T) { t.Run("WithAllOptions", func(t *testing.T) {
argsMap := requiredArgs(config.TraceTypeCannon) argsMap := requiredArgs(config.TraceTypeCannon)
addRequiredOutputArgs(argsMap) addRequiredOutputArgs(argsMap)
// Add Asterisc required flags
addRequiredAsteriscArgs(argsMap)
args := toArgList(argsMap) args := toArgList(argsMap)
// Add extra trace types (cannon is already specified) // Add extra trace types (cannon is already specified)
args = append(args, args = append(args,
"--trace-type", config.TraceTypeAlphabet.String()) "--trace-type", config.TraceTypeAlphabet.String())
args = append(args, args = append(args,
"--trace-type", config.TraceTypePermissioned.String()) "--trace-type", config.TraceTypePermissioned.String())
args = append(args,
"--trace-type", config.TraceTypeAsterisc.String())
cfg := configForArgs(t, args) cfg := configForArgs(t, args)
require.Equal(t, []config.TraceType{config.TraceTypeCannon, config.TraceTypeAlphabet, config.TraceTypePermissioned}, cfg.TraceTypes) require.Equal(t, []config.TraceType{config.TraceTypeCannon, config.TraceTypeAlphabet, config.TraceTypePermissioned, config.TraceTypeAsterisc}, cfg.TraceTypes)
}) })
t.Run("WithSomeOptions", func(t *testing.T) { t.Run("WithSomeOptions", func(t *testing.T) {
argsMap := requiredArgs(config.TraceTypeCannon) argsMap := requiredArgs(config.TraceTypeCannon)
...@@ -243,6 +251,178 @@ func TestPollInterval(t *testing.T) { ...@@ -243,6 +251,178 @@ func TestPollInterval(t *testing.T) {
}) })
} }
func TestAsteriscRequiredArgs(t *testing.T) {
for _, traceType := range []config.TraceType{config.TraceTypeAsterisc} {
traceType := traceType
t.Run(fmt.Sprintf("TestAsteriscBin-%v", traceType), func(t *testing.T) {
t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--asterisc-bin"))
})
t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag asterisc-bin is required", addRequiredArgsExcept(traceType, "--asterisc-bin"))
})
t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--asterisc-bin", "--asterisc-bin=./asterisc"))
require.Equal(t, "./asterisc", cfg.AsteriscBin)
})
})
t.Run(fmt.Sprintf("TestAsteriscServer-%v", traceType), func(t *testing.T) {
t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--asterisc-server"))
})
t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag asterisc-server is required", addRequiredArgsExcept(traceType, "--asterisc-server"))
})
t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--asterisc-server", "--asterisc-server=./op-program"))
require.Equal(t, "./op-program", cfg.AsteriscServer)
})
})
t.Run(fmt.Sprintf("TestAsteriscAbsolutePrestate-%v", traceType), func(t *testing.T) {
t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--asterisc-prestate"))
})
t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag asterisc-prestate is required", addRequiredArgsExcept(traceType, "--asterisc-prestate"))
})
t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--asterisc-prestate", "--asterisc-prestate=./pre.json"))
require.Equal(t, "./pre.json", cfg.AsteriscAbsolutePreState)
})
})
t.Run(fmt.Sprintf("TestL2Rpc-%v", traceType), func(t *testing.T) {
t.Run("NotRequiredForAlphabetTraceLegacy", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--cannon-l2"))
})
t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--l2-rpc"))
})
t.Run("RequiredForAsteriscTrace", func(t *testing.T) {
verifyArgsInvalid(t, "flag l2-rpc is required", addRequiredArgsExcept(traceType, "--l2-rpc"))
})
t.Run("ValidLegacy", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--l2-rpc", fmt.Sprintf("--cannon-l2=%s", l2Rpc)))
require.Equal(t, l2Rpc, cfg.L2Rpc)
})
t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs(traceType))
require.Equal(t, l2Rpc, cfg.L2Rpc)
})
t.Run("InvalidUsingBothFlags", func(t *testing.T) {
verifyArgsInvalid(t, "flag cannon-l2 and l2-rpc must not be both set", addRequiredArgsExcept(traceType, "", fmt.Sprintf("--cannon-l2=%s", l2Rpc)))
})
})
t.Run(fmt.Sprintf("TestAsteriscSnapshotFreq-%v", traceType), func(t *testing.T) {
t.Run("UsesDefault", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs(traceType))
require.Equal(t, config.DefaultAsteriscSnapshotFreq, cfg.AsteriscSnapshotFreq)
})
t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs(traceType, "--asterisc-snapshot-freq=1234"))
require.Equal(t, uint(1234), cfg.AsteriscSnapshotFreq)
})
t.Run("Invalid", func(t *testing.T) {
verifyArgsInvalid(t, "invalid value \"abc\" for flag -asterisc-snapshot-freq",
addRequiredArgs(traceType, "--asterisc-snapshot-freq=abc"))
})
})
t.Run(fmt.Sprintf("TestAsteriscInfoFreq-%v", traceType), func(t *testing.T) {
t.Run("UsesDefault", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs(traceType))
require.Equal(t, config.DefaultAsteriscInfoFreq, cfg.AsteriscInfoFreq)
})
t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs(traceType, "--asterisc-info-freq=1234"))
require.Equal(t, uint(1234), cfg.AsteriscInfoFreq)
})
t.Run("Invalid", func(t *testing.T) {
verifyArgsInvalid(t, "invalid value \"abc\" for flag -asterisc-info-freq",
addRequiredArgs(traceType, "--asterisc-info-freq=abc"))
})
})
t.Run(fmt.Sprintf("TestRequireEitherAsteriscNetworkOrRollupAndGenesis-%v", traceType), func(t *testing.T) {
verifyArgsInvalid(
t,
"flag asterisc-network or asterisc-rollup-config and asterisc-l2-genesis is required",
addRequiredArgsExcept(traceType, "--asterisc-network"))
verifyArgsInvalid(
t,
"flag asterisc-network or asterisc-rollup-config and asterisc-l2-genesis is required",
addRequiredArgsExcept(traceType, "--asterisc-network", "--asterisc-rollup-config=rollup.json"))
verifyArgsInvalid(
t,
"flag asterisc-network or asterisc-rollup-config and asterisc-l2-genesis is required",
addRequiredArgsExcept(traceType, "--asterisc-network", "--asterisc-l2-genesis=gensis.json"))
})
t.Run(fmt.Sprintf("TestMustNotSpecifyNetworkAndRollup-%v", traceType), func(t *testing.T) {
verifyArgsInvalid(
t,
"flag asterisc-network can not be used with asterisc-rollup-config and asterisc-l2-genesis",
addRequiredArgsExcept(traceType, "--asterisc-network",
"--asterisc-network", asteriscNetwork, "--asterisc-rollup-config=rollup.json"))
})
t.Run(fmt.Sprintf("TestAsteriscNetwork-%v", traceType), func(t *testing.T) {
t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--asterisc-network"))
})
t.Run("NotRequiredWhenRollupAndGenesIsSpecified", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(traceType, "--asterisc-network",
"--asterisc-rollup-config=rollup.json", "--asterisc-l2-genesis=genesis.json"))
})
t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--asterisc-network", "--asterisc-network", testNetwork))
require.Equal(t, testNetwork, cfg.AsteriscNetwork)
})
})
t.Run(fmt.Sprintf("TestAsteriscRollupConfig-%v", traceType), func(t *testing.T) {
t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--asterisc-rollup-config"))
})
t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--asterisc-network", "--asterisc-rollup-config=rollup.json", "--asterisc-l2-genesis=genesis.json"))
require.Equal(t, "rollup.json", cfg.AsteriscRollupConfigPath)
})
})
t.Run(fmt.Sprintf("TestAsteriscL2Genesis-%v", traceType), func(t *testing.T) {
t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--asterisc-l2-genesis"))
})
t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--asterisc-network", "--asterisc-rollup-config=rollup.json", "--asterisc-l2-genesis=genesis.json"))
require.Equal(t, "genesis.json", cfg.AsteriscL2GenesisPath)
})
})
}
}
func TestCannonRequiredArgs(t *testing.T) { func TestCannonRequiredArgs(t *testing.T) {
for _, traceType := range []config.TraceType{config.TraceTypeCannon, config.TraceTypePermissioned} { for _, traceType := range []config.TraceType{config.TraceTypeCannon, config.TraceTypePermissioned} {
traceType := traceType traceType := traceType
...@@ -291,18 +471,27 @@ func TestCannonRequiredArgs(t *testing.T) { ...@@ -291,18 +471,27 @@ func TestCannonRequiredArgs(t *testing.T) {
}) })
}) })
t.Run(fmt.Sprintf("TestCannonL2-%v", traceType), func(t *testing.T) { t.Run(fmt.Sprintf("TestL2Rpc-%v", traceType), func(t *testing.T) {
t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { t.Run("NotRequiredForAlphabetTraceLegacy", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--cannon-l2")) configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--cannon-l2"))
}) })
t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--l2-rpc"))
})
t.Run("RequiredForCannonTrace", func(t *testing.T) { t.Run("RequiredForCannonTrace", func(t *testing.T) {
verifyArgsInvalid(t, "flag cannon-l2 is required", addRequiredArgsExcept(traceType, "--cannon-l2")) verifyArgsInvalid(t, "flag l2-rpc is required", addRequiredArgsExcept(traceType, "--l2-rpc"))
})
t.Run("ValidLegacy", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--l2-rpc", fmt.Sprintf("--cannon-l2=%s", l2Rpc)))
require.Equal(t, l2Rpc, cfg.L2Rpc)
}) })
t.Run("Valid", func(t *testing.T) { t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs(traceType)) cfg := configForArgs(t, addRequiredArgs(traceType))
require.Equal(t, cannonL2, cfg.CannonL2) require.Equal(t, l2Rpc, cfg.L2Rpc)
}) })
}) })
...@@ -374,8 +563,8 @@ func TestCannonRequiredArgs(t *testing.T) { ...@@ -374,8 +563,8 @@ func TestCannonRequiredArgs(t *testing.T) {
}) })
t.Run("Valid", func(t *testing.T) { t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--cannon-network", "--cannon-network", otherCannonNetwork)) cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--cannon-network", "--cannon-network", testNetwork))
require.Equal(t, otherCannonNetwork, cfg.CannonNetwork) require.Equal(t, testNetwork, cfg.CannonNetwork)
}) })
}) })
...@@ -390,7 +579,7 @@ func TestCannonRequiredArgs(t *testing.T) { ...@@ -390,7 +579,7 @@ func TestCannonRequiredArgs(t *testing.T) {
}) })
}) })
t.Run(fmt.Sprintf("TestCannonL2Genesis-%v", traceType), func(t *testing.T) { t.Run(fmt.Sprintf("TestCannonL2qGenesis-%v", traceType), func(t *testing.T) {
t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--cannon-l2-genesis")) configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--cannon-l2-genesis"))
}) })
...@@ -560,6 +749,8 @@ func requiredArgs(traceType config.TraceType) map[string]string { ...@@ -560,6 +749,8 @@ func requiredArgs(traceType config.TraceType) map[string]string {
switch traceType { switch traceType {
case config.TraceTypeCannon, config.TraceTypePermissioned: case config.TraceTypeCannon, config.TraceTypePermissioned:
addRequiredCannonArgs(args) addRequiredCannonArgs(args)
case config.TraceTypeAsterisc:
addRequiredAsteriscArgs(args)
case config.TraceTypeAlphabet: case config.TraceTypeAlphabet:
addRequiredOutputArgs(args) addRequiredOutputArgs(args)
} }
...@@ -571,7 +762,16 @@ func addRequiredCannonArgs(args map[string]string) { ...@@ -571,7 +762,16 @@ func addRequiredCannonArgs(args map[string]string) {
args["--cannon-bin"] = cannonBin args["--cannon-bin"] = cannonBin
args["--cannon-server"] = cannonServer args["--cannon-server"] = cannonServer
args["--cannon-prestate"] = cannonPreState args["--cannon-prestate"] = cannonPreState
args["--cannon-l2"] = cannonL2 args["--l2-rpc"] = l2Rpc
addRequiredOutputArgs(args)
}
func addRequiredAsteriscArgs(args map[string]string) {
args["--asterisc-network"] = asteriscNetwork
args["--asterisc-bin"] = asteriscBin
args["--asterisc-server"] = asteriscServer
args["--asterisc-prestate"] = asteriscPreState
args["--l2-rpc"] = l2Rpc
addRequiredOutputArgs(args) addRequiredOutputArgs(args)
} }
......
...@@ -19,7 +19,7 @@ var ( ...@@ -19,7 +19,7 @@ var (
ErrMissingTraceType = errors.New("no supported trace types specified") ErrMissingTraceType = errors.New("no supported trace types specified")
ErrMissingDatadir = errors.New("missing datadir") ErrMissingDatadir = errors.New("missing datadir")
ErrMaxConcurrencyZero = errors.New("max concurrency must not be 0") ErrMaxConcurrencyZero = errors.New("max concurrency must not be 0")
ErrMissingCannonL2 = errors.New("missing cannon L2") ErrMissingL2Rpc = errors.New("missing L2 rpc url")
ErrMissingCannonBin = errors.New("missing cannon bin") ErrMissingCannonBin = errors.New("missing cannon bin")
ErrMissingCannonServer = errors.New("missing cannon server") ErrMissingCannonServer = errors.New("missing cannon server")
ErrMissingCannonAbsolutePreState = errors.New("missing cannon absolute pre-state") ErrMissingCannonAbsolutePreState = errors.New("missing cannon absolute pre-state")
...@@ -34,6 +34,17 @@ var ( ...@@ -34,6 +34,17 @@ var (
ErrCannonNetworkAndL2Genesis = errors.New("only specify one of network or l2 genesis path") ErrCannonNetworkAndL2Genesis = errors.New("only specify one of network or l2 genesis path")
ErrCannonNetworkUnknown = errors.New("unknown cannon network") ErrCannonNetworkUnknown = errors.New("unknown cannon network")
ErrMissingRollupRpc = errors.New("missing rollup rpc url") 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 type TraceType string
...@@ -41,10 +52,11 @@ type TraceType string ...@@ -41,10 +52,11 @@ type TraceType string
const ( const (
TraceTypeAlphabet TraceType = "alphabet" TraceTypeAlphabet TraceType = "alphabet"
TraceTypeCannon TraceType = "cannon" TraceTypeCannon TraceType = "cannon"
TraceTypeAsterisc TraceType = "asterisc"
TraceTypePermissioned TraceType = "permissioned" TraceTypePermissioned TraceType = "permissioned"
) )
var TraceTypes = []TraceType{TraceTypeAlphabet, TraceTypeCannon, TraceTypePermissioned} var TraceTypes = []TraceType{TraceTypeAlphabet, TraceTypeCannon, TraceTypePermissioned, TraceTypeAsterisc}
func (t TraceType) String() string { func (t TraceType) String() string {
return string(t) return string(t)
...@@ -74,9 +86,11 @@ func ValidTraceType(value TraceType) bool { ...@@ -74,9 +86,11 @@ func ValidTraceType(value TraceType) bool {
} }
const ( const (
DefaultPollInterval = time.Second * 12 DefaultPollInterval = time.Second * 12
DefaultCannonSnapshotFreq = uint(1_000_000_000) DefaultCannonSnapshotFreq = uint(1_000_000_000)
DefaultCannonInfoFreq = uint(10_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 // DefaultGameWindow is the default maximum time duration in the past
// that the challenger will look for games to progress. // that the challenger will look for games to progress.
// The default value is 15 days, which is an 8 day resolution buffer // The default value is 15 days, which is an 8 day resolution buffer
...@@ -105,8 +119,9 @@ type Config struct { ...@@ -105,8 +119,9 @@ type Config struct {
TraceTypes []TraceType // Type of traces supported TraceTypes []TraceType // Type of traces supported
// Specific to the output cannon trace type RollupRpc string // L2 Rollup RPC Url
RollupRpc string
L2Rpc string // L2 RPC Url
// Specific to the cannon trace provider // Specific to the cannon trace provider
CannonBin string // Path to the cannon executable to run when generating trace data CannonBin string // Path to the cannon executable to run when generating trace data
...@@ -115,9 +130,18 @@ type Config struct { ...@@ -115,9 +130,18 @@ type Config struct {
CannonNetwork string CannonNetwork string
CannonRollupConfigPath string CannonRollupConfigPath string
CannonL2GenesisPath string CannonL2GenesisPath string
CannonL2 string // L2 RPC Url CannonSnapshotFreq uint // Frequency of snapshots to create when executing cannon (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)
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) MaxPendingTx uint64 // Maximum number of pending transactions (0 == no limit)
...@@ -150,9 +174,11 @@ func NewConfig( ...@@ -150,9 +174,11 @@ func NewConfig(
Datadir: datadir, Datadir: datadir,
CannonSnapshotFreq: DefaultCannonSnapshotFreq, CannonSnapshotFreq: DefaultCannonSnapshotFreq,
CannonInfoFreq: DefaultCannonInfoFreq, CannonInfoFreq: DefaultCannonInfoFreq,
GameWindow: DefaultGameWindow, AsteriscSnapshotFreq: DefaultAsteriscSnapshotFreq,
AsteriscInfoFreq: DefaultAsteriscInfoFreq,
GameWindow: DefaultGameWindow,
} }
} }
...@@ -210,8 +236,8 @@ func (c Config) Check() error { ...@@ -210,8 +236,8 @@ func (c Config) Check() error {
if c.CannonAbsolutePreState == "" { if c.CannonAbsolutePreState == "" {
return ErrMissingCannonAbsolutePreState return ErrMissingCannonAbsolutePreState
} }
if c.CannonL2 == "" { if c.L2Rpc == "" {
return ErrMissingCannonL2 return ErrMissingL2Rpc
} }
if c.CannonSnapshotFreq == 0 { if c.CannonSnapshotFreq == 0 {
return ErrMissingCannonSnapshotFreq return ErrMissingCannonSnapshotFreq
...@@ -220,6 +246,44 @@ func (c Config) Check() error { ...@@ -220,6 +246,44 @@ func (c Config) Check() error {
return ErrMissingCannonInfoFreq 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 { if err := c.TxMgrConfig.Check(); err != nil {
return err return err
} }
......
...@@ -20,20 +20,40 @@ var ( ...@@ -20,20 +20,40 @@ var (
validCannonNetwork = "mainnet" validCannonNetwork = "mainnet"
validCannonAbsolutPreState = "pre.json" validCannonAbsolutPreState = "pre.json"
validDatadir = "/tmp/data" validDatadir = "/tmp/data"
validCannonL2 = "http://localhost:9545" validL2Rpc = "http://localhost:9545"
validRollupRpc = "http://localhost:8555" validRollupRpc = "http://localhost:8555"
validAsteriscBin = "./bin/asterisc"
validAsteriscOpProgramBin = "./bin/op-program"
validAsteriscNetwork = "mainnet"
validAsteriscAbsolutPreState = "pre.json"
) )
var cannonTraceTypes = []TraceType{TraceTypeCannon, TraceTypePermissioned} 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 { func validConfig(traceType TraceType) Config {
cfg := NewConfig(validGameFactoryAddress, validL1EthRpc, validL1BeaconUrl, validDatadir, traceType) cfg := NewConfig(validGameFactoryAddress, validL1EthRpc, validL1BeaconUrl, validDatadir, traceType)
if traceType == TraceTypeCannon || traceType == TraceTypePermissioned { if traceType == TraceTypeCannon || traceType == TraceTypePermissioned {
cfg.CannonBin = validCannonBin applyValidConfigForCannon(&cfg)
cfg.CannonServer = validCannonOpProgramBin }
cfg.CannonAbsolutePreState = validCannonAbsolutPreState if traceType == TraceTypeAsterisc {
cfg.CannonL2 = validCannonL2 applyValidConfigForAsterisc(&cfg)
cfg.CannonNetwork = validCannonNetwork
} }
cfg.RollupRpc = validRollupRpc cfg.RollupRpc = validRollupRpc
return cfg return cfg
...@@ -110,10 +130,10 @@ func TestCannonRequiredArgs(t *testing.T) { ...@@ -110,10 +130,10 @@ func TestCannonRequiredArgs(t *testing.T) {
require.ErrorIs(t, config.Check(), ErrMissingCannonAbsolutePreState) 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 := validConfig(traceType)
config.CannonL2 = "" config.L2Rpc = ""
require.ErrorIs(t, config.Check(), ErrMissingCannonL2) require.ErrorIs(t, config.Check(), ErrMissingL2Rpc)
}) })
t.Run(fmt.Sprintf("TestCannonSnapshotFreq-%v", traceType), func(t *testing.T) { t.Run(fmt.Sprintf("TestCannonSnapshotFreq-%v", traceType), func(t *testing.T) {
...@@ -209,7 +229,7 @@ func TestRollupRpcRequired(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 := validConfig(TraceTypeCannon)
cfg.TraceTypes = []TraceType{TraceTypeCannon, TraceTypeAlphabet} cfg.TraceTypes = []TraceType{TraceTypeCannon, TraceTypeAlphabet}
// Set all required options and check its valid // Set all required options and check its valid
...@@ -217,11 +237,56 @@ func TestRequireConfigForMultipleTraceTypes(t *testing.T) { ...@@ -217,11 +237,56 @@ func TestRequireConfigForMultipleTraceTypes(t *testing.T) {
require.NoError(t, cfg.Check()) require.NoError(t, cfg.Check())
// Require cannon specific args // Require cannon specific args
cfg.CannonL2 = "" cfg.CannonAbsolutePreState = ""
require.ErrorIs(t, cfg.Check(), ErrMissingCannonL2) require.ErrorIs(t, cfg.Check(), ErrMissingCannonAbsolutePreState)
cfg.CannonL2 = validCannonL2 cfg.CannonAbsolutePreState = validCannonAbsolutPreState
// Require output cannon specific args // Require output cannon specific args
cfg.RollupRpc = "" cfg.RollupRpc = ""
require.ErrorIs(t, cfg.Check(), ErrMissingRollupRpc) 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 ( ...@@ -71,6 +71,11 @@ var (
EnvVars: prefixEnvVars("MAX_CONCURRENCY"), EnvVars: prefixEnvVars("MAX_CONCURRENCY"),
Value: uint(runtime.NumCPU()), 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{ MaxPendingTransactionsFlag = &cli.Uint64Flag{
Name: "max-pending-tx", Name: "max-pending-tx",
Usage: "The maximum number of pending transactions. 0 for no limit.", Usage: "The maximum number of pending transactions. 0 for no limit.",
...@@ -123,7 +128,7 @@ var ( ...@@ -123,7 +128,7 @@ var (
} }
CannonL2Flag = &cli.StringFlag{ CannonL2Flag = &cli.StringFlag{
Name: "cannon-l2", 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"), EnvVars: prefixEnvVars("CANNON_L2"),
} }
CannonSnapshotFreqFlag = &cli.UintFlag{ CannonSnapshotFreqFlag = &cli.UintFlag{
...@@ -138,6 +143,51 @@ var ( ...@@ -138,6 +143,51 @@ var (
EnvVars: prefixEnvVars("CANNON_INFO_FREQ"), EnvVars: prefixEnvVars("CANNON_INFO_FREQ"),
Value: config.DefaultCannonInfoFreq, 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{ GameWindowFlag = &cli.DurationFlag{
Name: "game-window", Name: "game-window",
Usage: "The time window which the challenger will look for games to progress and claim bonds. " + Usage: "The time window which the challenger will look for games to progress and claim bonds. " +
...@@ -171,6 +221,7 @@ var requiredFlags = []cli.Flag{ ...@@ -171,6 +221,7 @@ var requiredFlags = []cli.Flag{
var optionalFlags = []cli.Flag{ var optionalFlags = []cli.Flag{
TraceTypeFlag, TraceTypeFlag,
MaxConcurrencyFlag, MaxConcurrencyFlag,
L2RpcFlag,
MaxPendingTransactionsFlag, MaxPendingTransactionsFlag,
HTTPPollInterval, HTTPPollInterval,
AdditionalBondClaimants, AdditionalBondClaimants,
...@@ -184,6 +235,14 @@ var optionalFlags = []cli.Flag{ ...@@ -184,6 +235,14 @@ var optionalFlags = []cli.Flag{
CannonL2Flag, CannonL2Flag,
CannonSnapshotFreqFlag, CannonSnapshotFreqFlag,
CannonInfoFreqFlag, CannonInfoFreqFlag,
AsteriscNetworkFlag,
AsteriscRollupConfigFlag,
AsteriscL2GenesisFlag,
AsteriscBinFlag,
AsteriscServerFlag,
AsteriscPreStateFlag,
AsteriscSnapshotFreqFlag,
AsteriscInfoFreqFlag,
GameWindowFlag, GameWindowFlag,
SelectiveClaimResolutionFlag, SelectiveClaimResolutionFlag,
UnsafeAllowInvalidPrestate, UnsafeAllowInvalidPrestate,
...@@ -221,8 +280,36 @@ func CheckCannonFlags(ctx *cli.Context) error { ...@@ -221,8 +280,36 @@ func CheckCannonFlags(ctx *cli.Context) error {
if !ctx.IsSet(CannonPreStateFlag.Name) { if !ctx.IsSet(CannonPreStateFlag.Name) {
return fmt.Errorf("flag %s is required", CannonPreStateFlag.Name) return fmt.Errorf("flag %s is required", CannonPreStateFlag.Name)
} }
if !ctx.IsSet(CannonL2Flag.Name) { // CannonL2Flag is checked because it is an alias with L2RpcFlag
return fmt.Errorf("flag %s is required", CannonL2Flag.Name) 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 return nil
} }
...@@ -239,6 +326,10 @@ func CheckRequired(ctx *cli.Context, traceTypes []config.TraceType) error { ...@@ -239,6 +326,10 @@ func CheckRequired(ctx *cli.Context, traceTypes []config.TraceType) error {
if err := CheckCannonFlags(ctx); err != nil { if err := CheckCannonFlags(ctx); err != nil {
return err return err
} }
case config.TraceTypeAsterisc:
if err := CheckAsteriscFlags(ctx); err != nil {
return err
}
case config.TraceTypeAlphabet: case config.TraceTypeAlphabet:
default: default:
return fmt.Errorf("invalid trace type. must be one of %v", config.TraceTypes) 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) { ...@@ -261,6 +352,20 @@ func parseTraceTypes(ctx *cli.Context) ([]config.TraceType, error) {
return traceTypes, nil 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. // NewConfigFromCLI parses the Config from the provided flags or environment variables.
func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) { func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
traceTypes, err := parseTraceTypes(ctx) traceTypes, err := parseTraceTypes(ctx)
...@@ -303,6 +408,10 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) { ...@@ -303,6 +408,10 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
claimants = append(claimants, claimant) claimants = append(claimants, claimant)
} }
} }
l2Rpc, err := getL2Rpc(ctx)
if err != nil {
return nil, err
}
return &config.Config{ return &config.Config{
// Required Flags // Required Flags
L1EthRpc: ctx.String(L1EthRpcFlag.Name), L1EthRpc: ctx.String(L1EthRpcFlag.Name),
...@@ -312,6 +421,7 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) { ...@@ -312,6 +421,7 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
GameAllowlist: allowedGames, GameAllowlist: allowedGames,
GameWindow: ctx.Duration(GameWindowFlag.Name), GameWindow: ctx.Duration(GameWindowFlag.Name),
MaxConcurrency: maxConcurrency, MaxConcurrency: maxConcurrency,
L2Rpc: l2Rpc,
MaxPendingTx: ctx.Uint64(MaxPendingTransactionsFlag.Name), MaxPendingTx: ctx.Uint64(MaxPendingTransactionsFlag.Name),
PollInterval: ctx.Duration(HTTPPollInterval.Name), PollInterval: ctx.Duration(HTTPPollInterval.Name),
AdditionalBondClaimants: claimants, AdditionalBondClaimants: claimants,
...@@ -323,9 +433,16 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) { ...@@ -323,9 +433,16 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
CannonServer: ctx.String(CannonServerFlag.Name), CannonServer: ctx.String(CannonServerFlag.Name),
CannonAbsolutePreState: ctx.String(CannonPreStateFlag.Name), CannonAbsolutePreState: ctx.String(CannonPreStateFlag.Name),
Datadir: ctx.String(DatadirFlag.Name), Datadir: ctx.String(DatadirFlag.Name),
CannonL2: ctx.String(CannonL2Flag.Name),
CannonSnapshotFreq: ctx.Uint(CannonSnapshotFreqFlag.Name), CannonSnapshotFreq: ctx.Uint(CannonSnapshotFreqFlag.Name),
CannonInfoFreq: ctx.Uint(CannonInfoFreqFlag.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, TxMgrConfig: txMgrConfig,
MetricsConfig: metricsConfig, MetricsConfig: metricsConfig,
PprofConfig: pprofConfig, PprofConfig: pprofConfig,
......
...@@ -8,8 +8,10 @@ import ( ...@@ -8,8 +8,10 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/claims" "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/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/alphabet" "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/cannon"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/outputs" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/outputs"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types" keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/scheduler" "github.com/ethereum-optimism/optimism/op-challenger/game/scheduler"
...@@ -58,10 +60,10 @@ func RegisterGameTypes( ...@@ -58,10 +60,10 @@ func RegisterGameTypes(
) (CloseFunc, error) { ) (CloseFunc, error) {
var closer CloseFunc var closer CloseFunc
var l2Client *ethclient.Client var l2Client *ethclient.Client
if cfg.TraceTypeEnabled(config.TraceTypeCannon) || cfg.TraceTypeEnabled(config.TraceTypePermissioned) { if cfg.TraceTypeEnabled(config.TraceTypeCannon) || cfg.TraceTypeEnabled(config.TraceTypePermissioned) || cfg.TraceTypeEnabled(config.TraceTypeAsterisc) {
l2, err := ethclient.DialContext(ctx, cfg.CannonL2) l2, err := ethclient.DialContext(ctx, cfg.L2Rpc)
if err != nil { 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 l2Client = l2
closer = l2Client.Close closer = l2Client.Close
...@@ -78,6 +80,11 @@ func RegisterGameTypes( ...@@ -78,6 +80,11 @@ func RegisterGameTypes(
return nil, fmt.Errorf("failed to register permissioned cannon game type: %w", err) 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 cfg.TraceTypeEnabled(config.TraceTypeAlphabet) {
if err := registerAlphabet(registry, oracles, ctx, systemClock, l1Clock, logger, m, syncValidator, rollupClient, txSender, gameFactory, caller, l1HeaderSource, selective, claimants); err != nil { 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) 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 ...@@ -167,6 +174,74 @@ func registerOracle(ctx context.Context, m metrics.Metricer, oracles OracleRegis
return nil 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( func registerCannon(
gameType uint32, gameType uint32,
registry Registry, registry Registry,
...@@ -182,7 +257,7 @@ func registerCannon( ...@@ -182,7 +257,7 @@ func registerCannon(
txSender TxSender, txSender TxSender,
gameFactory *contracts.DisputeGameFactoryContract, gameFactory *contracts.DisputeGameFactoryContract,
caller *batching.MultiCaller, caller *batching.MultiCaller,
l2Client cannon.L2HeaderSource, l2Client utils.L2HeaderSource,
l1HeaderSource L1HeaderSource, l1HeaderSource L1HeaderSource,
selective bool, selective bool,
claimants []common.Address, 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
}
package asterisc
import (
"context"
"embed"
"encoding/json"
"fmt"
"math"
"math/big"
"os"
"path/filepath"
"testing"
"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"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
//go:embed test_data
var testData embed.FS
func PositionFromTraceIndex(provider *AsteriscTraceProvider, idx *big.Int) types.Position {
return types.NewPosition(provider.gameDepth, idx)
}
func TestGet(t *testing.T) {
dataDir, prestate := setupTestData(t)
t.Run("ExistingProof", func(t *testing.T) {
provider, generator := setupWithTestData(t, dataDir, prestate)
value, err := provider.Get(context.Background(), PositionFromTraceIndex(provider, common.Big0))
require.NoError(t, err)
require.Equal(t, common.HexToHash("0x034689707b571db46b32c9e433def18e648f4e1fa9e5abd4012e7913031bfc10"), value)
require.Empty(t, generator.generated)
})
t.Run("ErrorsTraceIndexOutOfBounds", func(t *testing.T) {
provider, generator := setupWithTestData(t, dataDir, prestate)
largePosition := PositionFromTraceIndex(provider, new(big.Int).Mul(new(big.Int).SetUint64(math.MaxUint64), big.NewInt(2)))
_, err := provider.Get(context.Background(), largePosition)
require.ErrorContains(t, err, "trace index out of bounds")
require.Empty(t, generator.generated)
})
t.Run("MissingPostHash", func(t *testing.T) {
provider, generator := setupWithTestData(t, dataDir, prestate)
_, err := provider.Get(context.Background(), PositionFromTraceIndex(provider, big.NewInt(1)))
require.ErrorContains(t, err, "missing post hash")
require.Empty(t, generator.generated)
})
t.Run("IgnoreUnknownFields", func(t *testing.T) {
provider, generator := setupWithTestData(t, dataDir, prestate)
value, err := provider.Get(context.Background(), PositionFromTraceIndex(provider, big.NewInt(2)))
require.NoError(t, err)
expected := common.HexToHash("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
require.Equal(t, expected, value)
require.Empty(t, generator.generated)
})
}
func TestGetStepData(t *testing.T) {
t.Run("ExistingProof", func(t *testing.T) {
dataDir, prestate := setupTestData(t)
provider, generator := setupWithTestData(t, dataDir, prestate)
value, proof, data, err := provider.GetStepData(context.Background(), PositionFromTraceIndex(provider, common.Big0))
require.NoError(t, err)
expected := common.FromHex("0x354cfaf28a5b60c3f64f22f9f171b64aa067f90c6de6c96f725f44c5cf9f8ac1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080e080000000000000000000000007f0000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
require.Equal(t, expected, value)
expectedProof := common.FromHex("0x000000000000000003350100930581006f00800100000000970f000067800f01000000000000000097c2ffff938282676780020000000000032581009308e0050e1893682c323d6695396f1122b3cb562af8c65cab19978c9246434fda0536c90ca1cfabf684ebce3ad9fbd54000a2b258f8d0e447c1bb6f7e97de47aadfc12cd7b6f466bfd024daa905886c5f638f4692d843709e6c1c0d9eb2e251c626d53d15e04b59735fe0781bc4357a4243fbc28e6981902a8c2669a2d6456f7a964423db5d1585da978861f8b84067654b29490275c82b54083ee09c82eb7aa9ae693911226bb8297ad82c0963ae943f22d0c6086f4f14437e4d1c87ceb17e68caf5eaec77f14b46225b417d2191ca7b49564c896836a95ad4e9c383bd1c8ff9d8e888c64fb3836daa9535e58372e9646b7b144219980a4389aca5da241c3ec11fbc9297bd7a94ac671ccec288604c23a0072b0c1ed069198959cacdc2574aff65b7eceffc391e21778a1775deceb3ec0990836df98d98a4f3f0dc854587230fbf59e4daa60e8240d74caf90f7e2cd014c1d5d707b2e44269d9a9caf133882fe1ebb2f4237f6282abe89639b357e9231418d0c41373229ae9edfa6815bec484cb79772c9e2a7d80912123558f79b539bb45d435f2a4446970f1e2123494740285cec3491b0a41a9fd7403bdc8cd239a87508039a77b48ee39a951a8bd196b583de2b93444aafd456d0cd92050fa6a816d5183c1d75e96df540c8ac3bb8638b971f0cf3fb5b4a321487a1c8992b921de110f3d5bbb87369b25fe743ad7e789ca52d9f9fe62ccb103b78fe65eaa2cd47895022c590639c8f0c6a3999d8a5c71ed94d355815851b479f8d93eae90822294c96b39724b33491f8497b0bf7e1b995b37e4d759ff8a7958d194da6e00c475a6ddcf6efcb5fb4bb383c9b273da18d01e000dbe9c65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2f4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd95a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e3774df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652cdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618db8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea32293237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d7358448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a927ae5ba08d7291c96c8cbddcc148bf48a6d68c7974b94356f53754ef6171d757bf558bebd2ceec7f3c5dce04a4782f88c2c6036ae78ee206d0bc5289d20461a2e21908c2968c0699040a6fd866a577a99a9d2ec88745c815fd4a472c789244daae824d72ddc272aab68a8c3022e36f10454437c1886f3ff9927b64f232df414f27e429a4bef3083bc31a671d046ea5c1f5b8c3094d72868d9dfdc12c7334ac5f743cc5c365a9a6a15c1f240ac25880c7a9d1de290696cb766074a1d83d9278164adcf616c3bfabf63999a01966c998b7bb572774035a63ead49da73b5987f34775786645d0c5dd7c04a2f8a75dcae085213652f5bce3ea8b9b9bedd1cab3c5e9b88b152c9b8a7b79637d35911848b0c41e7cc7cca2ab4fe9a15f9c38bb4bb9390c4e2d8ce834ffd7a6cd85d7113d4521abb857774845c4291e6f6d010d97e3185bc799d83e3bb31501b3da786680df30fbc18eb41cbce611e8c0e9c72f69571ca10d3ef857d04d9c03ead7c6317d797a090fa1271ad9c7addfbcb412e9643d4fb33b1809c42623f474055fa9400a2027a7a885c8dfa4efe20666b4ee27d7529c134d7f28d53f175f6bf4b62faa2110d5b76f0f770c15e628181c1fcc18f970a9c34d24b2fc8c50ca9c07a7156ef4e5ff4bdf002eda0b11c1d359d0b59a54680704dbb9db631457879b27e0dfdbe50158fd9cf9b4cf77605c4ac4c95bd65fc9f6f9295a686647cb999090819cda700820c282c613cedcd218540bbc6f37b01c6567c4a1ea624f092a3a5cca2d6f0f0db231972fce627f0ecca0dee60f17551c5f8fdaeb5ab560b2ceb781cdb339361a0fbee1b9dffad59115138c8d6a70dda9ccc1bf0bbdd7fee15764845db875f6432559ff8dbc9055324431bc34e5b93d15da307317849eccd90c0c7b98870b9317c15a5959dcfb84c76dcc908c4fe6ba92126339bf06e458f6646df5e83ba7c3d35bc263b3222c8e9040068847749ca8e8f95045e4342aeb521eb3a5587ec268ed3aa6faf32b62b0bc41a9d549521f406fc3ec7d4dabb75e0d3e144d7cc882372d13746b6dcd481b1b229bcaec9f7422cdfb84e35c5d92171376cae5c86300822d729cd3a8479583bef09527027dba5f11263c5cbbeb3834b7a5c1cba9aa5fee0c95ec3f17a33ec3d8047fff799187f5ae2040bbe913c226c34c9fbe4389dd728984257a816892b3cae3e43191dd291f0eb50000000000000000420000000000000035000000000000000000000000000000060000000000000000100000000000001900000000000000480000000000001050edbc06b4bfc3ee108b66f7a8f772ca4d90e1a085f4a8398505920f7465bb44b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d3021ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a193440eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f839867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756afcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf8923490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99cc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8beccda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d22733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981fe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0b46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0c65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2f4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd95a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e3774df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652cdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618db8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea32293237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d7358448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a927ae5ba08d7291c96c8cbddcc148bf48a6d68c7974b94356f53754ef6171d757bf558bebd2ceec7f3c5dce04a4782f88c2c6036ae78ee206d0bc5289d20461a2e21908c2968c0699040a6fd866a577a99a9d2ec88745c815fd4a472c789244daae824d72ddc272aab68a8c3022e36f10454437c1886f3ff9927b64f232df414f27e429a4bef3083bc31a671d046ea5c1f5b8c3094d72868d9dfdc12c7334ac5f743cc5c365a9a6a15c1f240ac25880c7a9d1de290696cb766074a1d83d9278164adcf616c3bfabf63999a01966c998b7bb572774035a63ead49da73b5987f34775786645d0c5dd7c04a2f8a75dcae085213652f5bce3ea8b9b9bedd1cab3c5e9b88b152c9b8a7b79637d35911848b0c41e7cc7cca2ab4fe9a15f9c38bb4bb9390c4e2d8ce834ffd7a6cd85d7113d4521abb857774845c4291e6f6d010d97e3185bc799d83e3bb31501b3da786680df30fbc18eb41cbce611e8c0e9c72f69571ca10d3ef857d04d9c03ead7c6317d797a090fa1271ad9c7addfbcb412e9643d4fb33b1809c42623f474055fa9400a2027a7a885c8dfa4efe20666b4ee27d7529c134d7f28d53f175f6bf4b62faa2110d5b76f0f770c15e628181c1fcc18f970a9c34d24b2fc8c50ca9c07a7156ef4e5ff4bdf002eda0b11c1d359d0b59a54680704dbb9db631457879b27e0dfdbe50158fd9cf9b4cf77605c4ac4c95bd65fc9f6f9295a686647cb999090819cda700820c282c613cedcd218540bbc6f37b01c6567c4a1ea624f092a3a5cca2d6f0f0db231972fce627f0ecca0dee60f17551c5f8fdaeb5ab560b2ceb781cdb339361a0fbee1b9dffad59115138c8d6a70dda9ccc1bf0bbdd7fee15764845db875f6432559ff8dbc9055324431bc34e5b93d15da307317849eccd90c0c7b98870b9317c15a5959dcfb84c76dcc908c4fe6ba92126339bf06e458f6646df5e83ba7c3d35bc263b3222c8e9040068847749ca8e8f95045e4342aeb521eb3a5587ec268ed3aa6faf32b62b0bc41a9d549521f406fc30f3e39c5412c30550d1d07fb07ff0e546fbeea1988f6658f04a9b19693e5b99d84e35c5d92171376cae5c86300822d729cd3a8479583bef09527027dba5f11263c5cbbeb3834b7a5c1cba9aa5fee0c95ec3f17a33ec3d8047fff799187f5ae2040bbe913c226c34c9fbe4389dd728984257a816892b3cae3e43191dd291f0eb5")
require.Equal(t, expectedProof, proof)
// TODO: Need to add some oracle data
require.Nil(t, data)
require.Empty(t, generator.generated)
})
t.Run("ErrorsTraceIndexOutOfBounds", func(t *testing.T) {
dataDir, prestate := setupTestData(t)
provider, generator := setupWithTestData(t, dataDir, prestate)
largePosition := PositionFromTraceIndex(provider, new(big.Int).Mul(new(big.Int).SetUint64(math.MaxUint64), big.NewInt(2)))
_, _, _, err := provider.GetStepData(context.Background(), largePosition)
require.ErrorContains(t, err, "trace index out of bounds")
require.Empty(t, generator.generated)
})
t.Run("GenerateProof", func(t *testing.T) {
dataDir, prestate := setupTestData(t)
provider, generator := setupWithTestData(t, dataDir, prestate)
generator.finalState = &VMState{
Step: 10,
Exited: true,
Witness: make([]byte, asteriscWitnessLen),
}
generator.proof = &utils.ProofData{
ClaimValue: common.Hash{0xaa},
StateData: []byte{0xbb},
ProofData: []byte{0xcc},
OracleKey: common.Hash{0xdd}.Bytes(),
OracleValue: []byte{0xdd},
OracleOffset: 10,
}
preimage, proof, data, err := provider.GetStepData(context.Background(), PositionFromTraceIndex(provider, big.NewInt(4)))
require.NoError(t, err)
require.Contains(t, generator.generated, 4, "should have tried to generate the proof")
require.EqualValues(t, generator.proof.StateData, preimage)
require.EqualValues(t, generator.proof.ProofData, proof)
expectedData := types.NewPreimageOracleData(generator.proof.OracleKey, generator.proof.OracleValue, generator.proof.OracleOffset)
require.EqualValues(t, expectedData, data)
})
t.Run("ProofAfterEndOfTrace", func(t *testing.T) {
dataDir, prestate := setupTestData(t)
provider, generator := setupWithTestData(t, dataDir, prestate)
generator.finalState = &VMState{
Step: 10,
Exited: true,
Witness: make([]byte, asteriscWitnessLen),
}
generator.proof = &utils.ProofData{
ClaimValue: common.Hash{0xaa},
StateData: []byte{0xbb},
ProofData: []byte{0xcc},
OracleKey: common.Hash{0xdd}.Bytes(),
OracleValue: []byte{0xdd},
OracleOffset: 10,
}
preimage, proof, data, err := provider.GetStepData(context.Background(), PositionFromTraceIndex(provider, big.NewInt(7000)))
require.NoError(t, err)
require.Contains(t, generator.generated, 7000, "should have tried to generate the proof")
witness := generator.finalState.Witness
require.EqualValues(t, witness, preimage)
require.Equal(t, []byte{}, proof)
require.Nil(t, data)
})
t.Run("ReadLastStepFromDisk", func(t *testing.T) {
dataDir, prestate := setupTestData(t)
provider, initGenerator := setupWithTestData(t, dataDir, prestate)
initGenerator.finalState = &VMState{
Step: 10,
Exited: true,
Witness: make([]byte, asteriscWitnessLen),
}
initGenerator.proof = &utils.ProofData{
ClaimValue: common.Hash{0xaa},
StateData: []byte{0xbb},
ProofData: []byte{0xcc},
OracleKey: common.Hash{0xdd}.Bytes(),
OracleValue: []byte{0xdd},
OracleOffset: 10,
}
_, _, _, err := provider.GetStepData(context.Background(), PositionFromTraceIndex(provider, big.NewInt(7000)))
require.NoError(t, err)
require.Contains(t, initGenerator.generated, 7000, "should have tried to generate the proof")
provider, generator := setupWithTestData(t, dataDir, prestate)
generator.finalState = &VMState{
Step: 10,
Exited: true,
Witness: make([]byte, asteriscWitnessLen),
}
generator.proof = &utils.ProofData{
ClaimValue: common.Hash{0xaa},
StateData: []byte{0xbb},
ProofData: []byte{0xcc},
}
preimage, proof, data, err := provider.GetStepData(context.Background(), PositionFromTraceIndex(provider, big.NewInt(7000)))
require.NoError(t, err)
require.Empty(t, generator.generated, "should not have to generate the proof again")
require.EqualValues(t, initGenerator.finalState.Witness, preimage)
require.Empty(t, proof)
require.Nil(t, data)
})
t.Run("MissingStateData", func(t *testing.T) {
dataDir, prestate := setupTestData(t)
provider, generator := setupWithTestData(t, dataDir, prestate)
_, _, _, err := provider.GetStepData(context.Background(), PositionFromTraceIndex(provider, big.NewInt(1)))
require.ErrorContains(t, err, "missing state data")
require.Empty(t, generator.generated)
})
t.Run("IgnoreUnknownFields", func(t *testing.T) {
dataDir, prestate := setupTestData(t)
provider, generator := setupWithTestData(t, dataDir, prestate)
value, proof, data, err := provider.GetStepData(context.Background(), PositionFromTraceIndex(provider, big.NewInt(2)))
require.NoError(t, err)
expected := common.FromHex("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc")
require.Equal(t, expected, value)
expectedProof := common.FromHex("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd")
require.Equal(t, expectedProof, proof)
require.Empty(t, generator.generated)
require.Nil(t, data)
})
}
func setupTestData(t *testing.T) (string, string) {
srcDir := filepath.Join("test_data", "proofs")
entries, err := testData.ReadDir(srcDir)
require.NoError(t, err)
dataDir := t.TempDir()
require.NoError(t, os.Mkdir(filepath.Join(dataDir, proofsDir), 0o777))
for _, entry := range entries {
path := filepath.Join(srcDir, entry.Name())
file, err := testData.ReadFile(path)
require.NoErrorf(t, err, "reading %v", path)
proofFile := filepath.Join(dataDir, 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"
}
func setupWithTestData(t *testing.T, dataDir string, prestate string) (*AsteriscTraceProvider, *stubGenerator) {
generator := &stubGenerator{}
return &AsteriscTraceProvider{
logger: testlog.Logger(t, log.LevelInfo),
dir: dataDir,
generator: generator,
prestate: filepath.Join(dataDir, prestate),
gameDepth: 63,
}, generator
}
type stubGenerator struct {
generated []int // Using int makes assertions easier
finalState *VMState
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
proofFile = filepath.Join(dir, utils.FinalState)
data, err = json.Marshal(e.finalState)
if err != nil {
return err
}
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)
if err != nil {
return err
}
return ioutil.WriteCompressedBytes(proofFile, data, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0o644)
}
return nil
}
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 ...@@ -2,40 +2,26 @@ package cannon
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"math" "math"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"regexp"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/ethereum-optimism/optimism/op-challenger/config" "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" "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 { type Executor struct {
logger log.Logger logger log.Logger
metrics CannonMetricer metrics CannonMetricer
l1 string l1 string
l1Beacon string l1Beacon string
l2 string l2 string
inputs LocalGameInputs inputs utils.LocalGameInputs
cannon string cannon string
server string server string
network string network string
...@@ -44,17 +30,17 @@ type Executor struct { ...@@ -44,17 +30,17 @@ type Executor struct {
absolutePreState string absolutePreState string
snapshotFreq uint snapshotFreq uint
infoFreq uint infoFreq uint
selectSnapshot snapshotSelect selectSnapshot utils.SnapshotSelect
cmdExecutor cmdExecutor 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{ return &Executor{
logger: logger, logger: logger,
metrics: m, metrics: m,
l1: cfg.L1EthRpc, l1: cfg.L1EthRpc,
l1Beacon: cfg.L1Beacon, l1Beacon: cfg.L1Beacon,
l2: cfg.CannonL2, l2: cfg.L2Rpc,
inputs: inputs, inputs: inputs,
cannon: cfg.CannonBin, cannon: cfg.CannonBin,
server: cfg.CannonServer, server: cfg.CannonServer,
...@@ -64,8 +50,8 @@ func NewExecutor(logger log.Logger, m CannonMetricer, cfg *config.Config, inputs ...@@ -64,8 +50,8 @@ func NewExecutor(logger log.Logger, m CannonMetricer, cfg *config.Config, inputs
absolutePreState: cfg.CannonAbsolutePreState, absolutePreState: cfg.CannonAbsolutePreState,
snapshotFreq: cfg.CannonSnapshotFreq, snapshotFreq: cfg.CannonSnapshotFreq,
infoFreq: cfg.CannonInfoFreq, infoFreq: cfg.CannonInfoFreq,
selectSnapshot: findStartingSnapshot, selectSnapshot: utils.FindStartingSnapshot,
cmdExecutor: runCmd, cmdExecutor: utils.RunCmd,
} }
} }
...@@ -75,18 +61,17 @@ func (e *Executor) GenerateProof(ctx context.Context, dir string, i uint64) erro ...@@ -75,18 +61,17 @@ func (e *Executor) GenerateProof(ctx context.Context, dir string, i uint64) erro
return e.generateProof(ctx, dir, i, i) return e.generateProof(ctx, dir, i, i)
} }
// generateProofOrUntilPreimageRead executes cannon to generate a proof at the specified trace index, // generateProof executes cannon from the specified starting trace index until the end trace index.
// or until a non-local preimage read is encountered if untilPreimageRead is true.
// The proof is stored at the specified directory. // The proof is stored at the specified directory.
func (e *Executor) generateProof(ctx context.Context, dir string, begin uint64, end uint64, extraCannonArgs ...string) error { 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) start, err := e.selectSnapshot(e.logger, snapshotDir, e.absolutePreState, begin)
if err != nil { if err != nil {
return fmt.Errorf("find starting snapshot: %w", err) return fmt.Errorf("find starting snapshot: %w", err)
} }
proofDir := filepath.Join(dir, proofsDir) proofDir := filepath.Join(dir, utils.ProofsDir)
dataDir := preimageDir(dir) dataDir := utils.PreimageDir(dir)
lastGeneratedState := filepath.Join(dir, finalState) lastGeneratedState := filepath.Join(dir, utils.FinalState)
args := []string{ args := []string{
"run", "run",
"--input", start, "--input", start,
...@@ -140,58 +125,3 @@ func (e *Executor) generateProof(ctx context.Context, dir string, begin uint64, ...@@ -140,58 +125,3 @@ func (e *Executor) generateProof(ctx context.Context, dir string, begin uint64,
e.metrics.RecordCannonExecutionTime(time.Since(execStart).Seconds()) e.metrics.RecordCannonExecutionTime(time.Since(execStart).Seconds())
return err 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 ( ...@@ -11,6 +11,7 @@ import (
"time" "time"
"github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
"github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -25,14 +26,14 @@ func TestGenerateProof(t *testing.T) { ...@@ -25,14 +26,14 @@ func TestGenerateProof(t *testing.T) {
tempDir := t.TempDir() tempDir := t.TempDir()
dir := filepath.Join(tempDir, "gameDir") dir := filepath.Join(tempDir, "gameDir")
cfg := config.NewConfig(common.Address{0xbb}, "http://localhost:8888", "http://localhost:9000", tempDir, config.TraceTypeCannon) 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.CannonAbsolutePreState = "pre.json"
cfg.CannonBin = "./bin/cannon" cfg.CannonBin = "./bin/cannon"
cfg.CannonServer = "./bin/op-program" cfg.CannonServer = "./bin/op-program"
cfg.CannonL2 = "http://localhost:9999"
cfg.CannonSnapshotFreq = 500 cfg.CannonSnapshotFreq = 500
cfg.CannonInfoFreq = 900 cfg.CannonInfoFreq = 900
inputs := LocalGameInputs{ inputs := utils.LocalGameInputs{
L1Head: common.Hash{0x11}, L1Head: common.Hash{0x11},
L2Head: common.Hash{0x22}, L2Head: common.Hash{0x22},
L2OutputRoot: common.Hash{0x33}, L2OutputRoot: common.Hash{0x33},
...@@ -73,15 +74,15 @@ func TestGenerateProof(t *testing.T) { ...@@ -73,15 +74,15 @@ func TestGenerateProof(t *testing.T) {
cfg.CannonRollupConfigPath = "" cfg.CannonRollupConfigPath = ""
cfg.CannonL2GenesisPath = "" cfg.CannonL2GenesisPath = ""
binary, subcommand, args := captureExec(t, cfg, 150_000_000) binary, subcommand, args := captureExec(t, cfg, 150_000_000)
require.DirExists(t, filepath.Join(dir, preimagesDir)) require.DirExists(t, filepath.Join(dir, utils.PreimagesDir))
require.DirExists(t, filepath.Join(dir, proofsDir)) require.DirExists(t, filepath.Join(dir, utils.ProofsDir))
require.DirExists(t, filepath.Join(dir, snapsDir)) require.DirExists(t, filepath.Join(dir, utils.SnapsDir))
require.Equal(t, cfg.CannonBin, binary) require.Equal(t, cfg.CannonBin, binary)
require.Equal(t, "run", subcommand) require.Equal(t, "run", subcommand)
require.Equal(t, input, args["--input"]) require.Equal(t, input, args["--input"])
require.Contains(t, args, "--meta") require.Contains(t, args, "--meta")
require.Equal(t, "", args["--meta"]) require.Equal(t, "", args["--meta"])
require.Equal(t, filepath.Join(dir, finalState), args["--output"]) require.Equal(t, filepath.Join(dir, utils.FinalState), args["--output"])
require.Equal(t, "=150000000", args["--proof-at"]) require.Equal(t, "=150000000", args["--proof-at"])
require.Equal(t, "=150000001", args["--stop-at"]) require.Equal(t, "=150000001", args["--stop-at"])
require.Equal(t, "%500", args["--snapshot-at"]) require.Equal(t, "%500", args["--snapshot-at"])
...@@ -92,10 +93,10 @@ func TestGenerateProof(t *testing.T) { ...@@ -92,10 +93,10 @@ func TestGenerateProof(t *testing.T) {
require.Equal(t, "--server", args[cfg.CannonServer]) require.Equal(t, "--server", args[cfg.CannonServer])
require.Equal(t, cfg.L1EthRpc, args["--l1"]) require.Equal(t, cfg.L1EthRpc, args["--l1"])
require.Equal(t, cfg.L1Beacon, args["--l1.beacon"]) require.Equal(t, cfg.L1Beacon, args["--l1.beacon"])
require.Equal(t, cfg.CannonL2, args["--l2"]) require.Equal(t, cfg.L2Rpc, args["--l2"])
require.Equal(t, filepath.Join(dir, preimagesDir), args["--datadir"]) 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.ProofsDir, "%d.json.gz"), args["--proof-fmt"])
require.Equal(t, filepath.Join(dir, snapsDir, "%d.json.gz"), args["--snapshot-fmt"]) require.Equal(t, filepath.Join(dir, utils.SnapsDir, "%d.json.gz"), args["--snapshot-fmt"])
require.Equal(t, cfg.CannonNetwork, args["--network"]) require.Equal(t, cfg.CannonNetwork, args["--network"])
require.NotContains(t, args, "--rollup.config") require.NotContains(t, args, "--rollup.config")
require.NotContains(t, args, "--l2.genesis") require.NotContains(t, args, "--l2.genesis")
...@@ -137,7 +138,7 @@ func TestRunCmdLogsOutput(t *testing.T) { ...@@ -137,7 +138,7 @@ func TestRunCmdLogsOutput(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() defer cancel()
logger, logs := testlog.CaptureLogger(t, log.LevelInfo) 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) require.NoError(t, err)
levelFilter := testlog.NewLevelFilter(log.LevelInfo) levelFilter := testlog.NewLevelFilter(log.LevelInfo)
msgFilter := testlog.NewMessageFilter("Hello World") msgFilter := testlog.NewMessageFilter("Hello World")
...@@ -157,25 +158,25 @@ func TestFindStartingSnapshot(t *testing.T) { ...@@ -157,25 +158,25 @@ func TestFindStartingSnapshot(t *testing.T) {
t.Run("UsePrestateWhenSnapshotsDirDoesNotExist", func(t *testing.T) { t.Run("UsePrestateWhenSnapshotsDirDoesNotExist", func(t *testing.T) {
dir := t.TempDir() 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.NoError(t, err)
require.Equal(t, execTestCannonPrestate, snapshot) require.Equal(t, execTestCannonPrestate, snapshot)
}) })
t.Run("UsePrestateWhenSnapshotsDirEmpty", func(t *testing.T) { t.Run("UsePrestateWhenSnapshotsDirEmpty", func(t *testing.T) {
dir := withSnapshots(t) dir := withSnapshots(t)
snapshot, err := findStartingSnapshot(logger, dir, execTestCannonPrestate, 1200) snapshot, err := utils.FindStartingSnapshot(logger, dir, execTestCannonPrestate, 1200)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, execTestCannonPrestate, snapshot) require.Equal(t, execTestCannonPrestate, snapshot)
}) })
t.Run("UsePrestateWhenNoSnapshotBeforeTraceIndex", func(t *testing.T) { t.Run("UsePrestateWhenNoSnapshotBeforeTraceIndex", func(t *testing.T) {
dir := withSnapshots(t, "100.json", "200.json") 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.NoError(t, err)
require.Equal(t, execTestCannonPrestate, snapshot) 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.NoError(t, err)
require.Equal(t, execTestCannonPrestate, snapshot) require.Equal(t, execTestCannonPrestate, snapshot)
}) })
...@@ -183,19 +184,19 @@ func TestFindStartingSnapshot(t *testing.T) { ...@@ -183,19 +184,19 @@ func TestFindStartingSnapshot(t *testing.T) {
t.Run("UseClosestAvailableSnapshot", func(t *testing.T) { t.Run("UseClosestAvailableSnapshot", func(t *testing.T) {
dir := withSnapshots(t, "100.json.gz", "123.json.gz", "250.json.gz") 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.NoError(t, err)
require.Equal(t, filepath.Join(dir, "100.json.gz"), snapshot) 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.NoError(t, err)
require.Equal(t, filepath.Join(dir, "100.json.gz"), snapshot) 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.NoError(t, err)
require.Equal(t, filepath.Join(dir, "123.json.gz"), snapshot) 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.NoError(t, err)
require.Equal(t, filepath.Join(dir, "250.json.gz"), snapshot) require.Equal(t, filepath.Join(dir, "250.json.gz"), snapshot)
}) })
...@@ -203,14 +204,14 @@ func TestFindStartingSnapshot(t *testing.T) { ...@@ -203,14 +204,14 @@ func TestFindStartingSnapshot(t *testing.T) {
t.Run("IgnoreDirectories", func(t *testing.T) { t.Run("IgnoreDirectories", func(t *testing.T) {
dir := withSnapshots(t, "100.json.gz") dir := withSnapshots(t, "100.json.gz")
require.NoError(t, os.Mkdir(filepath.Join(dir, "120.json.gz"), 0o777)) 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.NoError(t, err)
require.Equal(t, filepath.Join(dir, "100.json.gz"), snapshot) require.Equal(t, filepath.Join(dir, "100.json.gz"), snapshot)
}) })
t.Run("IgnoreUnexpectedFiles", func(t *testing.T) { t.Run("IgnoreUnexpectedFiles", func(t *testing.T) {
dir := withSnapshots(t, ".file", "100.json.gz", "foo", "bar.json.gz") 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.NoError(t, err)
require.Equal(t, filepath.Join(dir, "100.json.gz"), snapshot) require.Equal(t, filepath.Join(dir, "100.json.gz"), snapshot)
}) })
......
...@@ -60,7 +60,7 @@ func TestAbsolutePreStateCommitment(t *testing.T) { ...@@ -60,7 +60,7 @@ func TestAbsolutePreStateCommitment(t *testing.T) {
}) })
t.Run("CacheAbsolutePreState", func(t *testing.T) { t.Run("CacheAbsolutePreState", func(t *testing.T) {
setupPreState(t, dataDir, "state.json") setupPreState(t, dataDir, prestate)
provider := newCannonPrestateProvider(dataDir, prestate) provider := newCannonPrestateProvider(dataDir, prestate)
first, err := provider.AbsolutePreStateCommitment(context.Background()) first, err := provider.AbsolutePreStateCommitment(context.Background())
require.NoError(t, err) require.NoError(t, err)
......
...@@ -9,11 +9,10 @@ import ( ...@@ -9,11 +9,10 @@ import (
"math" "math"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "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-program/host/kvstore"
"github.com/ethereum-optimism/optimism/op-service/ioutil" "github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -23,57 +22,34 @@ import ( ...@@ -23,57 +22,34 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm" "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 { type CannonMetricer interface {
RecordCannonExecutionTime(t float64) 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 { type CannonTraceProvider struct {
logger log.Logger logger log.Logger
dir string dir string
prestate string prestate string
generator ProofGenerator generator utils.ProofGenerator
gameDepth types.Depth gameDepth types.Depth
preimageLoader *preimageLoader preimageLoader *utils.PreimageLoader
// lastStep stores the last step in the actual trace if known. 0 indicates unknown. // lastStep stores the last step in the actual trace if known. 0 indicates unknown.
// Cached as an optimisation to avoid repeatedly attempting to execute beyond the end of the trace. // Cached as an optimisation to avoid repeatedly attempting to execute beyond the end of the trace.
lastStep uint64 lastStep uint64
} }
func NewTraceProvider(logger log.Logger, m CannonMetricer, cfg *config.Config, localInputs 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{ return &CannonTraceProvider{
logger: logger, logger: logger,
dir: dir, dir: dir,
prestate: cfg.CannonAbsolutePreState, prestate: cfg.CannonAbsolutePreState,
generator: NewExecutor(logger, m, cfg, localInputs), generator: NewExecutor(logger, m, cfg, localInputs),
gameDepth: gameDepth, gameDepth: gameDepth,
preimageLoader: 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) { func (p *CannonTraceProvider) Get(ctx context.Context, pos types.Position) (common.Hash, error) {
traceIndex := pos.TraceIndex(p.gameDepth) traceIndex := pos.TraceIndex(p.gameDepth)
if !traceIndex.IsUint64() { if !traceIndex.IsUint64() {
...@@ -137,10 +113,10 @@ func (p *CannonTraceProvider) AbsolutePreStateCommitment(_ context.Context) (com ...@@ -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 // loadProof will attempt to load or generate the proof data at the specified index
// If the requested index is beyond the end of the actual trace it is extended with no-op instructions. // If the requested index is beyond the end of the actual trace it is extended with no-op instructions.
func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofData, error) { func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*utils.ProofData, error) {
// Attempt to read the last step from disk cache // Attempt to read the last step from disk cache
if p.lastStep == 0 { if p.lastStep == 0 {
step, err := readLastStep(p.dir) step, err := utils.ReadLastStep(p.dir)
if err != nil { if err != nil {
p.logger.Warn("Failed to read last step from disk cache", "err", err) p.logger.Warn("Failed to read last step from disk cache", "err", err)
} else { } else {
...@@ -151,7 +127,7 @@ func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofDa ...@@ -151,7 +127,7 @@ func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofDa
if p.lastStep != 0 && i > p.lastStep { if p.lastStep != 0 && i > p.lastStep {
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) file, err := ioutil.OpenDecompressed(path)
if errors.Is(err, os.ErrNotExist) { if errors.Is(err, os.ErrNotExist) {
if err := p.generator.GenerateProof(ctx, p.dir, i); err != nil { if err := p.generator.GenerateProof(ctx, p.dir, i); err != nil {
...@@ -177,7 +153,7 @@ func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofDa ...@@ -177,7 +153,7 @@ func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofDa
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot hash witness: %w", err) return nil, fmt.Errorf("cannot hash witness: %w", err)
} }
proof := &proofData{ proof := &utils.ProofData{
ClaimValue: witnessHash, ClaimValue: witnessHash,
StateData: hexutil.Bytes(witness), StateData: hexutil.Bytes(witness),
ProofData: []byte{}, ProofData: []byte{},
...@@ -185,7 +161,7 @@ func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofDa ...@@ -185,7 +161,7 @@ func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofDa
OracleValue: nil, OracleValue: nil,
OracleOffset: 0, 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) p.logger.Warn("Failed to write last step to disk cache", "step", p.lastStep)
} }
return proof, nil return proof, nil
...@@ -198,7 +174,7 @@ func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofDa ...@@ -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) return nil, fmt.Errorf("cannot open proof file (%v): %w", path, err)
} }
defer file.Close() defer file.Close()
var proof proofData var proof utils.ProofData
err = json.NewDecoder(file).Decode(&proof) err = json.NewDecoder(file).Decode(&proof)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read proof (%v): %w", path, err) return nil, fmt.Errorf("failed to read proof (%v): %w", path, err)
...@@ -207,94 +183,32 @@ func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofDa ...@@ -207,94 +183,32 @@ func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofDa
} }
func (c *CannonTraceProvider) finalState() (*mipsevm.State, error) { 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 { if err != nil {
return nil, fmt.Errorf("cannot read final state: %w", err) return nil, fmt.Errorf("cannot read final state: %w", err)
} }
return state, nil 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 // CannonTraceProviderForTest is a CannonTraceProvider that can find the step referencing the preimage read
// Only to be used for testing // Only to be used for testing
type CannonTraceProviderForTest struct { type CannonTraceProviderForTest struct {
*CannonTraceProvider *CannonTraceProvider
} }
type preimageOpts []string func NewTraceProviderForTest(logger log.Logger, m CannonMetricer, cfg *config.Config, localInputs utils.LocalGameInputs, dir string, gameDepth types.Depth) *CannonTraceProviderForTest {
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 {
p := &CannonTraceProvider{ p := &CannonTraceProvider{
logger: logger, logger: logger,
dir: dir, dir: dir,
prestate: cfg.CannonAbsolutePreState, prestate: cfg.CannonAbsolutePreState,
generator: NewExecutor(logger, m, cfg, localInputs), generator: NewExecutor(logger, m, cfg, localInputs),
gameDepth: gameDepth, gameDepth: gameDepth,
preimageLoader: newPreimageLoader(kvstore.NewDiskKV(preimageDir(dir)).Get), preimageLoader: utils.NewPreimageLoader(kvstore.NewDiskKV(utils.PreimageDir(dir)).Get),
} }
return &CannonTraceProviderForTest{p} 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 // 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 { 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) return 0, fmt.Errorf("generate cannon trace (until preimage read): %w", err)
......
...@@ -13,6 +13,7 @@ import ( ...@@ -13,6 +13,7 @@ import (
"testing" "testing"
"github.com/ethereum-optimism/optimism/cannon/mipsevm" "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-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/ioutil" "github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
...@@ -110,7 +111,7 @@ func TestGetStepData(t *testing.T) { ...@@ -110,7 +111,7 @@ func TestGetStepData(t *testing.T) {
Step: 10, Step: 10,
Exited: true, Exited: true,
} }
generator.proof = &proofData{ generator.proof = &utils.ProofData{
ClaimValue: common.Hash{0xaa}, ClaimValue: common.Hash{0xaa},
StateData: []byte{0xbb}, StateData: []byte{0xbb},
ProofData: []byte{0xcc}, ProofData: []byte{0xcc},
...@@ -136,7 +137,7 @@ func TestGetStepData(t *testing.T) { ...@@ -136,7 +137,7 @@ func TestGetStepData(t *testing.T) {
Step: 10, Step: 10,
Exited: true, Exited: true,
} }
generator.proof = &proofData{ generator.proof = &utils.ProofData{
ClaimValue: common.Hash{0xaa}, ClaimValue: common.Hash{0xaa},
StateData: []byte{0xbb}, StateData: []byte{0xbb},
ProofData: []byte{0xcc}, ProofData: []byte{0xcc},
...@@ -162,7 +163,7 @@ func TestGetStepData(t *testing.T) { ...@@ -162,7 +163,7 @@ func TestGetStepData(t *testing.T) {
Step: 10, Step: 10,
Exited: true, Exited: true,
} }
initGenerator.proof = &proofData{ initGenerator.proof = &utils.ProofData{
ClaimValue: common.Hash{0xaa}, ClaimValue: common.Hash{0xaa},
StateData: []byte{0xbb}, StateData: []byte{0xbb},
ProofData: []byte{0xcc}, ProofData: []byte{0xcc},
...@@ -180,7 +181,7 @@ func TestGetStepData(t *testing.T) { ...@@ -180,7 +181,7 @@ func TestGetStepData(t *testing.T) {
Step: 10, Step: 10,
Exited: true, Exited: true,
} }
generator.proof = &proofData{ generator.proof = &utils.ProofData{
ClaimValue: common.Hash{0xaa}, ClaimValue: common.Hash{0xaa},
StateData: []byte{0xbb}, StateData: []byte{0xbb},
ProofData: []byte{0xcc}, ProofData: []byte{0xcc},
...@@ -221,12 +222,13 @@ func setupTestData(t *testing.T) (string, string) { ...@@ -221,12 +222,13 @@ func setupTestData(t *testing.T) (string, string) {
entries, err := testData.ReadDir(srcDir) entries, err := testData.ReadDir(srcDir)
require.NoError(t, err) require.NoError(t, err)
dataDir := t.TempDir() 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 { for _, entry := range entries {
path := filepath.Join(srcDir, entry.Name()) path := filepath.Join(srcDir, entry.Name())
file, err := testData.ReadFile(path) file, err := testData.ReadFile(path)
require.NoErrorf(t, err, "reading %v", 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) require.NoErrorf(t, err, "writing %v", path)
} }
return dataDir, "state.json" return dataDir, "state.json"
...@@ -246,36 +248,30 @@ func setupWithTestData(t *testing.T, dataDir string, prestate string) (*CannonTr ...@@ -246,36 +248,30 @@ func setupWithTestData(t *testing.T, dataDir string, prestate string) (*CannonTr
type stubGenerator struct { type stubGenerator struct {
generated []int // Using int makes assertions easier generated []int // Using int makes assertions easier
finalState *mipsevm.State finalState *mipsevm.State
proof *proofData proof *utils.ProofData
} }
func (e *stubGenerator) GenerateProof(ctx context.Context, dir string, i uint64) error { func (e *stubGenerator) GenerateProof(ctx context.Context, dir string, i uint64) error {
e.generated = append(e.generated, int(i)) e.generated = append(e.generated, int(i))
var proofFile string
var data []byte
var err error
if e.finalState != nil && e.finalState.Step <= i { if e.finalState != nil && e.finalState.Step <= i {
// Requesting a trace index past the end of the trace // 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 { if err != nil {
return err 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 { if e.proof != nil {
proofFile := filepath.Join(dir, proofsDir, fmt.Sprintf("%d.json.gz", i)) proofFile = filepath.Join(dir, utils.ProofsDir, fmt.Sprintf("%d.json.gz", i))
data, err := json.Marshal(e.proof) data, err = json.Marshal(e.proof)
if err != nil { if err != nil {
return err return err
} }
return writeGzip(proofFile, data) return ioutil.WriteCompressedBytes(proofFile, data, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0o644)
} }
return nil 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 ( ...@@ -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"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/split" "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/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
...@@ -21,7 +22,7 @@ func NewOutputCannonTraceAccessor( ...@@ -21,7 +22,7 @@ func NewOutputCannonTraceAccessor(
logger log.Logger, logger log.Logger,
m metrics.Metricer, m metrics.Metricer,
cfg *config.Config, cfg *config.Config,
l2Client cannon.L2HeaderSource, l2Client utils.L2HeaderSource,
prestateProvider types.PrestateProvider, prestateProvider types.PrestateProvider,
rollupClient OutputRollupClient, rollupClient OutputRollupClient,
dir string, dir string,
...@@ -34,7 +35,7 @@ func NewOutputCannonTraceAccessor( ...@@ -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) { 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) logger := logger.New("pre", agreed.OutputRoot, "post", claimed.OutputRoot, "localContext", localContext)
subdir := filepath.Join(dir, localContext.Hex()) 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 { if err != nil {
return nil, fmt.Errorf("failed to fetch cannon local inputs: %w", err) return nil, fmt.Errorf("failed to fetch cannon local inputs: %w", err)
} }
......
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 ( import (
"bytes" "bytes"
...@@ -29,17 +29,17 @@ var ( ...@@ -29,17 +29,17 @@ var (
type preimageSource func(key common.Hash) ([]byte, error) type preimageSource func(key common.Hash) ([]byte, error)
type preimageLoader struct { type PreimageLoader struct {
getPreimage preimageSource getPreimage preimageSource
} }
func newPreimageLoader(getPreimage preimageSource) *preimageLoader { func NewPreimageLoader(getPreimage preimageSource) *PreimageLoader {
return &preimageLoader{ return &PreimageLoader{
getPreimage: getPreimage, 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 { if len(proof.OracleKey) == 0 {
return nil, nil return nil, nil
} }
...@@ -53,7 +53,7 @@ func (l *preimageLoader) LoadPreimage(proof *proofData) (*types.PreimageOracleDa ...@@ -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. // 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 // 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() inputsKey := preimage.Keccak256Key(proof.OracleKey).PreimageKey()
...@@ -102,7 +102,7 @@ func (l *preimageLoader) loadBlobPreimage(proof *proofData) (*types.PreimageOrac ...@@ -102,7 +102,7 @@ func (l *preimageLoader) loadBlobPreimage(proof *proofData) (*types.PreimageOrac
return types.NewPreimageOracleBlobData(proof.OracleKey, claimWithLength, proof.OracleOffset, requiredFieldElement, commitment, kzgProof[:]), nil 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() inputKey := preimage.Keccak256Key(proof.OracleKey).PreimageKey()
input, err := l.getPreimage(inputKey) input, err := l.getPreimage(inputKey)
if err != nil { if err != nil {
......
package cannon package utils
import ( import (
"crypto/sha256" "crypto/sha256"
...@@ -20,15 +20,15 @@ import ( ...@@ -20,15 +20,15 @@ import (
) )
func TestPreimageLoader_NoPreimage(t *testing.T) { func TestPreimageLoader_NoPreimage(t *testing.T) {
loader := newPreimageLoader(kvstore.NewMemKV().Get) loader := NewPreimageLoader(kvstore.NewMemKV().Get)
actual, err := loader.LoadPreimage(&proofData{}) actual, err := loader.LoadPreimage(&ProofData{})
require.NoError(t, err) require.NoError(t, err)
require.Nil(t, actual) require.Nil(t, actual)
} }
func TestPreimageLoader_LocalPreimage(t *testing.T) { func TestPreimageLoader_LocalPreimage(t *testing.T) {
loader := newPreimageLoader(kvstore.NewMemKV().Get) loader := NewPreimageLoader(kvstore.NewMemKV().Get)
proof := &proofData{ proof := &ProofData{
OracleKey: common.Hash{byte(preimage.LocalKeyType), 0xaa, 0xbb}.Bytes(), OracleKey: common.Hash{byte(preimage.LocalKeyType), 0xaa, 0xbb}.Bytes(),
OracleValue: nil, OracleValue: nil,
OracleOffset: 4, OracleOffset: 4,
...@@ -48,8 +48,8 @@ func TestPreimageLoader_SimpleTypes(t *testing.T) { ...@@ -48,8 +48,8 @@ func TestPreimageLoader_SimpleTypes(t *testing.T) {
for _, keyType := range tests { for _, keyType := range tests {
keyType := keyType keyType := keyType
t.Run(fmt.Sprintf("type-%v", keyType), func(t *testing.T) { t.Run(fmt.Sprintf("type-%v", keyType), func(t *testing.T) {
loader := newPreimageLoader(kvstore.NewMemKV().Get) loader := NewPreimageLoader(kvstore.NewMemKV().Get)
proof := &proofData{ proof := &ProofData{
OracleKey: common.Hash{byte(keyType), 0xaa, 0xbb}.Bytes(), OracleKey: common.Hash{byte(keyType), 0xaa, 0xbb}.Bytes(),
OracleValue: []byte{1, 2, 3, 4, 5, 6}, OracleValue: []byte{1, 2, 3, 4, 5, 6},
OracleOffset: 3, OracleOffset: 3,
...@@ -82,7 +82,7 @@ func TestPreimageLoader_BlobPreimage(t *testing.T) { ...@@ -82,7 +82,7 @@ func TestPreimageLoader_BlobPreimage(t *testing.T) {
binary.BigEndian.PutUint64(keyBuf[72:], fieldIndex) binary.BigEndian.PutUint64(keyBuf[72:], fieldIndex)
key := preimage.BlobKey(crypto.Keccak256Hash(keyBuf)).PreimageKey() key := preimage.BlobKey(crypto.Keccak256Hash(keyBuf)).PreimageKey()
proof := &proofData{ proof := &ProofData{
OracleKey: key[:], OracleKey: key[:],
OracleValue: elementDataWithLengthPrefix, OracleValue: elementDataWithLengthPrefix,
OracleOffset: 4, OracleOffset: 4,
...@@ -90,8 +90,8 @@ func TestPreimageLoader_BlobPreimage(t *testing.T) { ...@@ -90,8 +90,8 @@ func TestPreimageLoader_BlobPreimage(t *testing.T) {
t.Run("NoKeyPreimage", func(t *testing.T) { t.Run("NoKeyPreimage", func(t *testing.T) {
kv := kvstore.NewMemKV() kv := kvstore.NewMemKV()
loader := newPreimageLoader(kv.Get) loader := NewPreimageLoader(kv.Get)
proof := &proofData{ proof := &ProofData{
OracleKey: common.Hash{byte(preimage.BlobKeyType), 0xaf}.Bytes(), OracleKey: common.Hash{byte(preimage.BlobKeyType), 0xaf}.Bytes(),
OracleValue: proof.OracleValue, OracleValue: proof.OracleValue,
OracleOffset: proof.OracleOffset, OracleOffset: proof.OracleOffset,
...@@ -102,8 +102,8 @@ func TestPreimageLoader_BlobPreimage(t *testing.T) { ...@@ -102,8 +102,8 @@ func TestPreimageLoader_BlobPreimage(t *testing.T) {
t.Run("InvalidKeyPreimage", func(t *testing.T) { t.Run("InvalidKeyPreimage", func(t *testing.T) {
kv := kvstore.NewMemKV() kv := kvstore.NewMemKV()
loader := newPreimageLoader(kv.Get) loader := NewPreimageLoader(kv.Get)
proof := &proofData{ proof := &ProofData{
OracleKey: common.Hash{byte(preimage.BlobKeyType), 0xad}.Bytes(), OracleKey: common.Hash{byte(preimage.BlobKeyType), 0xad}.Bytes(),
OracleValue: proof.OracleValue, OracleValue: proof.OracleValue,
OracleOffset: proof.OracleOffset, OracleOffset: proof.OracleOffset,
...@@ -115,8 +115,8 @@ func TestPreimageLoader_BlobPreimage(t *testing.T) { ...@@ -115,8 +115,8 @@ func TestPreimageLoader_BlobPreimage(t *testing.T) {
t.Run("MissingBlobs", func(t *testing.T) { t.Run("MissingBlobs", func(t *testing.T) {
kv := kvstore.NewMemKV() kv := kvstore.NewMemKV()
loader := newPreimageLoader(kv.Get) loader := NewPreimageLoader(kv.Get)
proof := &proofData{ proof := &ProofData{
OracleKey: common.Hash{byte(preimage.BlobKeyType), 0xae}.Bytes(), OracleKey: common.Hash{byte(preimage.BlobKeyType), 0xae}.Bytes(),
OracleValue: proof.OracleValue, OracleValue: proof.OracleValue,
OracleOffset: proof.OracleOffset, OracleOffset: proof.OracleOffset,
...@@ -128,7 +128,7 @@ func TestPreimageLoader_BlobPreimage(t *testing.T) { ...@@ -128,7 +128,7 @@ func TestPreimageLoader_BlobPreimage(t *testing.T) {
t.Run("Valid", func(t *testing.T) { t.Run("Valid", func(t *testing.T) {
kv := kvstore.NewMemKV() kv := kvstore.NewMemKV()
loader := newPreimageLoader(kv.Get) loader := NewPreimageLoader(kv.Get)
storeBlob(t, kv, gokzg4844.KZGCommitment(commitment), blob) storeBlob(t, kv, gokzg4844.KZGCommitment(commitment), blob)
actual, err := loader.LoadPreimage(proof) actual, err := loader.LoadPreimage(proof)
require.NoError(t, err) require.NoError(t, err)
...@@ -155,19 +155,19 @@ func TestPreimageLoader_BlobPreimage(t *testing.T) { ...@@ -155,19 +155,19 @@ func TestPreimageLoader_BlobPreimage(t *testing.T) {
func TestPreimageLoader_PrecompilePreimage(t *testing.T) { func TestPreimageLoader_PrecompilePreimage(t *testing.T) {
input := []byte("test input") input := []byte("test input")
key := preimage.PrecompileKey(crypto.Keccak256Hash(input)).PreimageKey() key := preimage.PrecompileKey(crypto.Keccak256Hash(input)).PreimageKey()
proof := &proofData{ proof := &ProofData{
OracleKey: key[:], OracleKey: key[:],
} }
t.Run("NoInputPreimage", func(t *testing.T) { t.Run("NoInputPreimage", func(t *testing.T) {
kv := kvstore.NewMemKV() kv := kvstore.NewMemKV()
loader := newPreimageLoader(kv.Get) loader := NewPreimageLoader(kv.Get)
_, err := loader.LoadPreimage(proof) _, err := loader.LoadPreimage(proof)
require.ErrorIs(t, err, kvstore.ErrNotFound) require.ErrorIs(t, err, kvstore.ErrNotFound)
}) })
t.Run("Valid", func(t *testing.T) { t.Run("Valid", func(t *testing.T) {
kv := kvstore.NewMemKV() kv := kvstore.NewMemKV()
loader := newPreimageLoader(kv.Get) loader := NewPreimageLoader(kv.Get)
require.NoError(t, kv.Put(preimage.Keccak256Key(proof.OracleKey).PreimageKey(), input)) require.NoError(t, kv.Put(preimage.Keccak256Key(proof.OracleKey).PreimageKey(), input))
actual, err := loader.LoadPreimage(proof) actual, err := loader.LoadPreimage(proof)
require.NoError(t, err) 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 ( ...@@ -18,6 +18,7 @@ var (
const ( const (
CannonGameType uint32 = 0 CannonGameType uint32 = 0
PermissionedGameType uint32 = 1 PermissionedGameType uint32 = 1
AsteriscGameType uint32 = 2
AlphabetGameType uint32 = 255 AlphabetGameType uint32 = 255
) )
......
...@@ -37,6 +37,7 @@ type Metricer interface { ...@@ -37,6 +37,7 @@ type Metricer interface {
RecordGameStep() RecordGameStep()
RecordGameMove() RecordGameMove()
RecordCannonExecutionTime(t float64) RecordCannonExecutionTime(t float64)
RecordAsteriscExecutionTime(t float64)
RecordClaimResolutionTime(t float64) RecordClaimResolutionTime(t float64)
RecordGameActTime(t float64) RecordGameActTime(t float64)
...@@ -85,9 +86,10 @@ type Metrics struct { ...@@ -85,9 +86,10 @@ type Metrics struct {
moves prometheus.Counter moves prometheus.Counter
steps prometheus.Counter steps prometheus.Counter
cannonExecutionTime prometheus.Histogram claimResolutionTime prometheus.Histogram
claimResolutionTime prometheus.Histogram gameActTime prometheus.Histogram
gameActTime prometheus.Histogram cannonExecutionTime prometheus.Histogram
asteriscExecutionTime prometheus.Histogram
trackedGames prometheus.GaugeVec trackedGames prometheus.GaugeVec
inflightGames prometheus.Gauge inflightGames prometheus.Gauge
...@@ -165,6 +167,14 @@ func NewMetrics() *Metrics { ...@@ -165,6 +167,14 @@ func NewMetrics() *Metrics {
[]float64{1.0, 2.0, 5.0, 10.0}, []float64{1.0, 2.0, 5.0, 10.0},
prometheus.ExponentialBuckets(30.0, 2.0, 14)...), 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{ bondClaimFailures: factory.NewCounter(prometheus.CounterOpts{
Namespace: Namespace, Namespace: Namespace,
Name: "claim_failures", Name: "claim_failures",
...@@ -261,6 +271,10 @@ func (m *Metrics) RecordCannonExecutionTime(t float64) { ...@@ -261,6 +271,10 @@ func (m *Metrics) RecordCannonExecutionTime(t float64) {
m.cannonExecutionTime.Observe(t) m.cannonExecutionTime.Observe(t)
} }
func (m *Metrics) RecordAsteriscExecutionTime(t float64) {
m.asteriscExecutionTime.Observe(t)
}
func (m *Metrics) RecordClaimResolutionTime(t float64) { func (m *Metrics) RecordClaimResolutionTime(t float64) {
m.claimResolutionTime.Observe(t) m.claimResolutionTime.Observe(t)
} }
......
...@@ -36,9 +36,10 @@ func (*NoopMetricsImpl) RecordPreimageChallengeFailed() {} ...@@ -36,9 +36,10 @@ func (*NoopMetricsImpl) RecordPreimageChallengeFailed() {}
func (*NoopMetricsImpl) RecordBondClaimFailed() {} func (*NoopMetricsImpl) RecordBondClaimFailed() {}
func (*NoopMetricsImpl) RecordBondClaimed(uint64) {} func (*NoopMetricsImpl) RecordBondClaimed(uint64) {}
func (*NoopMetricsImpl) RecordCannonExecutionTime(t float64) {} func (*NoopMetricsImpl) RecordCannonExecutionTime(t float64) {}
func (*NoopMetricsImpl) RecordClaimResolutionTime(t float64) {} func (*NoopMetricsImpl) RecordAsteriscExecutionTime(t float64) {}
func (*NoopMetricsImpl) RecordGameActTime(t float64) {} func (*NoopMetricsImpl) RecordClaimResolutionTime(t float64) {}
func (*NoopMetricsImpl) RecordGameActTime(t float64) {}
func (*NoopMetricsImpl) RecordGamesStatus(inProgress, defenderWon, challengerWon int) {} func (*NoopMetricsImpl) RecordGamesStatus(inProgress, defenderWon, challengerWon int) {}
......
...@@ -50,7 +50,7 @@ func (g *GameCallerCreator) CreateContract(game gameTypes.GameMetadata) (GameCal ...@@ -50,7 +50,7 @@ func (g *GameCallerCreator) CreateContract(game gameTypes.GameMetadata) (GameCal
return fdg, nil return fdg, nil
} }
switch game.GameType { 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) fdg, err := contracts.NewFaultDisputeGameContract(g.m, game.Proxy, g.caller)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create FaultDisputeGameContract: %w", err) return nil, fmt.Errorf("failed to create FaultDisputeGameContract: %w", err)
......
...@@ -29,14 +29,18 @@ func TestMetadataCreator_CreateContract(t *testing.T) { ...@@ -29,14 +29,18 @@ func TestMetadataCreator_CreateContract(t *testing.T) {
name: "validCannonGameType", name: "validCannonGameType",
game: types.GameMetadata{GameType: faultTypes.CannonGameType, Proxy: fdgAddr}, game: types.GameMetadata{GameType: faultTypes.CannonGameType, Proxy: fdgAddr},
}, },
{
name: "validAsteriscGameType",
game: types.GameMetadata{GameType: faultTypes.AsteriscGameType, Proxy: fdgAddr},
},
{ {
name: "validAlphabetGameType", name: "validAlphabetGameType",
game: types.GameMetadata{GameType: faultTypes.AlphabetGameType, Proxy: fdgAddr}, game: types.GameMetadata{GameType: faultTypes.AlphabetGameType, Proxy: fdgAddr},
}, },
{ {
name: "InvalidGameType", name: "InvalidGameType",
game: types.GameMetadata{GameType: 2, Proxy: fdgAddr}, game: types.GameMetadata{GameType: 3, Proxy: fdgAddr},
expectedErr: fmt.Errorf("unsupported game type: 2"), expectedErr: fmt.Errorf("unsupported game type: 3"),
}, },
} }
......
...@@ -93,7 +93,7 @@ func applyCannonConfig( ...@@ -93,7 +93,7 @@ func applyCannonConfig(
l2Endpoint string, l2Endpoint string,
) { ) {
require := require.New(t) require := require.New(t)
c.CannonL2 = l2Endpoint c.L2Rpc = l2Endpoint
root := findMonorepoRoot(t) root := findMonorepoRoot(t)
c.CannonBin = root + "cannon/bin/cannon" c.CannonBin = root + "cannon/bin/cannon"
c.CannonServer = root + "op-program/bin/op-program" c.CannonServer = root + "op-program/bin/op-program"
......
...@@ -17,6 +17,7 @@ import ( ...@@ -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/cannon"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/outputs" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/outputs"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/split" "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/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
...@@ -130,7 +131,7 @@ func (g *OutputCannonGameHelper) CreateStepPreimageLoadCheck(ctx context.Context ...@@ -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 // 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) // 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 // 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 // Identifying the first state transition that loads a global preimage
provider, _ := g.createCannonTraceProvider(ctx, "sequencer", outputRootClaim, challenger.WithPrivKey(challengerKey)) provider, _ := g.createCannonTraceProvider(ctx, "sequencer", outputRootClaim, challenger.WithPrivKey(challengerKey))
targetTraceIndex, err := provider.FindStep(ctx, 0, preimage) targetTraceIndex, err := provider.FindStep(ctx, 0, preimage)
...@@ -229,7 +230,7 @@ func (g *OutputCannonGameHelper) VerifyPreimage(ctx context.Context, outputRootC ...@@ -229,7 +230,7 @@ func (g *OutputCannonGameHelper) VerifyPreimage(ctx context.Context, outputRootC
start := uint64(0) start := uint64(0)
found := false found := false
for offset := uint32(0); ; offset += 4 { 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) g.t.Logf("Searching for step with key %x and offset %v", preimageKey.PreimageKey(), offset)
targetTraceIndex, err := provider.FindStep(ctx, start, preimageOpt) targetTraceIndex, err := provider.FindStep(ctx, start, preimageOpt)
if errors.Is(err, io.EOF) { if errors.Is(err, io.EOF) {
...@@ -310,7 +311,7 @@ func (g *OutputCannonGameHelper) createCannonTraceProvider(ctx context.Context, ...@@ -310,7 +311,7 @@ func (g *OutputCannonGameHelper) createCannonTraceProvider(ctx context.Context,
agreed, disputed, err := outputs.FetchProposals(ctx, outputProvider, pre, post) agreed, disputed, err := outputs.FetchProposals(ctx, outputProvider, pre, post)
g.require.NoError(err) g.require.NoError(err)
g.t.Logf("Using trace between blocks %v and %v\n", agreed.L2BlockNumber, disputed.L2BlockNumber) 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") g.require.NoError(err, "Failed to fetch local inputs")
localContext = outputs.CreateLocalContext(pre, post) localContext = outputs.CreateLocalContext(pre, post)
dir := filepath.Join(cfg.Datadir, "cannon-trace") dir := filepath.Join(cfg.Datadir, "cannon-trace")
......
...@@ -6,7 +6,7 @@ import ( ...@@ -6,7 +6,7 @@ import (
"math/big" "math/big"
"testing" "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" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e" op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
...@@ -252,14 +252,14 @@ func TestOutputCannonStepWithLargePreimage(t *testing.T) { ...@@ -252,14 +252,14 @@ func TestOutputCannonStepWithLargePreimage(t *testing.T) {
// execution game. We then move to challenge it to induce a large preimage load. // execution game. We then move to challenge it to induce a large preimage load.
sender := sys.Cfg.Secrets.Addresses().Alice sender := sys.Cfg.Secrets.Addresses().Alice
preimageLoadCheck := game.CreateStepLargePreimageLoadCheck(ctx, sender) 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 // 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. // So we don't waste time resolving the game - that's tested elsewhere.
} }
func TestOutputCannonStepWithPreimage(t *testing.T) { func TestOutputCannonStepWithPreimage(t *testing.T) {
op_e2e.InitParallel(t, op_e2e.UsesCannon) 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) op_e2e.InitParallel(t, op_e2e.UsesCannon)
ctx := context.Background() ctx := context.Background()
...@@ -290,12 +290,12 @@ func TestOutputCannonStepWithPreimage(t *testing.T) { ...@@ -290,12 +290,12 @@ func TestOutputCannonStepWithPreimage(t *testing.T) {
for _, preimageType := range preimageConditions { for _, preimageType := range preimageConditions {
preimageType := preimageType preimageType := preimageType
t.Run("non-existing preimage-"+preimageType, func(t *testing.T) { 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 // Only test pre-existing images with one type to save runtime
t.Run("preimage already exists", func(t *testing.T) { 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) { ...@@ -334,7 +334,7 @@ func TestOutputCannonStepWithKZGPointEvaluation(t *testing.T) {
// Now the honest challenger is positioned as the defender of the execution game // Now the honest challenger is positioned as the defender of the execution game
// We then move to challenge it to induce a preimage load // We then move to challenge it to induce a preimage load
preimageLoadCheck := game.CreateStepPreimageLoadCheck(ctx) 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 // 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. // So we don't waste time resolving the game - that's tested elsewhere.
} }
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon" "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" "github.com/ethereum-optimism/optimism/op-challenger/metrics"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e" op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
...@@ -122,7 +123,7 @@ func TestPrecompiles(t *testing.T) { ...@@ -122,7 +123,7 @@ func TestPrecompiles(t *testing.T) {
require.NoError(t, err, "get l1 head block") require.NoError(t, err, "get l1 head block")
l1Head := l1HeadBlock.Hash() l1Head := l1HeadBlock.Hash()
inputs := cannon.LocalGameInputs{ inputs := utils.LocalGameInputs{
L1Head: l1Head, L1Head: l1Head,
L2Head: l2Head, L2Head: l2Head,
L2Claim: common.Hash(l2Claim), L2Claim: common.Hash(l2Claim),
...@@ -134,7 +135,7 @@ func TestPrecompiles(t *testing.T) { ...@@ -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") l1Endpoint := sys.NodeEndpoint("l1")
l1Beacon := sys.L1BeaconEndpoint() l1Beacon := sys.L1BeaconEndpoint()
cannonOpts := challenger.WithCannon(t, sys.RollupCfg(), sys.L2Genesis(), sys.RollupEndpoint(l2Node), sys.NodeEndpoint(l2Node)) 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 ...@@ -36,6 +36,18 @@ func OpenCompressed(file string, flag int, perm os.FileMode) (io.WriteCloser, er
return CompressByFileType(file, out), nil 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 // WriteCompressedJson writes the object to the specified file as a compressed json object
// if the filename ends with .gz. // if the filename ends with .gz.
func WriteCompressedJson(file string, obj any) error { 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