Commit df4d723f authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

Reapply "challenger: Introduce StateConverter to abstract loading VM states" (#11752)

With fix to set a state converter when creating trace providers for test.
parent 224c5fd6
...@@ -57,7 +57,7 @@ func NewCannonRegisterTask(gameType faultTypes.GameType, cfg *config.Config, m c ...@@ -57,7 +57,7 @@ func NewCannonRegisterTask(gameType faultTypes.GameType, cfg *config.Config, m c
cfg.CannonAbsolutePreState, cfg.CannonAbsolutePreState,
filepath.Join(cfg.Datadir, "cannon-prestates"), filepath.Join(cfg.Datadir, "cannon-prestates"),
func(path string) faultTypes.PrestateProvider { func(path string) faultTypes.PrestateProvider {
return cannon.NewPrestateProvider(path) return vm.NewPrestateProvider(path, cannon.NewStateConverter())
}), }),
newTraceAccessor: func( newTraceAccessor: func(
logger log.Logger, logger log.Logger,
...@@ -71,7 +71,7 @@ func NewCannonRegisterTask(gameType faultTypes.GameType, cfg *config.Config, m c ...@@ -71,7 +71,7 @@ func NewCannonRegisterTask(gameType faultTypes.GameType, cfg *config.Config, m c
splitDepth faultTypes.Depth, splitDepth faultTypes.Depth,
prestateBlock uint64, prestateBlock uint64,
poststateBlock uint64) (*trace.Accessor, error) { poststateBlock uint64) (*trace.Accessor, error) {
provider := vmPrestateProvider.(*cannon.CannonPrestateProvider) provider := vmPrestateProvider.(*vm.PrestateProvider)
return outputs.NewOutputCannonTraceAccessor(logger, m, cfg.Cannon, serverExecutor, l2Client, prestateProvider, provider.PrestatePath(), rollupClient, dir, l1Head, splitDepth, prestateBlock, poststateBlock) return outputs.NewOutputCannonTraceAccessor(logger, m, cfg.Cannon, serverExecutor, l2Client, prestateProvider, provider.PrestatePath(), rollupClient, dir, l1Head, splitDepth, prestateBlock, poststateBlock)
}, },
} }
...@@ -87,7 +87,7 @@ func NewAsteriscRegisterTask(gameType faultTypes.GameType, cfg *config.Config, m ...@@ -87,7 +87,7 @@ func NewAsteriscRegisterTask(gameType faultTypes.GameType, cfg *config.Config, m
cfg.AsteriscAbsolutePreState, cfg.AsteriscAbsolutePreState,
filepath.Join(cfg.Datadir, "asterisc-prestates"), filepath.Join(cfg.Datadir, "asterisc-prestates"),
func(path string) faultTypes.PrestateProvider { func(path string) faultTypes.PrestateProvider {
return asterisc.NewPrestateProvider(path) return vm.NewPrestateProvider(path, asterisc.NewStateConverter())
}), }),
newTraceAccessor: func( newTraceAccessor: func(
logger log.Logger, logger log.Logger,
...@@ -101,7 +101,7 @@ func NewAsteriscRegisterTask(gameType faultTypes.GameType, cfg *config.Config, m ...@@ -101,7 +101,7 @@ func NewAsteriscRegisterTask(gameType faultTypes.GameType, cfg *config.Config, m
splitDepth faultTypes.Depth, splitDepth faultTypes.Depth,
prestateBlock uint64, prestateBlock uint64,
poststateBlock uint64) (*trace.Accessor, error) { poststateBlock uint64) (*trace.Accessor, error) {
provider := vmPrestateProvider.(*asterisc.AsteriscPreStateProvider) provider := vmPrestateProvider.(*vm.PrestateProvider)
return outputs.NewOutputAsteriscTraceAccessor(logger, m, cfg.Asterisc, serverExecutor, l2Client, prestateProvider, provider.PrestatePath(), rollupClient, dir, l1Head, splitDepth, prestateBlock, poststateBlock) return outputs.NewOutputAsteriscTraceAccessor(logger, m, cfg.Asterisc, serverExecutor, l2Client, prestateProvider, provider.PrestatePath(), rollupClient, dir, l1Head, splitDepth, prestateBlock, poststateBlock)
}, },
} }
......
...@@ -27,6 +27,7 @@ type AsteriscTraceProvider struct { ...@@ -27,6 +27,7 @@ type AsteriscTraceProvider struct {
generator utils.ProofGenerator generator utils.ProofGenerator
gameDepth types.Depth gameDepth types.Depth
preimageLoader *utils.PreimageLoader preimageLoader *utils.PreimageLoader
stateConverter vm.StateConverter
types.PrestateProvider types.PrestateProvider
...@@ -46,6 +47,7 @@ func NewTraceProvider(logger log.Logger, m vm.Metricer, cfg vm.Config, vmCfg vm. ...@@ -46,6 +47,7 @@ func NewTraceProvider(logger log.Logger, m vm.Metricer, cfg vm.Config, vmCfg vm.
return kvstore.NewFileKV(vm.PreimageDir(dir)) return kvstore.NewFileKV(vm.PreimageDir(dir))
}), }),
PrestateProvider: prestateProvider, PrestateProvider: prestateProvider,
stateConverter: NewStateConverter(),
} }
} }
...@@ -120,31 +122,23 @@ func (p *AsteriscTraceProvider) loadProof(ctx context.Context, i uint64) (*utils ...@@ -120,31 +122,23 @@ func (p *AsteriscTraceProvider) loadProof(ctx context.Context, i uint64) (*utils
file, err = ioutil.OpenDecompressed(path) file, err = ioutil.OpenDecompressed(path)
if errors.Is(err, os.ErrNotExist) { if errors.Is(err, os.ErrNotExist) {
// Expected proof wasn't generated, check if we reached the end of execution // Expected proof wasn't generated, check if we reached the end of execution
state, err := p.finalState() proof, step, exited, err := p.stateConverter.ConvertStateToProof(filepath.Join(p.dir, vm.FinalState))
if err != nil { if err != nil {
return nil, err return nil, err
} }
if state.Exited && state.Step <= i { if exited && step <= i {
p.logger.Warn("Requested proof was after the program exited", "proof", i, "last", state.Step) p.logger.Warn("Requested proof was after the program exited", "proof", i, "last", step)
// The final instruction has already been applied to this state, so the last step we can execute // The final instruction has already been applied to this state, so the last step we can execute
// is one before its Step value. // is one before its Step value.
p.lastStep = state.Step - 1 p.lastStep = step - 1
// Extend the trace out to the full length using a no-op instruction that doesn't change any state // 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. // 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 { 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
} else { } 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) return nil, fmt.Errorf("expected proof not generated but final state was not exited, requested step %v, final state at step %v", i, step)
} }
} }
} }
...@@ -160,14 +154,6 @@ func (p *AsteriscTraceProvider) loadProof(ctx context.Context, i uint64) (*utils ...@@ -160,14 +154,6 @@ func (p *AsteriscTraceProvider) loadProof(ctx context.Context, i uint64) (*utils
return &proof, nil return &proof, nil
} }
func (c *AsteriscTraceProvider) finalState() (*VMState, error) {
state, err := parseState(filepath.Join(c.dir, vm.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 // AsteriscTraceProviderForTest is a AsteriscTraceProvider that can find the step referencing the preimage read
// Only to be used for testing // Only to be used for testing
type AsteriscTraceProviderForTest struct { type AsteriscTraceProviderForTest struct {
...@@ -184,6 +170,7 @@ func NewTraceProviderForTest(logger log.Logger, m vm.Metricer, cfg *config.Confi ...@@ -184,6 +170,7 @@ func NewTraceProviderForTest(logger log.Logger, m vm.Metricer, cfg *config.Confi
preimageLoader: utils.NewPreimageLoader(func() utils.PreimageSource { preimageLoader: utils.NewPreimageLoader(func() utils.PreimageSource {
return kvstore.NewFileKV(vm.PreimageDir(dir)) return kvstore.NewFileKV(vm.PreimageDir(dir))
}), }),
stateConverter: NewStateConverter(),
} }
return &AsteriscTraceProviderForTest{p} return &AsteriscTraceProviderForTest{p}
} }
...@@ -194,14 +181,14 @@ func (p *AsteriscTraceProviderForTest) FindStep(ctx context.Context, start uint6 ...@@ -194,14 +181,14 @@ func (p *AsteriscTraceProviderForTest) FindStep(ctx context.Context, start uint6
return 0, fmt.Errorf("generate asterisc trace (until preimage read): %w", err) return 0, fmt.Errorf("generate asterisc trace (until preimage read): %w", err)
} }
// Load the step from the state asterisc finished with // Load the step from the state asterisc finished with
state, err := p.finalState() _, step, exited, err := p.stateConverter.ConvertStateToProof(filepath.Join(p.dir, vm.FinalState))
if err != nil { if err != nil {
return 0, fmt.Errorf("failed to load final state: %w", err) 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 // Check we didn't get to the end of the trace without finding the preimage read we were looking for
if state.Exited { if exited {
return 0, fmt.Errorf("preimage read not found: %w", io.EOF) 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. // 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 return step - 1, nil
} }
...@@ -221,11 +221,12 @@ func setupTestData(t *testing.T) (string, string) { ...@@ -221,11 +221,12 @@ func setupTestData(t *testing.T) (string, string) {
func setupWithTestData(t *testing.T, dataDir string, prestate string) (*AsteriscTraceProvider, *stubGenerator) { func setupWithTestData(t *testing.T, dataDir string, prestate string) (*AsteriscTraceProvider, *stubGenerator) {
generator := &stubGenerator{} generator := &stubGenerator{}
return &AsteriscTraceProvider{ return &AsteriscTraceProvider{
logger: testlog.Logger(t, log.LevelInfo), logger: testlog.Logger(t, log.LevelInfo),
dir: dataDir, dir: dataDir,
generator: generator, generator: generator,
prestate: filepath.Join(dataDir, prestate), prestate: filepath.Join(dataDir, prestate),
gameDepth: 63, gameDepth: 63,
stateConverter: &StateConverter{},
}, generator }, generator
} }
......
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"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-service/ioutil" "github.com/ethereum-optimism/optimism/op-service/ioutil"
) )
...@@ -74,3 +75,27 @@ func parseStateFromReader(in io.ReadCloser) (*VMState, error) { ...@@ -74,3 +75,27 @@ func parseStateFromReader(in io.ReadCloser) (*VMState, error) {
} }
return &state, nil return &state, nil
} }
type StateConverter struct {
}
func NewStateConverter() *StateConverter {
return &StateConverter{}
}
func (c *StateConverter) ConvertStateToProof(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)
}
// 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.
return &utils.ProofData{
ClaimValue: state.StateHash,
StateData: state.Witness,
ProofData: []byte{},
OracleKey: nil,
OracleValue: nil,
OracleOffset: 0,
}, state.Step, state.Exited, nil
}
package cannon
import (
"context"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
)
var _ types.PrestateProvider = (*CannonPrestateProvider)(nil)
type CannonPrestateProvider struct {
prestate string
prestateCommitment common.Hash
}
func NewPrestateProvider(prestate string) *CannonPrestateProvider {
return &CannonPrestateProvider{prestate: prestate}
}
func (p *CannonPrestateProvider) absolutePreState() ([]byte, common.Hash, error) {
state, err := parseState(p.prestate)
if err != nil {
return nil, common.Hash{}, fmt.Errorf("cannot load absolute pre-state: %w", err)
}
witness, hash := state.EncodeWitness()
return witness, hash, nil
}
func (p *CannonPrestateProvider) AbsolutePreStateCommitment(_ context.Context) (common.Hash, error) {
if p.prestateCommitment != (common.Hash{}) {
return p.prestateCommitment, nil
}
_, hash, err := p.absolutePreState()
if err != nil {
return common.Hash{}, fmt.Errorf("cannot load absolute pre-state: %w", err)
}
p.prestateCommitment = hash
return hash, nil
}
func (p *CannonPrestateProvider) PrestatePath() string {
return p.prestate
}
package cannon
import (
"context"
"os"
"path/filepath"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
)
func newCannonPrestateProvider(dataDir string, prestate string) *CannonPrestateProvider {
return &CannonPrestateProvider{
prestate: filepath.Join(dataDir, prestate),
}
}
func TestAbsolutePreStateCommitment(t *testing.T) {
dataDir := t.TempDir()
prestate := "state.json"
t.Run("StateUnavailable", func(t *testing.T) {
provider := newCannonPrestateProvider("/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 := newCannonPrestateProvider(dataDir, prestate)
_, err := provider.AbsolutePreStateCommitment(context.Background())
require.ErrorContains(t, err, "invalid mipsevm state")
})
t.Run("ExpectedAbsolutePreState", func(t *testing.T) {
setupPreState(t, dataDir, "state.json")
provider := newCannonPrestateProvider(dataDir, prestate)
actual, err := provider.AbsolutePreStateCommitment(context.Background())
require.NoError(t, err)
state := singlethreaded.State{
Memory: memory.NewMemory(),
PreimageKey: common.HexToHash("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"),
PreimageOffset: 0,
Cpu: mipsevm.CpuScalars{
PC: 0,
NextPC: 1,
LO: 0,
HI: 0,
},
Heap: 0,
ExitCode: 0,
Exited: false,
Step: 0,
Registers: [32]uint32{},
}
_, expected := state.EncodeWitness()
require.Equal(t, expected, actual)
})
t.Run("CacheAbsolutePreState", func(t *testing.T) {
setupPreState(t, dataDir, prestate)
provider := newCannonPrestateProvider(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)
}
...@@ -11,10 +11,8 @@ import ( ...@@ -11,10 +11,8 @@ import (
"path/filepath" "path/filepath"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
"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/trace/utils"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm"
...@@ -30,6 +28,7 @@ type CannonTraceProvider struct { ...@@ -30,6 +28,7 @@ type CannonTraceProvider struct {
generator utils.ProofGenerator generator utils.ProofGenerator
gameDepth types.Depth gameDepth types.Depth
preimageLoader *utils.PreimageLoader preimageLoader *utils.PreimageLoader
stateConverter vm.StateConverter
types.PrestateProvider types.PrestateProvider
...@@ -49,6 +48,7 @@ func NewTraceProvider(logger log.Logger, m vm.Metricer, cfg vm.Config, vmCfg vm. ...@@ -49,6 +48,7 @@ func NewTraceProvider(logger log.Logger, m vm.Metricer, cfg vm.Config, vmCfg vm.
return kvstore.NewFileKV(vm.PreimageDir(dir)) return kvstore.NewFileKV(vm.PreimageDir(dir))
}), }),
PrestateProvider: prestateProvider, PrestateProvider: prestateProvider,
stateConverter: &StateConverter{},
} }
} }
...@@ -122,33 +122,22 @@ func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*utils.P ...@@ -122,33 +122,22 @@ func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*utils.P
// Try opening the file again now and it should exist. // Try opening the file again now and it should exist.
file, err = ioutil.OpenDecompressed(path) file, err = ioutil.OpenDecompressed(path)
if errors.Is(err, os.ErrNotExist) { if errors.Is(err, os.ErrNotExist) {
// Expected proof wasn't generated, check if we reached the end of execution proof, stateStep, exited, err := p.stateConverter.ConvertStateToProof(filepath.Join(p.dir, vm.FinalState))
state, err := p.finalState()
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("cannot create proof from final state: %w", err)
} }
if state.Exited && state.Step <= i {
p.logger.Warn("Requested proof was after the program exited", "proof", i, "last", state.Step) if exited && stateStep <= i {
p.logger.Warn("Requested proof was after the program exited", "proof", i, "last", stateStep)
// The final instruction has already been applied to this state, so the last step we can execute // The final instruction has already been applied to this state, so the last step we can execute
// is one before its Step value. // is one before its Step value.
p.lastStep = state.Step - 1 p.lastStep = stateStep - 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.
witness, witnessHash := state.EncodeWitness()
proof := &utils.ProofData{
ClaimValue: witnessHash,
StateData: hexutil.Bytes(witness),
ProofData: []byte{},
OracleKey: nil,
OracleValue: nil,
OracleOffset: 0,
}
if err := utils.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
} else { } 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) return nil, fmt.Errorf("expected proof not generated but final state was not exited, requested step %v, final state at step %v", i, stateStep)
} }
} }
} }
...@@ -164,14 +153,6 @@ func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*utils.P ...@@ -164,14 +153,6 @@ func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*utils.P
return &proof, nil return &proof, nil
} }
func (c *CannonTraceProvider) finalState() (*singlethreaded.State, error) {
state, err := parseState(filepath.Join(c.dir, vm.FinalState))
if err != nil {
return nil, fmt.Errorf("cannot read final state: %w", err)
}
return state, 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 {
...@@ -188,6 +169,7 @@ func NewTraceProviderForTest(logger log.Logger, m vm.Metricer, cfg *config.Confi ...@@ -188,6 +169,7 @@ func NewTraceProviderForTest(logger log.Logger, m vm.Metricer, cfg *config.Confi
preimageLoader: utils.NewPreimageLoader(func() utils.PreimageSource { preimageLoader: utils.NewPreimageLoader(func() utils.PreimageSource {
return kvstore.NewFileKV(vm.PreimageDir(dir)) return kvstore.NewFileKV(vm.PreimageDir(dir))
}), }),
stateConverter: NewStateConverter(),
} }
return &CannonTraceProviderForTest{p} return &CannonTraceProviderForTest{p}
} }
...@@ -198,14 +180,14 @@ func (p *CannonTraceProviderForTest) FindStep(ctx context.Context, start uint64, ...@@ -198,14 +180,14 @@ func (p *CannonTraceProviderForTest) FindStep(ctx context.Context, start uint64,
return 0, fmt.Errorf("generate cannon trace (until preimage read): %w", err) return 0, fmt.Errorf("generate cannon trace (until preimage read): %w", err)
} }
// Load the step from the state cannon finished with // Load the step from the state cannon finished with
state, err := p.finalState() _, step, exited, err := p.stateConverter.ConvertStateToProof(filepath.Join(p.dir, vm.FinalState))
if err != nil { if err != nil {
return 0, fmt.Errorf("failed to load final state: %w", err) 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 // Check we didn't get to the end of the trace without finding the preimage read we were looking for
if state.Exited { if exited {
return 0, fmt.Errorf("preimage read not found: %w", io.EOF) 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. // 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 return step - 1, nil
} }
...@@ -239,11 +239,12 @@ func setupTestData(t *testing.T) (string, string) { ...@@ -239,11 +239,12 @@ func setupTestData(t *testing.T) (string, string) {
func setupWithTestData(t *testing.T, dataDir string, prestate string) (*CannonTraceProvider, *stubGenerator) { func setupWithTestData(t *testing.T, dataDir string, prestate string) (*CannonTraceProvider, *stubGenerator) {
generator := &stubGenerator{} generator := &stubGenerator{}
return &CannonTraceProvider{ return &CannonTraceProvider{
logger: testlog.Logger(t, log.LevelInfo), logger: testlog.Logger(t, log.LevelInfo),
dir: dataDir, dir: dataDir,
generator: generator, generator: generator,
prestate: filepath.Join(dataDir, prestate), prestate: filepath.Join(dataDir, prestate),
gameDepth: 63, gameDepth: 63,
stateConverter: &StateConverter{},
}, generator }, generator
} }
......
...@@ -6,9 +6,35 @@ import ( ...@@ -6,9 +6,35 @@ import (
"io" "io"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded" "github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
"github.com/ethereum-optimism/optimism/op-service/ioutil" "github.com/ethereum-optimism/optimism/op-service/ioutil"
) )
type StateConverter struct {
}
func NewStateConverter() *StateConverter {
return &StateConverter{}
}
func (c *StateConverter) ConvertStateToProof(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)
}
// 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,
ProofData: []byte{},
OracleKey: nil,
OracleValue: nil,
OracleOffset: 0,
}, state.Step, state.Exited, nil
}
func parseState(path string) (*singlethreaded.State, error) { func parseState(path string) (*singlethreaded.State, error) {
file, err := ioutil.OpenDecompressed(path) file, err := ioutil.OpenDecompressed(path)
if err != nil { if err != nil {
......
package vm
import "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)
}
package asterisc package vm
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
) )
var _ types.PrestateProvider = (*AsteriscPreStateProvider)(nil) var _ types.PrestateProvider = (*PrestateProvider)(nil)
type AsteriscPreStateProvider struct { type PrestateProvider struct {
prestate string prestate string
stateConverter StateConverter
prestateCommitment common.Hash prestateCommitment common.Hash
} }
func NewPrestateProvider(prestate string) *AsteriscPreStateProvider { func NewPrestateProvider(prestate string, converter StateConverter) *PrestateProvider {
return &AsteriscPreStateProvider{prestate: prestate} return &PrestateProvider{
} prestate: prestate,
stateConverter: converter,
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) { func (p *PrestateProvider) AbsolutePreStateCommitment(_ context.Context) (common.Hash, error) {
if p.prestateCommitment != (common.Hash{}) { if p.prestateCommitment != (common.Hash{}) {
return p.prestateCommitment, nil return p.prestateCommitment, nil
} }
state, err := p.absolutePreState() proof, _, _, err := p.stateConverter.ConvertStateToProof(p.prestate)
if err != nil { if err != nil {
return common.Hash{}, fmt.Errorf("cannot load absolute pre-state: %w", err) return common.Hash{}, fmt.Errorf("cannot load absolute pre-state: %w", err)
} }
p.prestateCommitment = state.StateHash p.prestateCommitment = proof.ClaimValue
return state.StateHash, nil return proof.ClaimValue, nil
} }
func (p *AsteriscPreStateProvider) PrestatePath() string { func (p *PrestateProvider) PrestatePath() string {
return p.prestate return p.prestate
} }
package asterisc package vm
import ( import (
"context" "context"
"os" "errors"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func newAsteriscPrestateProvider(dataDir string, prestate string) *AsteriscPreStateProvider { type stubConverter struct {
return &AsteriscPreStateProvider{ err error
prestate: filepath.Join(dataDir, prestate), hash common.Hash
}
func (s *stubConverter) ConvertStateToProof(statePath string) (*utils.ProofData, uint64, bool, error) {
if s.err != nil {
return nil, 0, false, s.err
} }
return &utils.ProofData{
ClaimValue: s.hash,
}, 0, false, nil
} }
func TestAbsolutePreStateCommitment(t *testing.T) { func newPrestateProvider(prestate common.Hash) *PrestateProvider {
dataDir := t.TempDir() return NewPrestateProvider("state.json", &stubConverter{hash: prestate})
}
prestate := "state.json" func TestAbsolutePreStateCommitment(t *testing.T) {
prestate := common.Hash{0xaa, 0xbb}
t.Run("StateUnavailable", func(t *testing.T) { t.Run("StateUnavailable", func(t *testing.T) {
provider := newAsteriscPrestateProvider("/dir/does/not/exist", prestate) expectedErr := errors.New("kaboom")
provider := NewPrestateProvider("foo", &stubConverter{err: expectedErr})
_, err := provider.AbsolutePreStateCommitment(context.Background()) _, err := provider.AbsolutePreStateCommitment(context.Background())
require.ErrorIs(t, err, os.ErrNotExist) require.ErrorIs(t, err, expectedErr)
}) })
t.Run("InvalidStateFile", func(t *testing.T) { t.Run("ExpectedAbsolutePreState", func(t *testing.T) {
setupPreState(t, dataDir, "invalid.json") provider := newPrestateProvider(prestate)
provider := newAsteriscPrestateProvider(dataDir, prestate) actual, err := provider.AbsolutePreStateCommitment(context.Background())
_, err := provider.AbsolutePreStateCommitment(context.Background()) require.NoError(t, err)
require.ErrorContains(t, err, "invalid asterisc VM state") require.Equal(t, prestate, actual)
}) })
t.Run("CacheAbsolutePreState", func(t *testing.T) { t.Run("CacheAbsolutePreState", func(t *testing.T) {
setupPreState(t, dataDir, prestate) converter := &stubConverter{hash: prestate}
provider := newAsteriscPrestateProvider(dataDir, prestate) provider := NewPrestateProvider(filepath.Join("state.json"), converter)
first, err := provider.AbsolutePreStateCommitment(context.Background()) first, err := provider.AbsolutePreStateCommitment(context.Background())
require.NoError(t, err) require.NoError(t, err)
// Remove the prestate from disk // Remove the prestate from disk
require.NoError(t, os.Remove(provider.prestate)) converter.err = errors.New("no soup for you")
// Value should still be available from cache // Value should still be available from cache
cached, err := provider.AbsolutePreStateCommitment(context.Background()) cached, err := provider.AbsolutePreStateCommitment(context.Background())
...@@ -48,12 +61,3 @@ func TestAbsolutePreStateCommitment(t *testing.T) { ...@@ -48,12 +61,3 @@ func TestAbsolutePreStateCommitment(t *testing.T) {
require.Equal(t, first, cached) 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)
}
...@@ -33,7 +33,7 @@ func createTraceProvider( ...@@ -33,7 +33,7 @@ func createTraceProvider(
if err != nil { if err != nil {
return nil, err return nil, err
} }
prestateProvider := cannon.NewPrestateProvider(prestate) prestateProvider := vm.NewPrestateProvider(prestate, cannon.NewStateConverter())
return cannon.NewTraceProvider(logger, m, cfg.Cannon, vmConfig, prestateProvider, prestate, localInputs, dir, 42), nil return cannon.NewTraceProvider(logger, m, cfg.Cannon, vmConfig, prestateProvider, prestate, localInputs, dir, 42), nil
case types.TraceTypeAsterisc: case types.TraceTypeAsterisc:
vmConfig := vm.NewOpProgramServerExecutor() vmConfig := vm.NewOpProgramServerExecutor()
...@@ -41,7 +41,7 @@ func createTraceProvider( ...@@ -41,7 +41,7 @@ func createTraceProvider(
if err != nil { if err != nil {
return nil, err return nil, err
} }
prestateProvider := asterisc.NewPrestateProvider(prestate) prestateProvider := vm.NewPrestateProvider(prestate, asterisc.NewStateConverter())
return asterisc.NewTraceProvider(logger, m, cfg.Asterisc, vmConfig, prestateProvider, prestate, localInputs, dir, 42), nil return asterisc.NewTraceProvider(logger, m, cfg.Asterisc, vmConfig, prestateProvider, prestate, localInputs, dir, 42), nil
case types.TraceTypeAsteriscKona: case types.TraceTypeAsteriscKona:
vmConfig := vm.NewKonaServerExecutor() vmConfig := vm.NewKonaServerExecutor()
...@@ -49,7 +49,7 @@ func createTraceProvider( ...@@ -49,7 +49,7 @@ func createTraceProvider(
if err != nil { if err != nil {
return nil, err return nil, err
} }
prestateProvider := asterisc.NewPrestateProvider(prestate) prestateProvider := vm.NewPrestateProvider(prestate, asterisc.NewStateConverter())
return asterisc.NewTraceProvider(logger, m, cfg.Asterisc, vmConfig, prestateProvider, prestate, localInputs, dir, 42), nil return asterisc.NewTraceProvider(logger, m, cfg.Asterisc, vmConfig, prestateProvider, prestate, localInputs, dir, 42), nil
} }
return nil, errors.New("invalid trace type") return nil, errors.New("invalid trace type")
......
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