Commit 85ff49b8 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-challenger: Use proof from last step (#6652)

* op-challenger: Increase the default cannon snapshot frequency

Generating snapshots is very slow and uses a lot of disk space so they should be relatively infrequent.

* op-challenger: Use proof from last step when required trace index is beyond end of trace
parent beeffe9c
...@@ -58,7 +58,7 @@ func ValidTraceType(value TraceType) bool { ...@@ -58,7 +58,7 @@ func ValidTraceType(value TraceType) bool {
return false return false
} }
const DefaultCannonSnapshotFreq = uint(10_000) const DefaultCannonSnapshotFreq = uint(1_000_000_000)
// Config is a well typed config that is parsed from the CLI params. // Config is a well typed config that is parsed from the CLI params.
// This also contains config options for auxiliary services. // This also contains config options for auxiliary services.
......
package cannon
import (
"encoding/json"
"fmt"
"os"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
)
func parseState(path string) (*mipsevm.State, error) {
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("cannot open state file (%v): %w", path, err)
}
defer file.Close()
var state mipsevm.State
err = json.NewDecoder(file).Decode(&state)
if err != nil {
return nil, fmt.Errorf("invalid mipsevm state (%v): %w", path, err)
}
return &state, nil
}
...@@ -20,6 +20,7 @@ import ( ...@@ -20,6 +20,7 @@ import (
const ( const (
snapsDir = "snapshots" snapsDir = "snapshots"
preimagesDir = "preimages" preimagesDir = "preimages"
finalState = "final.json"
) )
var snapshotNameRegexp = regexp.MustCompile(`^[0-9]+\.json$`) var snapshotNameRegexp = regexp.MustCompile(`^[0-9]+\.json$`)
...@@ -71,15 +72,21 @@ func (e *Executor) GenerateProof(ctx context.Context, dir string, i uint64) erro ...@@ -71,15 +72,21 @@ func (e *Executor) GenerateProof(ctx context.Context, dir string, i uint64) erro
} }
proofDir := filepath.Join(dir, proofsDir) proofDir := filepath.Join(dir, proofsDir)
dataDir := filepath.Join(e.dataDir, preimagesDir) dataDir := filepath.Join(e.dataDir, preimagesDir)
lastGeneratedState := filepath.Join(dir, finalState)
args := []string{ args := []string{
"run", "run",
"--input", start, "--input", start,
"--output", filepath.Join(dir, "out.json"), "--output", lastGeneratedState,
"--meta", "", "--meta", "",
"--proof-at", "=" + strconv.FormatUint(i, 10), "--proof-at", "=" + strconv.FormatUint(i, 10),
"--proof-fmt", filepath.Join(proofDir, "%d.json"), "--proof-fmt", filepath.Join(proofDir, "%d.json"),
"--snapshot-at", "%" + strconv.FormatUint(uint64(e.snapshotFreq), 10), "--snapshot-at", "%" + strconv.FormatUint(uint64(e.snapshotFreq), 10),
"--snapshot-fmt", filepath.Join(snapshotDir, "%d.json"), "--snapshot-fmt", filepath.Join(snapshotDir, "%d.json"),
}
if i < math.MaxUint64 {
args = append(args, "--stop-at", "="+strconv.FormatUint(i+1, 10))
}
args = append(args,
"--", "--",
e.server, "--server", e.server, "--server",
"--l1", e.l1, "--l1", e.l1,
...@@ -90,10 +97,7 @@ func (e *Executor) GenerateProof(ctx context.Context, dir string, i uint64) erro ...@@ -90,10 +97,7 @@ func (e *Executor) GenerateProof(ctx context.Context, dir string, i uint64) erro
"--l2.outputroot", e.inputs.l2OutputRoot.Hex(), "--l2.outputroot", e.inputs.l2OutputRoot.Hex(),
"--l2.claim", e.inputs.l2Claim.Hex(), "--l2.claim", e.inputs.l2Claim.Hex(),
"--l2.blocknumber", e.inputs.l2BlockNumber.Text(10), "--l2.blocknumber", e.inputs.l2BlockNumber.Text(10),
} )
if i < math.MaxUint64 {
args = append(args, "--stop-at", "="+strconv.FormatUint(i+1, 10))
}
if e.network != "" { if e.network != "" {
args = append(args, "--network", e.network) args = append(args, "--network", e.network)
} }
......
...@@ -36,7 +36,7 @@ func TestGenerateProof(t *testing.T) { ...@@ -36,7 +36,7 @@ func TestGenerateProof(t *testing.T) {
l2Claim: common.Hash{0x44}, l2Claim: common.Hash{0x44},
l2BlockNumber: big.NewInt(3333), l2BlockNumber: big.NewInt(3333),
} }
captureExec := func(cfg config.Config, proofAt uint64) (string, string, map[string]string) { captureExec := func(t *testing.T, cfg config.Config, proofAt uint64) (string, string, map[string]string) {
executor := NewExecutor(testlog.Logger(t, log.LvlInfo), &cfg, inputs) executor := NewExecutor(testlog.Logger(t, log.LvlInfo), &cfg, inputs)
executor.selectSnapshot = func(logger log.Logger, dir string, absolutePreState string, i uint64) (string, error) { executor.selectSnapshot = func(logger log.Logger, dir string, absolutePreState string, i uint64) (string, error) {
return input, nil return input, nil
...@@ -67,7 +67,7 @@ func TestGenerateProof(t *testing.T) { ...@@ -67,7 +67,7 @@ func TestGenerateProof(t *testing.T) {
cfg.CannonNetwork = "mainnet" cfg.CannonNetwork = "mainnet"
cfg.CannonRollupConfigPath = "" cfg.CannonRollupConfigPath = ""
cfg.CannonL2GenesisPath = "" cfg.CannonL2GenesisPath = ""
binary, subcommand, args := captureExec(cfg, 150_000_000) binary, subcommand, args := captureExec(t, cfg, 150_000_000)
require.DirExists(t, filepath.Join(cfg.CannonDatadir, preimagesDir)) require.DirExists(t, filepath.Join(cfg.CannonDatadir, preimagesDir))
require.DirExists(t, filepath.Join(cfg.CannonDatadir, proofsDir)) require.DirExists(t, filepath.Join(cfg.CannonDatadir, proofsDir))
require.DirExists(t, filepath.Join(cfg.CannonDatadir, snapsDir)) require.DirExists(t, filepath.Join(cfg.CannonDatadir, snapsDir))
...@@ -76,7 +76,7 @@ func TestGenerateProof(t *testing.T) { ...@@ -76,7 +76,7 @@ func TestGenerateProof(t *testing.T) {
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(cfg.CannonDatadir, "out.json"), args["--output"]) require.Equal(t, filepath.Join(cfg.CannonDatadir, 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"])
...@@ -105,7 +105,7 @@ func TestGenerateProof(t *testing.T) { ...@@ -105,7 +105,7 @@ func TestGenerateProof(t *testing.T) {
cfg.CannonNetwork = "" cfg.CannonNetwork = ""
cfg.CannonRollupConfigPath = "rollup.json" cfg.CannonRollupConfigPath = "rollup.json"
cfg.CannonL2GenesisPath = "genesis.json" cfg.CannonL2GenesisPath = "genesis.json"
_, _, args := captureExec(cfg, 150_000_000) _, _, args := captureExec(t, cfg, 150_000_000)
require.NotContains(t, args, "--network") require.NotContains(t, args, "--network")
require.Equal(t, cfg.CannonRollupConfigPath, args["--rollup.config"]) require.Equal(t, cfg.CannonRollupConfigPath, args["--rollup.config"])
require.Equal(t, cfg.CannonL2GenesisPath, args["--l2.genesis"]) require.Equal(t, cfg.CannonL2GenesisPath, args["--l2.genesis"])
...@@ -115,7 +115,7 @@ func TestGenerateProof(t *testing.T) { ...@@ -115,7 +115,7 @@ func TestGenerateProof(t *testing.T) {
cfg.CannonNetwork = "mainnet" cfg.CannonNetwork = "mainnet"
cfg.CannonRollupConfigPath = "rollup.json" cfg.CannonRollupConfigPath = "rollup.json"
cfg.CannonL2GenesisPath = "genesis.json" cfg.CannonL2GenesisPath = "genesis.json"
_, _, args := captureExec(cfg, math.MaxUint64) _, _, args := captureExec(t, cfg, math.MaxUint64)
// stop-at would need to be one more than the proof step which would overflow back to 0 // 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 cannon to execute until the program exits. // so expect that it will be omitted. We'll ultimately want cannon to execute until the program exits.
require.NotContains(t, args, "--stop-at") require.NotContains(t, args, "--stop-at")
......
...@@ -15,6 +15,7 @@ import ( ...@@ -15,6 +15,7 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
...@@ -38,9 +39,14 @@ type ProofGenerator interface { ...@@ -38,9 +39,14 @@ type ProofGenerator interface {
} }
type CannonTraceProvider struct { type CannonTraceProvider struct {
logger log.Logger
dir string dir string
prestate string prestate string
generator ProofGenerator generator ProofGenerator
// 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(ctx context.Context, logger log.Logger, cfg *config.Config, l1Client bind.ContractCaller) (*CannonTraceProvider, error) { func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config, l1Client bind.ContractCaller) (*CannonTraceProvider, error) {
...@@ -58,6 +64,7 @@ func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config ...@@ -58,6 +64,7 @@ func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config
return nil, fmt.Errorf("fetch local game inputs: %w", err) return nil, fmt.Errorf("fetch local game inputs: %w", err)
} }
return &CannonTraceProvider{ return &CannonTraceProvider{
logger: logger,
dir: cfg.CannonDatadir, dir: cfg.CannonDatadir,
prestate: cfg.CannonAbsolutePreState, prestate: cfg.CannonAbsolutePreState,
generator: NewExecutor(logger, cfg, l1Head), generator: NewExecutor(logger, cfg, l1Head),
...@@ -65,7 +72,7 @@ func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config ...@@ -65,7 +72,7 @@ func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config
} }
func (p *CannonTraceProvider) GetOracleData(ctx context.Context, i uint64) (*types.PreimageOracleData, error) { func (p *CannonTraceProvider) GetOracleData(ctx context.Context, i uint64) (*types.PreimageOracleData, error) {
proof, err := p.loadProof(ctx, i) proof, err := p.loadProofData(ctx, i)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -74,10 +81,14 @@ func (p *CannonTraceProvider) GetOracleData(ctx context.Context, i uint64) (*typ ...@@ -74,10 +81,14 @@ func (p *CannonTraceProvider) GetOracleData(ctx context.Context, i uint64) (*typ
} }
func (p *CannonTraceProvider) Get(ctx context.Context, i uint64) (common.Hash, error) { func (p *CannonTraceProvider) Get(ctx context.Context, i uint64) (common.Hash, error) {
proof, err := p.loadProof(ctx, i) proof, state, err := p.loadProof(ctx, i)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
if proof == nil && state != nil {
// Use the hash from the final state
return crypto.Keccak256Hash(state.EncodeWitness()), nil
}
value := common.BytesToHash(proof.ClaimValue) value := common.BytesToHash(proof.ClaimValue)
if value == (common.Hash{}) { if value == (common.Hash{}) {
...@@ -87,7 +98,7 @@ func (p *CannonTraceProvider) Get(ctx context.Context, i uint64) (common.Hash, e ...@@ -87,7 +98,7 @@ func (p *CannonTraceProvider) Get(ctx context.Context, i uint64) (common.Hash, e
} }
func (p *CannonTraceProvider) GetPreimage(ctx context.Context, i uint64) ([]byte, []byte, error) { func (p *CannonTraceProvider) GetPreimage(ctx context.Context, i uint64) ([]byte, []byte, error) {
proof, err := p.loadProof(ctx, i) proof, err := p.loadProofData(ctx, i)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
...@@ -104,37 +115,77 @@ func (p *CannonTraceProvider) GetPreimage(ctx context.Context, i uint64) ([]byte ...@@ -104,37 +115,77 @@ func (p *CannonTraceProvider) GetPreimage(ctx context.Context, i uint64) ([]byte
func (p *CannonTraceProvider) AbsolutePreState(ctx context.Context) ([]byte, error) { func (p *CannonTraceProvider) AbsolutePreState(ctx context.Context) ([]byte, error) {
path := filepath.Join(p.dir, p.prestate) path := filepath.Join(p.dir, p.prestate)
file, err := os.Open(path) state, err := parseState(path)
if err != nil { if err != nil {
return []byte{}, fmt.Errorf("cannot open state file (%v): %w", path, err) return []byte{}, fmt.Errorf("cannot load absolute pre-state: %w", err)
} }
defer file.Close() return state.EncodeWitness(), nil
var state mipsevm.State }
err = json.NewDecoder(file).Decode(&state)
// loadProofData loads the proof data for the specified step.
// If the requested index is beyond the end of the actual trace, the proof data from the last step is returned.
// Cannon will be executed a second time if required to generate the full proof data.
func (p *CannonTraceProvider) loadProofData(ctx context.Context, i uint64) (*proofData, error) {
proof, state, err := p.loadProof(ctx, i)
if err != nil { if err != nil {
return []byte{}, fmt.Errorf("invalid mipsevm state (%v): %w", path, err) return nil, err
} else if proof == nil && state != nil {
p.logger.Info("Re-executing to generate proof for last step", "step", state.Step)
proof, _, err = p.loadProof(ctx, state.Step)
if err != nil {
return nil, err
}
if proof == nil {
return nil, fmt.Errorf("proof at step %v was not generated", i)
}
return proof, nil
} }
return state.EncodeWitness(), nil return proof, nil
} }
func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofData, error) { // 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:
// - When the actual trace length is known, the proof data from the last step is returned with nil state
// - When the actual trace length is not yet know, the state from after the last step is returned with nil proofData
// and the actual trace length is cached for future runs
func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofData, *mipsevm.State, error) {
if p.lastStep != 0 && i > p.lastStep {
// If the requested index is after the last step in the actual trace, use the last step
i = p.lastStep
}
path := filepath.Join(p.dir, proofsDir, fmt.Sprintf("%d.json", i)) path := filepath.Join(p.dir, proofsDir, fmt.Sprintf("%d.json", i))
file, err := os.Open(path) file, err := os.Open(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 {
return nil, fmt.Errorf("generate cannon trace with proof at %v: %w", i, err) return nil, nil, fmt.Errorf("generate cannon trace with proof at %v: %w", i, err)
} }
// Try opening the file again now and it should exist. // Try opening the file again now and it should exist.
file, err = os.Open(path) file, err = os.Open(path)
if errors.Is(err, os.ErrNotExist) {
// Expected proof wasn't generated, check if we reached the end of execution
state, err := parseState(filepath.Join(p.dir, finalState))
if err != nil {
return nil, nil, fmt.Errorf("cannot read 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)
// 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
return nil, state, nil
} else {
return nil, 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 { if err != nil {
return nil, fmt.Errorf("cannot open proof file (%v): %w", path, err) return nil, nil, fmt.Errorf("cannot open proof file (%v): %w", path, err)
} }
defer file.Close() defer file.Close()
var proof proofData var proof 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, nil, fmt.Errorf("failed to read proof (%v): %w", path, err)
} }
return &proof, nil return &proof, nil, nil
} }
...@@ -4,12 +4,17 @@ import ( ...@@ -4,12 +4,17 @@ import (
"context" "context"
"embed" "embed"
_ "embed" _ "embed"
"encoding/json"
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -19,29 +24,35 @@ var testData embed.FS ...@@ -19,29 +24,35 @@ var testData embed.FS
func TestGet(t *testing.T) { func TestGet(t *testing.T) {
dataDir, prestate := setupTestData(t) dataDir, prestate := setupTestData(t)
t.Run("ExistingProof", func(t *testing.T) { t.Run("ExistingProof", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate) provider, generator := setupWithTestData(t, dataDir, prestate)
value, err := provider.Get(context.Background(), 0) value, err := provider.Get(context.Background(), 0)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, common.HexToHash("0x45fd9aa59768331c726e719e76aa343e73123af888804604785ae19506e65e87"), value) require.Equal(t, common.HexToHash("0x45fd9aa59768331c726e719e76aa343e73123af888804604785ae19506e65e87"), value)
require.Empty(t, generator.generated) require.Empty(t, generator.generated)
}) })
t.Run("ProofUnavailable", func(t *testing.T) { t.Run("ProofAfterEndOfTrace", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate) provider, generator := setupWithTestData(t, dataDir, prestate)
_, err := provider.Get(context.Background(), 7) generator.finalState = &mipsevm.State{
require.ErrorIs(t, err, os.ErrNotExist) Memory: &mipsevm.Memory{},
require.Contains(t, generator.generated, 7, "should have tried to generate the proof") Step: 10,
Exited: true,
}
value, err := provider.Get(context.Background(), 7000)
require.NoError(t, err)
require.Contains(t, generator.generated, 7000, "should have tried to generate the proof")
require.Equal(t, crypto.Keccak256Hash(generator.finalState.EncodeWitness()), value)
}) })
t.Run("MissingPostHash", func(t *testing.T) { t.Run("MissingPostHash", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate) provider, generator := setupWithTestData(t, dataDir, prestate)
_, err := provider.Get(context.Background(), 1) _, err := provider.Get(context.Background(), 1)
require.ErrorContains(t, err, "missing post hash") require.ErrorContains(t, err, "missing post hash")
require.Empty(t, generator.generated) require.Empty(t, generator.generated)
}) })
t.Run("IgnoreUnknownFields", func(t *testing.T) { t.Run("IgnoreUnknownFields", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate) provider, generator := setupWithTestData(t, dataDir, prestate)
value, err := provider.Get(context.Background(), 2) value, err := provider.Get(context.Background(), 2)
require.NoError(t, err) require.NoError(t, err)
expected := common.HexToHash("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb") expected := common.HexToHash("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
...@@ -53,7 +64,7 @@ func TestGet(t *testing.T) { ...@@ -53,7 +64,7 @@ func TestGet(t *testing.T) {
func TestGetOracleData(t *testing.T) { func TestGetOracleData(t *testing.T) {
dataDir, prestate := setupTestData(t) dataDir, prestate := setupTestData(t)
t.Run("ExistingProof", func(t *testing.T) { t.Run("ExistingProof", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate) provider, generator := setupWithTestData(t, dataDir, prestate)
oracleData, err := provider.GetOracleData(context.Background(), 420) oracleData, err := provider.GetOracleData(context.Background(), 420)
require.NoError(t, err) require.NoError(t, err)
require.False(t, oracleData.IsLocal) require.False(t, oracleData.IsLocal)
...@@ -64,15 +75,32 @@ func TestGetOracleData(t *testing.T) { ...@@ -64,15 +75,32 @@ func TestGetOracleData(t *testing.T) {
require.Empty(t, generator.generated) require.Empty(t, generator.generated)
}) })
t.Run("ProofUnavailable", func(t *testing.T) { t.Run("ProofAfterEndOfTrace", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate) provider, generator := setupWithTestData(t, dataDir, prestate)
_, err := provider.GetOracleData(context.Background(), 7) generator.finalState = &mipsevm.State{
require.ErrorIs(t, err, os.ErrNotExist) Memory: &mipsevm.Memory{},
require.Contains(t, generator.generated, 7, "should have tried to generate the proof") Step: 10,
Exited: true,
}
generator.proof = &proofData{
ClaimValue: common.Hash{0xaa}.Bytes(),
StateData: []byte{0xbb},
ProofData: []byte{0xcc},
OracleKey: common.Hash{0xdd}.Bytes(),
OracleValue: []byte{0xdd},
OracleOffset: 10,
}
oracleData, err := provider.GetOracleData(context.Background(), 7000)
require.NoError(t, err)
require.Contains(t, generator.generated, 7000, "should have tried to generate the proof")
require.Contains(t, generator.generated, 9, "should have regenerated proof from last step")
require.False(t, oracleData.IsLocal)
require.EqualValues(t, generator.proof.OracleKey, oracleData.OracleKey)
require.EqualValues(t, generator.proof.OracleValue, oracleData.OracleData)
}) })
t.Run("IgnoreUnknownFields", func(t *testing.T) { t.Run("IgnoreUnknownFields", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate) provider, generator := setupWithTestData(t, dataDir, prestate)
oracleData, err := provider.GetOracleData(context.Background(), 421) oracleData, err := provider.GetOracleData(context.Background(), 421)
require.NoError(t, err) require.NoError(t, err)
require.False(t, oracleData.IsLocal) require.False(t, oracleData.IsLocal)
...@@ -87,7 +115,7 @@ func TestGetOracleData(t *testing.T) { ...@@ -87,7 +115,7 @@ func TestGetOracleData(t *testing.T) {
func TestGetPreimage(t *testing.T) { func TestGetPreimage(t *testing.T) {
dataDir, prestate := setupTestData(t) dataDir, prestate := setupTestData(t)
t.Run("ExistingProof", func(t *testing.T) { t.Run("ExistingProof", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate) provider, generator := setupWithTestData(t, dataDir, prestate)
value, proof, err := provider.GetPreimage(context.Background(), 0) value, proof, err := provider.GetPreimage(context.Background(), 0)
require.NoError(t, err) require.NoError(t, err)
expected := common.Hex2Bytes("b8f068de604c85ea0e2acd437cdb47add074a2d70b81d018390c504b71fe26f400000000000000000000000000000000000000000000000000000000000000000000000000") expected := common.Hex2Bytes("b8f068de604c85ea0e2acd437cdb47add074a2d70b81d018390c504b71fe26f400000000000000000000000000000000000000000000000000000000000000000000000000")
...@@ -97,22 +125,38 @@ func TestGetPreimage(t *testing.T) { ...@@ -97,22 +125,38 @@ func TestGetPreimage(t *testing.T) {
require.Empty(t, generator.generated) require.Empty(t, generator.generated)
}) })
t.Run("ProofUnavailable", func(t *testing.T) { t.Run("ProofAfterEndOfTrace", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate) provider, generator := setupWithTestData(t, dataDir, prestate)
_, _, err := provider.GetPreimage(context.Background(), 7) generator.finalState = &mipsevm.State{
require.ErrorIs(t, err, os.ErrNotExist) Memory: &mipsevm.Memory{},
require.Contains(t, generator.generated, 7, "should have tried to generate the proof") Step: 10,
Exited: true,
}
generator.proof = &proofData{
ClaimValue: common.Hash{0xaa}.Bytes(),
StateData: []byte{0xbb},
ProofData: []byte{0xcc},
OracleKey: common.Hash{0xdd}.Bytes(),
OracleValue: []byte{0xdd},
OracleOffset: 10,
}
preimage, proof, err := provider.GetPreimage(context.Background(), 7000)
require.NoError(t, err)
require.Contains(t, generator.generated, 7000, "should have tried to generate the proof")
require.Contains(t, generator.generated, 9, "should have regenerated proof from last step")
require.EqualValues(t, generator.proof.StateData, preimage)
require.EqualValues(t, generator.proof.ProofData, proof)
}) })
t.Run("MissingStateData", func(t *testing.T) { t.Run("MissingStateData", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate) provider, generator := setupWithTestData(t, dataDir, prestate)
_, _, err := provider.GetPreimage(context.Background(), 1) _, _, err := provider.GetPreimage(context.Background(), 1)
require.ErrorContains(t, err, "missing state data") require.ErrorContains(t, err, "missing state data")
require.Empty(t, generator.generated) require.Empty(t, generator.generated)
}) })
t.Run("IgnoreUnknownFields", func(t *testing.T) { t.Run("IgnoreUnknownFields", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate) provider, generator := setupWithTestData(t, dataDir, prestate)
value, proof, err := provider.GetPreimage(context.Background(), 2) value, proof, err := provider.GetPreimage(context.Background(), 2)
require.NoError(t, err) require.NoError(t, err)
expected := common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc") expected := common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc")
...@@ -130,21 +174,21 @@ func TestAbsolutePreState(t *testing.T) { ...@@ -130,21 +174,21 @@ func TestAbsolutePreState(t *testing.T) {
prestate := "state.json" prestate := "state.json"
t.Run("StateUnavailable", func(t *testing.T) { t.Run("StateUnavailable", func(t *testing.T) {
provider, _ := setupWithTestData("/dir/does/not/exist", prestate) provider, _ := setupWithTestData(t, "/dir/does/not/exist", prestate)
_, err := provider.AbsolutePreState(context.Background()) _, err := provider.AbsolutePreState(context.Background())
require.ErrorIs(t, err, os.ErrNotExist) require.ErrorIs(t, err, os.ErrNotExist)
}) })
t.Run("InvalidStateFile", func(t *testing.T) { t.Run("InvalidStateFile", func(t *testing.T) {
setupPreState(t, dataDir, "invalid.json") setupPreState(t, dataDir, "invalid.json")
provider, _ := setupWithTestData(dataDir, prestate) provider, _ := setupWithTestData(t, dataDir, prestate)
_, err := provider.AbsolutePreState(context.Background()) _, err := provider.AbsolutePreState(context.Background())
require.ErrorContains(t, err, "invalid mipsevm state") require.ErrorContains(t, err, "invalid mipsevm state")
}) })
t.Run("ExpectedAbsolutePreState", func(t *testing.T) { t.Run("ExpectedAbsolutePreState", func(t *testing.T) {
setupPreState(t, dataDir, "state.json") setupPreState(t, dataDir, "state.json")
provider, _ := setupWithTestData(dataDir, prestate) provider, _ := setupWithTestData(t, dataDir, prestate)
preState, err := provider.AbsolutePreState(context.Background()) preState, err := provider.AbsolutePreState(context.Background())
require.NoError(t, err) require.NoError(t, err)
state := mipsevm.State{ state := mipsevm.State{
...@@ -190,9 +234,10 @@ func setupTestData(t *testing.T) (string, string) { ...@@ -190,9 +234,10 @@ func setupTestData(t *testing.T) (string, string) {
return dataDir, "state.json" return dataDir, "state.json"
} }
func setupWithTestData(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.LvlInfo),
dir: dataDir, dir: dataDir,
generator: generator, generator: generator,
prestate: prestate, prestate: prestate,
...@@ -200,10 +245,28 @@ func setupWithTestData(dataDir string, prestate string) (*CannonTraceProvider, * ...@@ -200,10 +245,28 @@ func setupWithTestData(dataDir string, prestate string) (*CannonTraceProvider, *
} }
type stubGenerator struct { type stubGenerator struct {
generated []int // Using int makes assertions easier generated []int // Using int makes assertions easier
finalState *mipsevm.State
proof *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))
if e.finalState != nil && e.finalState.Step <= i {
// Requesting a trace index past the end of the trace
data, err := json.Marshal(e.finalState)
if err != nil {
return err
}
return os.WriteFile(filepath.Join(dir, finalState), data, 0644)
}
if e.proof != nil {
proofFile := filepath.Join(dir, proofsDir, fmt.Sprintf("%d.json", i))
data, err := json.Marshal(e.proof)
if err != nil {
return err
}
return os.WriteFile(proofFile, data, 0644)
}
return nil return nil
} }
...@@ -33,7 +33,7 @@ func (g *FaultGameHelper) GameDuration(ctx context.Context) time.Duration { ...@@ -33,7 +33,7 @@ func (g *FaultGameHelper) GameDuration(ctx context.Context) time.Duration {
} }
func (g *FaultGameHelper) WaitForClaimCount(ctx context.Context, count int64) { func (g *FaultGameHelper) WaitForClaimCount(ctx context.Context, count int64) {
ctx, cancel := context.WithTimeout(ctx, time.Minute) ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel() defer cancel()
err := utils.WaitFor(ctx, time.Second, func() (bool, error) { err := utils.WaitFor(ctx, time.Second, func() (bool, error) {
actual, err := g.game.ClaimDataLen(&bind.CallOpts{Context: ctx}) actual, err := g.game.ClaimDataLen(&bind.CallOpts{Context: ctx})
......
...@@ -145,7 +145,6 @@ func TestChallengerCompleteDisputeGame(t *testing.T) { ...@@ -145,7 +145,6 @@ func TestChallengerCompleteDisputeGame(t *testing.T) {
} }
func TestCannonDisputeGame(t *testing.T) { func TestCannonDisputeGame(t *testing.T) {
t.Skip("CLI-4290: op-challenger doesn't handle trace extension correctly for cannon")
InitParallel(t) InitParallel(t)
ctx := context.Background() ctx := context.Background()
......
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