Commit 368c1332 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-challenger: Use witness subcommand instead of parsing cannon states (#12141)

* op-challenger: Use the cannon witness subcommand to read states instead of directly calling the parsing code.

* op-challenger: Use a context when downloading the prestate.
parent c8afa158
......@@ -8,6 +8,7 @@ import (
"github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum-optimism/optimism/op-service/jsonutil"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/urfave/cli/v2"
)
......@@ -26,10 +27,11 @@ var (
)
type response struct {
WitnessHash common.Hash `json:"witnessHash"`
Step uint64 `json:"step"`
Exited bool `json:"exited"`
ExitCode uint8 `json:"exitCode"`
WitnessHash common.Hash `json:"witnessHash"`
Witness hexutil.Bytes `json:"witness"`
Step uint64 `json:"step"`
Exited bool `json:"exited"`
ExitCode uint8 `json:"exitCode"`
}
func Witness(ctx *cli.Context) error {
......@@ -47,6 +49,7 @@ func Witness(ctx *cli.Context) error {
}
output := response{
WitnessHash: h,
Witness: witness,
Step: state.GetStep(),
Exited: state.GetExited(),
ExitCode: state.GetExitCode(),
......
......@@ -35,7 +35,7 @@ type PrestateSource interface {
// PrestatePath returns the path to the prestate file to use for the game.
// The provided prestateHash may be used to differentiate between different states but no guarantee is made that
// the returned prestate matches the supplied hash.
PrestatePath(prestateHash common.Hash) (string, error)
PrestatePath(ctx context.Context, prestateHash common.Hash) (string, error)
}
type RollupClient interface {
......
......@@ -33,7 +33,7 @@ type RegisterTask struct {
gameType faultTypes.GameType
skipPrestateValidation bool
getPrestateProvider func(prestateHash common.Hash) (faultTypes.PrestateProvider, error)
getPrestateProvider func(ctx context.Context, prestateHash common.Hash) (faultTypes.PrestateProvider, error)
newTraceAccessor func(
logger log.Logger,
m metrics.Metricer,
......@@ -49,7 +49,7 @@ type RegisterTask struct {
}
func NewCannonRegisterTask(gameType faultTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor) *RegisterTask {
stateConverter := cannon.NewStateConverter()
stateConverter := cannon.NewStateConverter(cfg.Cannon)
return &RegisterTask{
gameType: gameType,
// Don't validate the absolute prestate or genesis output root for permissioned games
......@@ -63,7 +63,7 @@ func NewCannonRegisterTask(gameType faultTypes.GameType, cfg *config.Config, m c
cfg.CannonAbsolutePreStateBaseURL,
cfg.CannonAbsolutePreState,
filepath.Join(cfg.Datadir, "cannon-prestates"),
func(path string) faultTypes.PrestateProvider {
func(ctx context.Context, path string) faultTypes.PrestateProvider {
return vm.NewPrestateProvider(path, stateConverter)
}),
newTraceAccessor: func(
......@@ -95,7 +95,7 @@ func NewAsteriscRegisterTask(gameType faultTypes.GameType, cfg *config.Config, m
cfg.AsteriscAbsolutePreStateBaseURL,
cfg.AsteriscAbsolutePreState,
filepath.Join(cfg.Datadir, "asterisc-prestates"),
func(path string) faultTypes.PrestateProvider {
func(ctx context.Context, path string) faultTypes.PrestateProvider {
return vm.NewPrestateProvider(path, stateConverter)
}),
newTraceAccessor: func(
......@@ -127,7 +127,7 @@ func NewAsteriscKonaRegisterTask(gameType faultTypes.GameType, cfg *config.Confi
cfg.AsteriscKonaAbsolutePreStateBaseURL,
cfg.AsteriscKonaAbsolutePreState,
filepath.Join(cfg.Datadir, "asterisc-kona-prestates"),
func(path string) faultTypes.PrestateProvider {
func(ctx context.Context, path string) faultTypes.PrestateProvider {
return vm.NewPrestateProvider(path, stateConverter)
}),
newTraceAccessor: func(
......@@ -151,7 +151,7 @@ func NewAsteriscKonaRegisterTask(gameType faultTypes.GameType, cfg *config.Confi
func NewAlphabetRegisterTask(gameType faultTypes.GameType) *RegisterTask {
return &RegisterTask{
gameType: gameType,
getPrestateProvider: func(_ common.Hash) (faultTypes.PrestateProvider, error) {
getPrestateProvider: func(_ context.Context, _ common.Hash) (faultTypes.PrestateProvider, error) {
return alphabet.PrestateProvider, nil
},
newTraceAccessor: func(
......@@ -178,15 +178,15 @@ func cachePrestates(
prestateBaseURL *url.URL,
preStatePath string,
prestateDir string,
newPrestateProvider func(path string) faultTypes.PrestateProvider,
) func(prestateHash common.Hash) (faultTypes.PrestateProvider, error) {
newPrestateProvider func(ctx context.Context, path string) faultTypes.PrestateProvider,
) func(ctx context.Context, prestateHash common.Hash) (faultTypes.PrestateProvider, error) {
prestateSource := prestates.NewPrestateSource(prestateBaseURL, preStatePath, prestateDir, stateConverter)
prestateProviderCache := prestates.NewPrestateProviderCache(m, fmt.Sprintf("prestates-%v", gameType), func(prestateHash common.Hash) (faultTypes.PrestateProvider, error) {
prestatePath, err := prestateSource.PrestatePath(prestateHash)
prestateProviderCache := prestates.NewPrestateProviderCache(m, fmt.Sprintf("prestates-%v", gameType), func(ctx context.Context, prestateHash common.Hash) (faultTypes.PrestateProvider, error) {
prestatePath, err := prestateSource.PrestatePath(ctx, prestateHash)
if err != nil {
return nil, fmt.Errorf("required prestate %v not available: %w", prestateHash, err)
}
return newPrestateProvider(prestatePath), nil
return newPrestateProvider(ctx, prestatePath), nil
})
return prestateProviderCache.GetOrCreate
}
......@@ -219,7 +219,7 @@ func (e *RegisterTask) Register(
return nil, fmt.Errorf("failed to load prestate hash for game %v: %w", game.Proxy, err)
}
vmPrestateProvider, err := e.getPrestateProvider(requiredPrestatehash)
vmPrestateProvider, err := e.getPrestateProvider(ctx, requiredPrestatehash)
if err != nil {
return nil, fmt.Errorf("required prestate %v not available for game %v: %w", requiredPrestatehash, game.Proxy, err)
}
......
......@@ -125,7 +125,7 @@ func (p *AsteriscTraceProvider) loadProof(ctx context.Context, i uint64) (*utils
file, err = ioutil.OpenDecompressed(path)
if errors.Is(err, os.ErrNotExist) {
// Expected proof wasn't generated, check if we reached the end of execution
proof, step, exited, err := p.stateConverter.ConvertStateToProof(vm.FinalStatePath(p.dir, p.cfg.BinarySnapshots))
proof, step, exited, err := p.stateConverter.ConvertStateToProof(ctx, vm.FinalStatePath(p.dir, p.cfg.BinarySnapshots))
if err != nil {
return nil, err
}
......@@ -185,7 +185,7 @@ func (p *AsteriscTraceProviderForTest) FindStep(ctx context.Context, start uint6
return 0, fmt.Errorf("generate asterisc trace (until preimage read): %w", err)
}
// Load the step from the state asterisc finished with
_, step, exited, err := p.stateConverter.ConvertStateToProof(vm.FinalStatePath(p.dir, p.cfg.BinarySnapshots))
_, step, exited, err := p.stateConverter.ConvertStateToProof(ctx, vm.FinalStatePath(p.dir, p.cfg.BinarySnapshots))
if err != nil {
return 0, fmt.Errorf("failed to load final state: %w", err)
}
......
package asterisc
import (
"context"
"encoding/json"
"fmt"
"io"
......@@ -83,7 +84,7 @@ func NewStateConverter() *StateConverter {
return &StateConverter{}
}
func (c *StateConverter) ConvertStateToProof(statePath string) (*utils.ProofData, uint64, bool, error) {
func (c *StateConverter) ConvertStateToProof(_ context.Context, statePath string) (*utils.ProofData, uint64, bool, error) {
state, err := parseState(statePath)
if err != nil {
return nil, 0, false, fmt.Errorf("cannot read final state: %w", err)
......
......@@ -50,7 +50,7 @@ func NewTraceProvider(logger log.Logger, m vm.Metricer, cfg vm.Config, vmCfg vm.
return kvstore.NewDiskKV(logger, vm.PreimageDir(dir), kvtypes.DataFormatFile)
}),
PrestateProvider: prestateProvider,
stateConverter: &StateConverter{},
stateConverter: NewStateConverter(cfg),
cfg: cfg,
}
}
......@@ -125,7 +125,7 @@ func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*utils.P
// Try opening the file again now and it should exist.
file, err = ioutil.OpenDecompressed(path)
if errors.Is(err, os.ErrNotExist) {
proof, stateStep, exited, err := p.stateConverter.ConvertStateToProof(vm.FinalStatePath(p.dir, p.cfg.BinarySnapshots))
proof, stateStep, exited, err := p.stateConverter.ConvertStateToProof(ctx, vm.FinalStatePath(p.dir, p.cfg.BinarySnapshots))
if err != nil {
return nil, fmt.Errorf("cannot create proof from final state: %w", err)
}
......@@ -172,7 +172,7 @@ func NewTraceProviderForTest(logger log.Logger, m vm.Metricer, cfg *config.Confi
preimageLoader: utils.NewPreimageLoader(func() (utils.PreimageSource, error) {
return kvstore.NewDiskKV(logger, vm.PreimageDir(dir), kvtypes.DataFormatFile)
}),
stateConverter: NewStateConverter(),
stateConverter: NewStateConverter(cfg.Cannon),
cfg: cfg.Cannon,
}
return &CannonTraceProviderForTest{p}
......@@ -185,7 +185,7 @@ func (p *CannonTraceProviderForTest) FindStep(ctx context.Context, start uint64,
}
// Load the step from the state cannon finished with
_, step, exited, err := p.stateConverter.ConvertStateToProof(vm.FinalStatePath(p.dir, p.cfg.BinarySnapshots))
_, step, exited, err := p.stateConverter.ConvertStateToProof(ctx, vm.FinalStatePath(p.dir, p.cfg.BinarySnapshots))
if err != nil {
return 0, fmt.Errorf("failed to load final state: %w", err)
}
......
......@@ -244,7 +244,7 @@ func setupWithTestData(t *testing.T, dataDir string, prestate string) (*CannonTr
generator: generator,
prestate: filepath.Join(dataDir, prestate),
gameDepth: 63,
stateConverter: &StateConverter{},
stateConverter: generator,
}, generator
}
......@@ -252,6 +252,21 @@ type stubGenerator struct {
generated []int // Using int makes assertions easier
finalState *singlethreaded.State
proof *utils.ProofData
finalStatePath string
}
func (e *stubGenerator) ConvertStateToProof(ctx context.Context, statePath string) (*utils.ProofData, uint64, bool, error) {
if statePath == e.finalStatePath {
witness, hash := e.finalState.EncodeWitness()
return &utils.ProofData{
ClaimValue: hash,
StateData: witness,
ProofData: []byte{},
}, e.finalState.Step, e.finalState.Exited, nil
} else {
return nil, 0, false, fmt.Errorf("loading unexpected state: %s, only support: %s", statePath, e.finalStatePath)
}
}
func (e *stubGenerator) GenerateProof(ctx context.Context, dir string, i uint64) error {
......@@ -262,6 +277,7 @@ func (e *stubGenerator) GenerateProof(ctx context.Context, dir string, i uint64)
if e.finalState != nil && e.finalState.Step <= i {
// Requesting a trace index past the end of the trace
proofFile = vm.FinalStatePath(dir, false)
e.finalStatePath = proofFile
data, err = json.Marshal(e.finalState)
if err != nil {
return err
......
package cannon
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/versions"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
type stateData struct {
WitnessHash common.Hash `json:"witnessHash"`
Witness hexutil.Bytes `json:"witness"`
Step uint64 `json:"step"`
Exited bool `json:"exited"`
}
type StateConverter struct {
vmConfig vm.Config
cmdExecutor func(ctx context.Context, binary string, args ...string) (stdOut string, stdErr string, err error)
}
func NewStateConverter() *StateConverter {
return &StateConverter{}
func NewStateConverter(vmConfig vm.Config) *StateConverter {
return &StateConverter{
vmConfig: vmConfig,
cmdExecutor: runCmd,
}
}
func (c *StateConverter) ConvertStateToProof(statePath string) (*utils.ProofData, uint64, bool, error) {
state, err := parseState(statePath)
func (c *StateConverter) ConvertStateToProof(ctx context.Context, statePath string) (*utils.ProofData, uint64, bool, error) {
stdOut, stdErr, err := c.cmdExecutor(ctx, c.vmConfig.VmBin, "witness", "--input", statePath)
if err != nil {
return nil, 0, false, fmt.Errorf("cannot read final state: %w", err)
return nil, 0, false, fmt.Errorf("state conversion failed: %w (%s)", err, stdErr)
}
var data stateData
if err := json.Unmarshal([]byte(stdOut), &data); err != nil {
return nil, 0, false, fmt.Errorf("failed to parse state data: %w", err)
}
// 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.
witness, witnessHash := state.EncodeWitness()
return &utils.ProofData{
ClaimValue: witnessHash,
StateData: witness,
ClaimValue: data.WitnessHash,
StateData: data.Witness,
ProofData: []byte{},
OracleKey: nil,
OracleValue: nil,
OracleOffset: 0,
}, state.GetStep(), state.GetExited(), nil
}, data.Step, data.Exited, nil
}
func parseState(path string) (mipsevm.FPVMState, error) {
return versions.LoadStateFromFile(path)
func runCmd(ctx context.Context, binary string, args ...string) (stdOut string, stdErr string, err error) {
var outBuf bytes.Buffer
var errBuf bytes.Buffer
cmd := exec.CommandContext(ctx, binary, args...)
cmd.Stdout = &outBuf
cmd.Stderr = &errBuf
err = cmd.Run()
stdOut = outBuf.String()
stdErr = errBuf.String()
return
}
package cannon
import (
_ "embed"
"path/filepath"
"context"
"encoding/json"
"errors"
"testing"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/versions"
"github.com/ethereum-optimism/optimism/cannon/serialize"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
)
func TestLoadState(t *testing.T) {
tests := []struct {
name string
creator func() mipsevm.FPVMState
supportsJSON bool
}{
{
name: "singlethreaded",
creator: func() mipsevm.FPVMState { return singlethreaded.CreateInitialState(234, 82) },
supportsJSON: true,
},
{
name: "multithreaded",
creator: func() mipsevm.FPVMState { return multithreaded.CreateInitialState(982, 492) },
supportsJSON: false,
},
}
for _, test := range tests {
test := test
loadExpectedState := func(t *testing.T) *versions.VersionedState {
state, err := versions.NewFromState(test.creator())
require.NoError(t, err)
return state
}
t.Run(test.name, func(t *testing.T) {
t.Run("Uncompressed", func(t *testing.T) {
if !test.supportsJSON {
t.Skip("JSON not supported by state version")
}
expected := loadExpectedState(t)
path := writeState(t, "state.json", expected)
state, err := parseState(path)
require.NoError(t, err)
require.Equal(t, expected, state)
})
t.Run("Gzipped", func(t *testing.T) {
if !test.supportsJSON {
t.Skip("JSON not supported by state version")
}
expected := loadExpectedState(t)
path := writeState(t, "state.json.gz", expected)
state, err := parseState(path)
require.NoError(t, err)
require.Equal(t, expected, state)
})
const testBinary = "./somewhere/cannon"
t.Run("Binary", func(t *testing.T) {
expected := loadExpectedState(t)
path := writeState(t, "state.bin", expected)
state, err := parseState(path)
require.NoError(t, err)
require.Equal(t, expected, state)
})
func TestStateConverter(t *testing.T) {
setup := func(t *testing.T) (*StateConverter, *capturingExecutor) {
vmCfg := vm.Config{
VmBin: testBinary,
}
executor := &capturingExecutor{}
converter := NewStateConverter(vmCfg)
converter.cmdExecutor = executor.exec
return converter, executor
}
t.Run("BinaryGzip", func(t *testing.T) {
expected := loadExpectedState(t)
t.Run("Valid", func(t *testing.T) {
converter, executor := setup(t)
data := stateData{
WitnessHash: common.Hash{0xab},
Witness: []byte{1, 2, 3, 4},
Step: 42,
Exited: true,
}
ser, err := json.Marshal(data)
require.NoError(t, err)
executor.stdOut = string(ser)
proof, step, exited, err := converter.ConvertStateToProof(context.Background(), "foo.json")
require.NoError(t, err)
require.Equal(t, data.Exited, exited)
require.Equal(t, data.Step, step)
require.Equal(t, data.WitnessHash, proof.ClaimValue)
require.Equal(t, data.Witness, proof.StateData)
require.NotNil(t, proof.ProofData, "later validations require this to be non-nil")
require.Equal(t, testBinary, executor.binary)
require.Equal(t, []string{"witness", "--input", "foo.json"}, executor.args)
})
t.Run("CommandError", func(t *testing.T) {
converter, executor := setup(t)
executor.err = errors.New("boom")
_, _, _, err := converter.ConvertStateToProof(context.Background(), "foo.json")
require.ErrorIs(t, err, executor.err)
})
t.Run("InvalidOutput", func(t *testing.T) {
converter, executor := setup(t)
executor.stdOut = "blah blah"
_, _, _, err := converter.ConvertStateToProof(context.Background(), "foo.json")
require.ErrorContains(t, err, "failed to parse state data")
})
}
path := writeState(t, "state.bin.gz", expected)
type capturingExecutor struct {
binary string
args []string
state, err := parseState(path)
require.NoError(t, err)
require.Equal(t, expected, state)
})
})
}
stdOut string
stdErr string
err error
}
func writeState(t *testing.T, filename string, state *versions.VersionedState) string {
dir := t.TempDir()
path := filepath.Join(dir, filename)
require.NoError(t, serialize.Write(path, state, 0644))
return path
func (c *capturingExecutor) exec(_ context.Context, binary string, args ...string) (string, string, error) {
c.binary = binary
c.args = args
return c.stdOut, c.stdErr, c.err
}
package prestates
import (
"context"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/sources/caching"
"github.com/ethereum/go-ethereum/common"
......@@ -10,27 +12,27 @@ type PrestateSource interface {
// PrestatePath returns the path to the prestate file to use for the game.
// The provided prestateHash may be used to differentiate between different states but no guarantee is made that
// the returned prestate matches the supplied hash.
PrestatePath(prestateHash common.Hash) (string, error)
PrestatePath(ctx context.Context, prestateHash common.Hash) (string, error)
}
type PrestateProviderCache struct {
createProvider func(prestateHash common.Hash) (types.PrestateProvider, error)
createProvider func(ctx context.Context, prestateHash common.Hash) (types.PrestateProvider, error)
cache *caching.LRUCache[common.Hash, types.PrestateProvider]
}
func NewPrestateProviderCache(m caching.Metrics, label string, createProvider func(prestateHash common.Hash) (types.PrestateProvider, error)) *PrestateProviderCache {
func NewPrestateProviderCache(m caching.Metrics, label string, createProvider func(ctx context.Context, prestateHash common.Hash) (types.PrestateProvider, error)) *PrestateProviderCache {
return &PrestateProviderCache{
createProvider: createProvider,
cache: caching.NewLRUCache[common.Hash, types.PrestateProvider](m, label, 5),
}
}
func (p *PrestateProviderCache) GetOrCreate(prestateHash common.Hash) (types.PrestateProvider, error) {
func (p *PrestateProviderCache) GetOrCreate(ctx context.Context, prestateHash common.Hash) (types.PrestateProvider, error) {
provider, ok := p.cache.Get(prestateHash)
if ok {
return provider, nil
}
provider, err := p.createProvider(prestateHash)
provider, err := p.createProvider(ctx, prestateHash)
if err != nil {
return nil, err
}
......
......@@ -11,26 +11,26 @@ import (
)
func TestPrestateProviderCache_CreateAndCache(t *testing.T) {
cache := NewPrestateProviderCache(nil, "", func(prestateHash common.Hash) (types.PrestateProvider, error) {
cache := NewPrestateProviderCache(nil, "", func(_ context.Context, prestateHash common.Hash) (types.PrestateProvider, error) {
return &stubPrestateProvider{commitment: prestateHash}, nil
})
hash1 := common.Hash{0xaa}
hash2 := common.Hash{0xbb}
provider1a, err := cache.GetOrCreate(hash1)
provider1a, err := cache.GetOrCreate(context.Background(), hash1)
require.NoError(t, err)
commitment, err := provider1a.AbsolutePreStateCommitment(context.Background())
require.NoError(t, err)
require.Equal(t, hash1, commitment)
provider1b, err := cache.GetOrCreate(hash1)
provider1b, err := cache.GetOrCreate(context.Background(), hash1)
require.NoError(t, err)
require.Same(t, provider1a, provider1b)
commitment, err = provider1b.AbsolutePreStateCommitment(context.Background())
require.NoError(t, err)
require.Equal(t, hash1, commitment)
provider2, err := cache.GetOrCreate(hash2)
provider2, err := cache.GetOrCreate(context.Background(), hash2)
require.NoError(t, err)
require.NotSame(t, provider1a, provider2)
commitment, err = provider2.AbsolutePreStateCommitment(context.Background())
......@@ -41,10 +41,10 @@ func TestPrestateProviderCache_CreateAndCache(t *testing.T) {
func TestPrestateProviderCache_CreateFails(t *testing.T) {
hash1 := common.Hash{0xaa}
expectedErr := errors.New("boom")
cache := NewPrestateProviderCache(nil, "", func(prestateHash common.Hash) (types.PrestateProvider, error) {
cache := NewPrestateProviderCache(nil, "", func(_ context.Context, prestateHash common.Hash) (types.PrestateProvider, error) {
return nil, expectedErr
})
provider, err := cache.GetOrCreate(hash1)
provider, err := cache.GetOrCreate(context.Background(), hash1)
require.ErrorIs(t, err, expectedErr)
require.Nil(t, provider)
}
......
package prestates
import (
"context"
"errors"
"fmt"
"io"
......@@ -8,6 +9,7 @@ import (
"net/url"
"os"
"path/filepath"
"time"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
......@@ -35,7 +37,7 @@ func NewMultiPrestateProvider(baseUrl *url.URL, dataDir string, stateConverter v
}
}
func (m *MultiPrestateProvider) PrestatePath(hash common.Hash) (string, error) {
func (m *MultiPrestateProvider) PrestatePath(ctx context.Context, hash common.Hash) (string, error) {
// First try to find a previously downloaded prestate
for _, fileType := range supportedFileTypes {
path := filepath.Join(m.dataDir, hash.Hex()+fileType)
......@@ -51,7 +53,7 @@ func (m *MultiPrestateProvider) PrestatePath(hash common.Hash) (string, error) {
var combinedErr error // Keep a track of each download attempt so we can report them if none work
for _, fileType := range supportedFileTypes {
path := filepath.Join(m.dataDir, hash.Hex()+fileType)
if err := m.fetchPrestate(hash, fileType, path); errors.Is(err, ErrPrestateUnavailable) {
if err := m.fetchPrestate(ctx, hash, fileType, path); errors.Is(err, ErrPrestateUnavailable) {
combinedErr = errors.Join(combinedErr, err)
continue // Didn't find prestate in this format, try the next
} else if err != nil {
......@@ -62,12 +64,18 @@ func (m *MultiPrestateProvider) PrestatePath(hash common.Hash) (string, error) {
return "", errors.Join(ErrPrestateUnavailable, combinedErr)
}
func (m *MultiPrestateProvider) fetchPrestate(hash common.Hash, fileType string, dest string) error {
func (m *MultiPrestateProvider) fetchPrestate(ctx context.Context, hash common.Hash, fileType string, dest string) error {
if err := os.MkdirAll(m.dataDir, 0755); err != nil {
return fmt.Errorf("error creating prestate dir: %w", err)
}
prestateUrl := m.baseUrl.JoinPath(hash.Hex() + fileType)
resp, err := http.Get(prestateUrl.String())
tCtx, cancel := context.WithTimeout(ctx, time.Minute)
defer cancel()
req, err := http.NewRequestWithContext(tCtx, "GET", prestateUrl.String(), nil)
if err != nil {
return fmt.Errorf("failed to create prestate request: %w", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("failed to fetch prestate from %v: %w", prestateUrl, err)
}
......@@ -91,7 +99,7 @@ func (m *MultiPrestateProvider) fetchPrestate(hash common.Hash, fileType string,
return fmt.Errorf("failed to close file %v: %w", dest, err)
}
// Verify the prestate actually matches the expected hash before moving it into the final destination
proof, _, _, err := m.stateConverter.ConvertStateToProof(tmpFile)
proof, _, _, err := m.stateConverter.ConvertStateToProof(ctx, tmpFile)
if err != nil || proof.ClaimValue != hash {
// Treat invalid prestates as unavailable. Often servers return a 404 page with 200 status code
_ = os.Remove(tmpFile) // Best effort attempt to clean up the temporary file
......
package prestates
import (
"context"
"errors"
"io"
"net/http"
......@@ -25,7 +26,7 @@ func TestDownloadPrestate(t *testing.T) {
defer server.Close()
hash := common.Hash{0xaa}
provider := NewMultiPrestateProvider(parseURL(t, server.URL), dir, &stubStateConverter{hash: hash})
path, err := provider.PrestatePath(hash)
path, err := provider.PrestatePath(context.Background(), hash)
require.NoError(t, err)
in, err := os.Open(path)
require.NoError(t, err)
......@@ -46,7 +47,7 @@ func TestCreateDirectory(t *testing.T) {
defer server.Close()
hash := common.Hash{0xaa}
provider := NewMultiPrestateProvider(parseURL(t, server.URL), dir, &stubStateConverter{hash: hash})
path, err := provider.PrestatePath(hash)
path, err := provider.PrestatePath(context.Background(), hash)
require.NoError(t, err)
in, err := os.Open(path)
require.NoError(t, err)
......@@ -66,7 +67,7 @@ func TestExistingPrestate(t *testing.T) {
err := ioutil.WriteCompressedBytes(expectedFile, []byte("expected content"), os.O_WRONLY|os.O_CREATE, 0o644)
require.NoError(t, err)
path, err := provider.PrestatePath(hash)
path, err := provider.PrestatePath(context.Background(), hash)
require.NoError(t, err)
require.Equal(t, expectedFile, path)
in, err := ioutil.OpenDecompressed(path)
......@@ -87,7 +88,7 @@ func TestMissingPrestate(t *testing.T) {
defer server.Close()
hash := common.Hash{0xaa}
provider := NewMultiPrestateProvider(parseURL(t, server.URL), dir, &stubStateConverter{hash: hash})
path, err := provider.PrestatePath(hash)
path, err := provider.PrestatePath(context.Background(), hash)
require.ErrorIs(t, err, ErrPrestateUnavailable)
_, err = os.Stat(path)
require.ErrorIs(t, err, os.ErrNotExist)
......@@ -115,7 +116,7 @@ func TestStorePrestateWithCorrectExtension(t *testing.T) {
defer server.Close()
hash := common.Hash{0xaa}
provider := NewMultiPrestateProvider(parseURL(t, server.URL), dir, &stubStateConverter{hash: hash})
path, err := provider.PrestatePath(hash)
path, err := provider.PrestatePath(context.Background(), hash)
require.NoError(t, err)
require.Truef(t, strings.HasSuffix(path, ext), "Expected path %v to have extension %v", path, ext)
in, err := os.Open(path)
......@@ -136,7 +137,7 @@ func TestDetectInvalidPrestate(t *testing.T) {
defer server.Close()
hash := common.Hash{0xaa}
provider := NewMultiPrestateProvider(parseURL(t, server.URL), dir, &stubStateConverter{hash: hash, err: errors.New("boom")})
_, err := provider.PrestatePath(hash)
_, err := provider.PrestatePath(context.Background(), hash)
require.ErrorIs(t, err, ErrPrestateUnavailable)
entries, err := os.ReadDir(dir)
require.NoError(t, err)
......@@ -152,7 +153,7 @@ func TestDetectPrestateWithWrongHash(t *testing.T) {
hash := common.Hash{0xaa}
actualHash := common.Hash{0xbb}
provider := NewMultiPrestateProvider(parseURL(t, server.URL), dir, &stubStateConverter{hash: actualHash})
_, err := provider.PrestatePath(hash)
_, err := provider.PrestatePath(context.Background(), hash)
require.ErrorIs(t, err, ErrPrestateUnavailable)
entries, err := os.ReadDir(dir)
require.NoError(t, err)
......@@ -180,7 +181,7 @@ type stubStateConverter struct {
hash common.Hash
}
func (s *stubStateConverter) ConvertStateToProof(path string) (*utils.ProofData, uint64, bool, error) {
func (s *stubStateConverter) ConvertStateToProof(_ context.Context, path string) (*utils.ProofData, uint64, bool, error) {
// Return an error if we're given the wrong path
if _, err := os.Stat(path); err != nil {
return nil, 0, false, err
......
package prestates
import "github.com/ethereum/go-ethereum/common"
import (
"context"
"github.com/ethereum/go-ethereum/common"
)
type SinglePrestateSource struct {
path string
......@@ -10,6 +14,6 @@ func NewSinglePrestateSource(path string) *SinglePrestateSource {
return &SinglePrestateSource{path: path}
}
func (s *SinglePrestateSource) PrestatePath(_ common.Hash) (string, error) {
func (s *SinglePrestateSource) PrestatePath(_ context.Context, _ common.Hash) (string, error) {
return s.path, nil
}
package vm
import "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
import (
"context"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
)
type StateConverter interface {
// ConvertStateToProof reads the state snapshot at the specified path and converts it to ProofData.
// Returns the proof data, the VM step the state is from and whether or not the VM had exited.
ConvertStateToProof(statePath string) (*utils.ProofData, uint64, bool, error)
ConvertStateToProof(ctx context.Context, statePath string) (*utils.ProofData, uint64, bool, error)
}
......@@ -25,11 +25,11 @@ func NewPrestateProvider(prestate string, converter StateConverter) *PrestatePro
}
}
func (p *PrestateProvider) AbsolutePreStateCommitment(_ context.Context) (common.Hash, error) {
func (p *PrestateProvider) AbsolutePreStateCommitment(ctx context.Context) (common.Hash, error) {
if p.prestateCommitment != (common.Hash{}) {
return p.prestateCommitment, nil
}
proof, _, _, err := p.stateConverter.ConvertStateToProof(p.prestate)
proof, _, _, err := p.stateConverter.ConvertStateToProof(ctx, p.prestate)
if err != nil {
return common.Hash{}, fmt.Errorf("cannot load absolute pre-state: %w", err)
}
......
......@@ -16,7 +16,7 @@ type stubConverter struct {
hash common.Hash
}
func (s *stubConverter) ConvertStateToProof(statePath string) (*utils.ProofData, uint64, bool, error) {
func (s *stubConverter) ConvertStateToProof(_ context.Context, _ string) (*utils.ProofData, uint64, bool, error) {
if s.err != nil {
return nil, 0, false, s.err
}
......
package runner
import (
"context"
"errors"
"fmt"
"net/url"
......@@ -18,6 +19,7 @@ import (
)
func createTraceProvider(
ctx context.Context,
logger log.Logger,
m vm.Metricer,
cfg *config.Config,
......@@ -28,51 +30,51 @@ func createTraceProvider(
) (types.TraceProvider, error) {
switch traceType {
case types.TraceTypeCannon:
vmConfig := vm.NewOpProgramServerExecutor()
stateConverter := cannon.NewStateConverter()
prestate, err := getPrestate(prestateHash, cfg.CannonAbsolutePreStateBaseURL, cfg.CannonAbsolutePreState, dir, stateConverter)
serverExecutor := vm.NewOpProgramServerExecutor()
stateConverter := cannon.NewStateConverter(cfg.Cannon)
prestate, err := getPrestate(ctx, prestateHash, cfg.CannonAbsolutePreStateBaseURL, cfg.CannonAbsolutePreState, dir, stateConverter)
if err != nil {
return nil, err
}
prestateProvider := vm.NewPrestateProvider(prestate, stateConverter)
return cannon.NewTraceProvider(logger, m, cfg.Cannon, vmConfig, prestateProvider, prestate, localInputs, dir, 42), nil
return cannon.NewTraceProvider(logger, m, cfg.Cannon, serverExecutor, prestateProvider, prestate, localInputs, dir, 42), nil
case types.TraceTypeAsterisc:
vmConfig := vm.NewOpProgramServerExecutor()
serverExecutor := vm.NewOpProgramServerExecutor()
stateConverter := asterisc.NewStateConverter()
prestate, err := getPrestate(prestateHash, cfg.AsteriscAbsolutePreStateBaseURL, cfg.AsteriscAbsolutePreState, dir, stateConverter)
prestate, err := getPrestate(ctx, prestateHash, cfg.AsteriscAbsolutePreStateBaseURL, cfg.AsteriscAbsolutePreState, dir, stateConverter)
if err != nil {
return nil, err
}
prestateProvider := vm.NewPrestateProvider(prestate, stateConverter)
return asterisc.NewTraceProvider(logger, m, cfg.Asterisc, vmConfig, prestateProvider, prestate, localInputs, dir, 42), nil
return asterisc.NewTraceProvider(logger, m, cfg.Asterisc, serverExecutor, prestateProvider, prestate, localInputs, dir, 42), nil
case types.TraceTypeAsteriscKona:
vmConfig := vm.NewKonaExecutor()
serverExecutor := vm.NewKonaExecutor()
stateConverter := asterisc.NewStateConverter()
prestate, err := getPrestate(prestateHash, cfg.AsteriscKonaAbsolutePreStateBaseURL, cfg.AsteriscKonaAbsolutePreState, dir, stateConverter)
prestate, err := getPrestate(ctx, prestateHash, cfg.AsteriscKonaAbsolutePreStateBaseURL, cfg.AsteriscKonaAbsolutePreState, dir, stateConverter)
if err != nil {
return nil, err
}
prestateProvider := vm.NewPrestateProvider(prestate, stateConverter)
return asterisc.NewTraceProvider(logger, m, cfg.AsteriscKona, vmConfig, prestateProvider, prestate, localInputs, dir, 42), nil
return asterisc.NewTraceProvider(logger, m, cfg.AsteriscKona, serverExecutor, prestateProvider, prestate, localInputs, dir, 42), nil
}
return nil, errors.New("invalid trace type")
}
func createMTTraceProvider(
ctx context.Context,
logger log.Logger,
m vm.Metricer,
vmConfig vm.Config,
prestateHash common.Hash,
absolutePrestateBaseURL *url.URL,
traceType types.TraceType,
localInputs utils.LocalGameInputs,
dir string,
) (types.TraceProvider, error) {
executor := vm.NewOpProgramServerExecutor()
stateConverter := cannon.NewStateConverter()
stateConverter := cannon.NewStateConverter(vmConfig)
prestateSource := prestates.NewMultiPrestateProvider(absolutePrestateBaseURL, filepath.Join(dir, "prestates"), cannon.NewStateConverter())
prestatePath, err := prestateSource.PrestatePath(prestateHash)
prestateSource := prestates.NewMultiPrestateProvider(absolutePrestateBaseURL, filepath.Join(dir, "prestates"), stateConverter)
prestatePath, err := prestateSource.PrestatePath(ctx, prestateHash)
if err != nil {
return nil, fmt.Errorf("failed to get prestate %v: %w", prestateHash, err)
}
......@@ -80,14 +82,14 @@ func createMTTraceProvider(
return cannon.NewTraceProvider(logger, m, vmConfig, executor, prestateProvider, prestatePath, localInputs, dir, 42), nil
}
func getPrestate(prestateHash common.Hash, prestateBaseUrl *url.URL, prestatePath string, dataDir string, stateConverter vm.StateConverter) (string, error) {
func getPrestate(ctx context.Context, prestateHash common.Hash, prestateBaseUrl *url.URL, prestatePath string, dataDir string, stateConverter vm.StateConverter) (string, error) {
prestateSource := prestates.NewPrestateSource(
prestateBaseUrl,
prestatePath,
filepath.Join(dataDir, "prestates"),
stateConverter)
prestate, err := prestateSource.PrestatePath(prestateHash)
prestate, err := prestateSource.PrestatePath(ctx, prestateHash)
if err != nil {
return "", fmt.Errorf("failed to get prestate %v: %w", prestateHash, err)
}
......
......@@ -172,7 +172,7 @@ func (r *Runner) runAndRecordOnce(ctx context.Context, traceType types.TraceType
}
func (r *Runner) runOnce(ctx context.Context, logger log.Logger, traceType types.TraceType, prestateHash common.Hash, localInputs utils.LocalGameInputs, dir string) error {
provider, err := createTraceProvider(logger, metrics.NewVmMetrics(r.m, traceType.String()), r.cfg, prestateHash, traceType, localInputs, dir)
provider, err := createTraceProvider(ctx, logger, metrics.NewVmMetrics(r.m, traceType.String()), r.cfg, prestateHash, traceType, localInputs, dir)
if err != nil {
return fmt.Errorf("failed to create trace provider: %w", err)
}
......@@ -187,7 +187,7 @@ func (r *Runner) runOnce(ctx context.Context, logger log.Logger, traceType types
}
func (r *Runner) runMTOnce(ctx context.Context, logger log.Logger, localInputs utils.LocalGameInputs, dir string) error {
provider, err := createMTTraceProvider(logger, metrics.NewVmMetrics(r.m, mtCannonType), r.cfg.Cannon, r.addMTCannonPrestate, r.addMTCannonPrestateURL, types.TraceTypeCannon, localInputs, dir)
provider, err := createMTTraceProvider(ctx, logger, metrics.NewVmMetrics(r.m, mtCannonType), r.cfg.Cannon, r.addMTCannonPrestate, r.addMTCannonPrestateURL, localInputs, dir)
if err != nil {
return fmt.Errorf("failed to create trace provider: %w", err)
}
......
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