Commit 40750a58 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-challenger: Support binary and JSON snapshots (#11754)

* op-challenger: Support loading json or binary cannon states.

* op-challenger: Use binary cannon snapshots

* op-challenger: Support downloading prestates in multiple formats.

* op-challenger: Verify newly downloaded snapshots.

* op-e2e: Update test to handle binary snapshots correctly.

* op-e2e: Remove unused parameter

* op-challenger: Add more varied data to the test cannon state.

* op-challenger: Add more varied data to the test cannon state.
parent 9170e93e
......@@ -139,13 +139,14 @@ func NewConfig(
Datadir: datadir,
Cannon: vm.Config{
VmType: types.TraceTypeCannon,
L1: l1EthRpc,
L1Beacon: l1BeaconApi,
L2: l2EthRpc,
SnapshotFreq: DefaultCannonSnapshotFreq,
InfoFreq: DefaultCannonInfoFreq,
DebugInfo: true,
VmType: types.TraceTypeCannon,
L1: l1EthRpc,
L1Beacon: l1BeaconApi,
L2: l2EthRpc,
SnapshotFreq: DefaultCannonSnapshotFreq,
InfoFreq: DefaultCannonInfoFreq,
DebugInfo: true,
BinarySnapshots: true,
},
Asterisc: vm.Config{
VmType: types.TraceTypeAsterisc,
......
......@@ -578,6 +578,7 @@ func NewConfigFromCLI(ctx *cli.Context, logger log.Logger) (*config.Config, erro
SnapshotFreq: ctx.Uint(CannonSnapshotFreqFlag.Name),
InfoFreq: ctx.Uint(CannonInfoFreqFlag.Name),
DebugInfo: true,
BinarySnapshots: true,
},
CannonAbsolutePreState: ctx.String(CannonPreStateFlag.Name),
CannonAbsolutePreStateBaseURL: cannonPrestatesURL,
......
......@@ -48,16 +48,18 @@ type RegisterTask struct {
}
func NewCannonRegisterTask(gameType faultTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor) *RegisterTask {
stateConverter := cannon.NewStateConverter()
return &RegisterTask{
gameType: gameType,
getPrestateProvider: cachePrestates(
gameType,
stateConverter,
m,
cfg.CannonAbsolutePreStateBaseURL,
cfg.CannonAbsolutePreState,
filepath.Join(cfg.Datadir, "cannon-prestates"),
func(path string) faultTypes.PrestateProvider {
return vm.NewPrestateProvider(path, cannon.NewStateConverter())
return vm.NewPrestateProvider(path, stateConverter)
}),
newTraceAccessor: func(
logger log.Logger,
......@@ -78,16 +80,18 @@ func NewCannonRegisterTask(gameType faultTypes.GameType, cfg *config.Config, m c
}
func NewAsteriscRegisterTask(gameType faultTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor) *RegisterTask {
stateConverter := asterisc.NewStateConverter()
return &RegisterTask{
gameType: gameType,
getPrestateProvider: cachePrestates(
gameType,
stateConverter,
m,
cfg.AsteriscAbsolutePreStateBaseURL,
cfg.AsteriscAbsolutePreState,
filepath.Join(cfg.Datadir, "asterisc-prestates"),
func(path string) faultTypes.PrestateProvider {
return vm.NewPrestateProvider(path, asterisc.NewStateConverter())
return vm.NewPrestateProvider(path, stateConverter)
}),
newTraceAccessor: func(
logger log.Logger,
......@@ -108,16 +112,18 @@ func NewAsteriscRegisterTask(gameType faultTypes.GameType, cfg *config.Config, m
}
func NewAsteriscKonaRegisterTask(gameType faultTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor) *RegisterTask {
stateConverter := asterisc.NewStateConverter()
return &RegisterTask{
gameType: gameType,
getPrestateProvider: cachePrestates(
gameType,
stateConverter,
m,
cfg.AsteriscKonaAbsolutePreStateBaseURL,
cfg.AsteriscKonaAbsolutePreState,
filepath.Join(cfg.Datadir, "asterisc-kona-prestates"),
func(path string) faultTypes.PrestateProvider {
return vm.NewPrestateProvider(path, asterisc.NewStateConverter())
return vm.NewPrestateProvider(path, stateConverter)
}),
newTraceAccessor: func(
logger log.Logger,
......@@ -162,13 +168,14 @@ func NewAlphabetRegisterTask(gameType faultTypes.GameType) *RegisterTask {
func cachePrestates(
gameType faultTypes.GameType,
stateConverter vm.StateConverter,
m caching.Metrics,
prestateBaseURL *url.URL,
preStatePath string,
prestateDir string,
newPrestateProvider func(path string) faultTypes.PrestateProvider,
) func(prestateHash common.Hash) (faultTypes.PrestateProvider, error) {
prestateSource := prestates.NewPrestateSource(prestateBaseURL, preStatePath, prestateDir)
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)
if err != nil {
......
......@@ -28,6 +28,7 @@ type AsteriscTraceProvider struct {
gameDepth types.Depth
preimageLoader *utils.PreimageLoader
stateConverter vm.StateConverter
cfg vm.Config
types.PrestateProvider
......@@ -48,6 +49,7 @@ func NewTraceProvider(logger log.Logger, m vm.Metricer, cfg vm.Config, vmCfg vm.
}),
PrestateProvider: prestateProvider,
stateConverter: NewStateConverter(),
cfg: cfg,
}
}
......@@ -122,7 +124,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(filepath.Join(p.dir, vm.FinalState))
proof, step, exited, err := p.stateConverter.ConvertStateToProof(vm.FinalStatePath(p.dir, p.cfg.BinarySnapshots))
if err != nil {
return nil, err
}
......@@ -171,6 +173,7 @@ func NewTraceProviderForTest(logger log.Logger, m vm.Metricer, cfg *config.Confi
return kvstore.NewFileKV(vm.PreimageDir(dir))
}),
stateConverter: NewStateConverter(),
cfg: cfg.Asterisc,
}
return &AsteriscTraceProviderForTest{p}
}
......@@ -181,7 +184,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(filepath.Join(p.dir, vm.FinalState))
_, step, exited, err := p.stateConverter.ConvertStateToProof(vm.FinalStatePath(p.dir, p.cfg.BinarySnapshots))
if err != nil {
return 0, fmt.Errorf("failed to load final state: %w", err)
}
......
......@@ -243,7 +243,7 @@ func (e *stubGenerator) GenerateProof(ctx context.Context, dir string, i uint64)
var err error
if e.finalState != nil && e.finalState.Step <= i {
// Requesting a trace index past the end of the trace
proofFile = filepath.Join(dir, vm.FinalState)
proofFile = vm.FinalStatePath(dir, false)
data, err = json.Marshal(e.finalState)
if err != nil {
return err
......
......@@ -29,6 +29,7 @@ type CannonTraceProvider struct {
gameDepth types.Depth
preimageLoader *utils.PreimageLoader
stateConverter vm.StateConverter
cfg vm.Config
types.PrestateProvider
......@@ -49,6 +50,7 @@ func NewTraceProvider(logger log.Logger, m vm.Metricer, cfg vm.Config, vmCfg vm.
}),
PrestateProvider: prestateProvider,
stateConverter: &StateConverter{},
cfg: cfg,
}
}
......@@ -122,7 +124,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(filepath.Join(p.dir, vm.FinalState))
proof, stateStep, exited, err := p.stateConverter.ConvertStateToProof(vm.FinalStatePath(p.dir, p.cfg.BinarySnapshots))
if err != nil {
return nil, fmt.Errorf("cannot create proof from final state: %w", err)
}
......@@ -170,6 +172,7 @@ func NewTraceProviderForTest(logger log.Logger, m vm.Metricer, cfg *config.Confi
return kvstore.NewFileKV(vm.PreimageDir(dir))
}),
stateConverter: NewStateConverter(),
cfg: cfg.Cannon,
}
return &CannonTraceProviderForTest{p}
}
......@@ -180,7 +183,8 @@ func (p *CannonTraceProviderForTest) FindStep(ctx context.Context, start uint64,
return 0, fmt.Errorf("generate cannon trace (until preimage read): %w", err)
}
// Load the step from the state cannon finished with
_, step, exited, err := p.stateConverter.ConvertStateToProof(filepath.Join(p.dir, vm.FinalState))
_, step, exited, err := p.stateConverter.ConvertStateToProof(vm.FinalStatePath(p.dir, p.cfg.BinarySnapshots))
if err != nil {
return 0, fmt.Errorf("failed to load final state: %w", err)
}
......
......@@ -261,7 +261,7 @@ func (e *stubGenerator) GenerateProof(ctx context.Context, dir string, i uint64)
var err error
if e.finalState != nil && e.finalState.Step <= i {
// Requesting a trace index past the end of the trace
proofFile = filepath.Join(dir, vm.FinalState)
proofFile = vm.FinalStatePath(dir, false)
data, err = json.Marshal(e.finalState)
if err != nil {
return err
......
package cannon
import (
"encoding/json"
"fmt"
"io"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
"github.com/ethereum-optimism/optimism/cannon/serialize"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
)
type StateConverter struct {
......@@ -36,18 +34,5 @@ func (c *StateConverter) ConvertStateToProof(statePath string) (*utils.ProofData
}
func parseState(path string) (*singlethreaded.State, error) {
file, err := ioutil.OpenDecompressed(path)
if err != nil {
return nil, fmt.Errorf("cannot open state file (%v): %w", path, err)
}
return parseStateFromReader(file)
}
func parseStateFromReader(in io.ReadCloser) (*singlethreaded.State, error) {
defer in.Close()
var state singlethreaded.State
if err := json.NewDecoder(in).Decode(&state); err != nil {
return nil, fmt.Errorf("invalid mipsevm state: %w", err)
}
return &state, nil
return serialize.Load[singlethreaded.State](path)
}
......@@ -8,6 +8,7 @@ import (
"path/filepath"
"testing"
"github.com/ethereum-optimism/optimism/cannon/serialize"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
......@@ -48,4 +49,30 @@ func TestLoadState(t *testing.T) {
require.NoError(t, json.Unmarshal(testState, &expected))
require.Equal(t, &expected, state)
})
t.Run("Binary", func(t *testing.T) {
var expected singlethreaded.State
require.NoError(t, json.Unmarshal(testState, &expected))
dir := t.TempDir()
path := filepath.Join(dir, "state.bin")
require.NoError(t, serialize.Write[*singlethreaded.State](path, &expected, 0644))
state, err := parseState(path)
require.NoError(t, err)
require.Equal(t, &expected, state)
})
t.Run("BinaryGzip", func(t *testing.T) {
var expected singlethreaded.State
require.NoError(t, json.Unmarshal(testState, &expected))
dir := t.TempDir()
path := filepath.Join(dir, "state.bin.gz")
require.NoError(t, serialize.Write[*singlethreaded.State](path, &expected, 0644))
state, err := parseState(path)
require.NoError(t, err)
require.Equal(t, &expected, state)
})
}
{
"memory": [],
"memory": [{"index":10,"data":"eJzswAENAAAAwiD7p7bHBwMAAADyHgAA//8QAAAB"}],
"preimageKey": "0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
"preimageOffset": 0,
"pc": 0,
"preimageOffset": 42,
"pc": 94,
"nextPC": 1,
"lo": 0,
"hi": 0,
"heap": 0,
"exit": 0,
"exited": false,
"step": 0,
"registers": []
"lo": 3,
"hi": 5,
"heap": 4,
"exit": 1,
"exited": true,
"step": 8849,
"registers": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2147471360,0,0]
}
......@@ -9,43 +9,64 @@ import (
"os"
"path/filepath"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum/go-ethereum/common"
)
var (
ErrPrestateUnavailable = errors.New("prestate unavailable")
// supportedFileTypes lists, in preferred order, the prestate file types to attempt to download
supportedFileTypes = []string{".bin.gz", ".json.gz", ".json"}
)
type MultiPrestateProvider struct {
baseUrl *url.URL
dataDir string
baseUrl *url.URL
dataDir string
stateConverter vm.StateConverter
}
func NewMultiPrestateProvider(baseUrl *url.URL, dataDir string) *MultiPrestateProvider {
func NewMultiPrestateProvider(baseUrl *url.URL, dataDir string, stateConverter vm.StateConverter) *MultiPrestateProvider {
return &MultiPrestateProvider{
baseUrl: baseUrl,
dataDir: dataDir,
baseUrl: baseUrl,
dataDir: dataDir,
stateConverter: stateConverter,
}
}
func (m *MultiPrestateProvider) PrestatePath(hash common.Hash) (string, error) {
path := filepath.Join(m.dataDir, hash.Hex()+".json.gz")
if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
if err := m.fetchPrestate(hash, path); err != nil {
return "", fmt.Errorf("failed to fetch prestate: %w", err)
// First try to find a previously downloaded prestate
for _, fileType := range supportedFileTypes {
path := filepath.Join(m.dataDir, hash.Hex()+fileType)
if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
continue // File doesn't exist, try the next file type
} else if err != nil {
return "", fmt.Errorf("error checking for existing prestate %v in file %v: %w", hash, path, err)
}
return path, nil // Found an existing file so use it
}
// Didn't find any available files, try to download one
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) {
combinedErr = errors.Join(combinedErr, err)
continue // Didn't find prestate in this format, try the next
} else if err != nil {
return "", fmt.Errorf("error downloading prestate %v to file %v: %w", hash, path, err)
}
} else if err != nil {
return "", fmt.Errorf("error checking for existing prestate %v: %w", hash, err)
return path, nil // Successfully downloaded a prestate so use it
}
return path, nil
return "", errors.Join(ErrPrestateUnavailable, combinedErr)
}
func (m *MultiPrestateProvider) fetchPrestate(hash common.Hash, dest string) error {
func (m *MultiPrestateProvider) fetchPrestate(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() + ".json")
prestateUrl := m.baseUrl.JoinPath(hash.Hex() + fileType)
resp, err := http.Get(prestateUrl.String())
if err != nil {
return fmt.Errorf("failed to fetch prestate from %v: %w", prestateUrl, err)
......@@ -54,7 +75,8 @@ func (m *MultiPrestateProvider) fetchPrestate(hash common.Hash, dest string) err
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("%w from url %v: status %v", ErrPrestateUnavailable, prestateUrl, resp.StatusCode)
}
out, err := ioutil.NewAtomicWriterCompressed(dest, 0o644)
tmpFile := dest + ".tmp" + fileType // Preserve the file type extension so compression is applied correctly
out, err := ioutil.NewAtomicWriterCompressed(tmpFile, 0o644)
if err != nil {
return fmt.Errorf("failed to open atomic writer for %v: %w", dest, err)
}
......@@ -68,5 +90,15 @@ func (m *MultiPrestateProvider) fetchPrestate(hash common.Hash, dest string) err
if err := out.Close(); err != nil {
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(dest)
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
return fmt.Errorf("invalid prestate from url: %v, ignoring: %w", prestateUrl, errors.Join(ErrPrestateUnavailable, err))
}
if err := os.Rename(tmpFile, dest); err != nil {
return fmt.Errorf("failed to move temp file to final destination: %w", err)
}
return nil
}
package prestates
import (
"errors"
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strings"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
......@@ -20,8 +23,8 @@ func TestDownloadPrestate(t *testing.T) {
_, _ = w.Write([]byte(r.URL.Path))
}))
defer server.Close()
provider := NewMultiPrestateProvider(parseURL(t, server.URL), dir)
hash := common.Hash{0xaa}
provider := NewMultiPrestateProvider(parseURL(t, server.URL), dir, &stubStateConverter{hash: hash})
path, err := provider.PrestatePath(hash)
require.NoError(t, err)
in, err := ioutil.OpenDecompressed(path)
......@@ -29,7 +32,7 @@ func TestDownloadPrestate(t *testing.T) {
defer in.Close()
content, err := io.ReadAll(in)
require.NoError(t, err)
require.Equal(t, "/"+hash.Hex()+".json", string(content))
require.Equal(t, "/"+hash.Hex()+".bin.gz", string(content))
}
func TestCreateDirectory(t *testing.T) {
......@@ -39,8 +42,8 @@ func TestCreateDirectory(t *testing.T) {
_, _ = w.Write([]byte(r.URL.Path))
}))
defer server.Close()
provider := NewMultiPrestateProvider(parseURL(t, server.URL), dir)
hash := common.Hash{0xaa}
provider := NewMultiPrestateProvider(parseURL(t, server.URL), dir, &stubStateConverter{hash: hash})
path, err := provider.PrestatePath(hash)
require.NoError(t, err)
in, err := ioutil.OpenDecompressed(path)
......@@ -48,13 +51,13 @@ func TestCreateDirectory(t *testing.T) {
defer in.Close()
content, err := io.ReadAll(in)
require.NoError(t, err)
require.Equal(t, "/"+hash.Hex()+".json", string(content))
require.Equal(t, "/"+hash.Hex()+".bin.gz", string(content))
}
func TestExistingPrestate(t *testing.T) {
dir := t.TempDir()
provider := NewMultiPrestateProvider(parseURL(t, "http://127.0.0.1:1"), dir)
hash := common.Hash{0xaa}
provider := NewMultiPrestateProvider(parseURL(t, "http://127.0.0.1:1"), dir, &stubStateConverter{hash: hash})
expectedFile := filepath.Join(dir, hash.Hex()+".json.gz")
err := ioutil.WriteCompressedBytes(expectedFile, []byte("expected content"), os.O_WRONLY|os.O_CREATE, 0o644)
require.NoError(t, err)
......@@ -72,16 +75,84 @@ func TestExistingPrestate(t *testing.T) {
func TestMissingPrestate(t *testing.T) {
dir := t.TempDir()
var requests []string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requests = append(requests, r.URL.Path)
w.WriteHeader(404)
}))
defer server.Close()
provider := NewMultiPrestateProvider(parseURL(t, server.URL), dir)
hash := common.Hash{0xaa}
provider := NewMultiPrestateProvider(parseURL(t, server.URL), dir, &stubStateConverter{hash: hash})
path, err := provider.PrestatePath(hash)
require.ErrorIs(t, err, ErrPrestateUnavailable)
_, err = os.Stat(path)
require.ErrorIs(t, err, os.ErrNotExist)
expectedRequests := []string{
"/" + hash.Hex() + ".bin.gz",
"/" + hash.Hex() + ".json.gz",
"/" + hash.Hex() + ".json",
}
require.Equal(t, expectedRequests, requests)
}
func TestStorePrestateWithCorrectExtension(t *testing.T) {
extensions := []string{".bin.gz", ".json.gz", ".json"}
for _, ext := range extensions {
ext := ext
t.Run(ext, func(t *testing.T) {
dir := t.TempDir()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !strings.HasSuffix(r.URL.Path, ext) {
w.WriteHeader(404)
return
}
_, _ = w.Write([]byte("content"))
}))
defer server.Close()
hash := common.Hash{0xaa}
provider := NewMultiPrestateProvider(parseURL(t, server.URL), dir, &stubStateConverter{hash: hash})
path, err := provider.PrestatePath(hash)
require.NoError(t, err)
require.Truef(t, strings.HasSuffix(path, ext), "Expected path %v to have extension %v", path, ext)
in, err := ioutil.OpenDecompressed(path)
require.NoError(t, err)
defer in.Close()
content, err := io.ReadAll(in)
require.NoError(t, err)
require.Equal(t, "content", string(content))
})
}
}
func TestDetectInvalidPrestate(t *testing.T) {
dir := t.TempDir()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("content"))
}))
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)
require.ErrorIs(t, err, ErrPrestateUnavailable)
entries, err := os.ReadDir(dir)
require.NoError(t, err)
require.Empty(t, entries, "should not leave any files in temp dir")
}
func TestDetectPrestateWithWrongHash(t *testing.T) {
dir := t.TempDir()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("content"))
}))
defer server.Close()
hash := common.Hash{0xaa}
actualHash := common.Hash{0xbb}
provider := NewMultiPrestateProvider(parseURL(t, server.URL), dir, &stubStateConverter{hash: actualHash})
_, err := provider.PrestatePath(hash)
require.ErrorIs(t, err, ErrPrestateUnavailable)
entries, err := os.ReadDir(dir)
require.NoError(t, err)
require.Empty(t, entries, "should not leave any files in temp dir")
}
func parseURL(t *testing.T, str string) *url.URL {
......@@ -89,3 +160,12 @@ func parseURL(t *testing.T, str string) *url.URL {
require.NoError(t, err)
return parsed
}
type stubStateConverter struct {
err error
hash common.Hash
}
func (s *stubStateConverter) ConvertStateToProof(path string) (*utils.ProofData, uint64, bool, error) {
return &utils.ProofData{ClaimValue: s.hash}, 0, false, s.err
}
package prestates
import "net/url"
import (
"net/url"
func NewPrestateSource(baseURL *url.URL, path string, localDir string) PrestateSource {
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm"
)
func NewPrestateSource(baseURL *url.URL, path string, localDir string, stateConverter vm.StateConverter) PrestateSource {
if baseURL != nil {
return NewMultiPrestateProvider(baseURL, localDir)
return NewMultiPrestateProvider(baseURL, localDir, stateConverter)
} else {
return NewSinglePrestateSource(path)
}
......
......@@ -23,7 +23,6 @@ const (
)
var (
ErrInvalidScalarValue = errors.New("invalid scalar value")
ErrInvalidBlobKeyPreimage = errors.New("invalid blob key preimage")
)
......
......@@ -28,11 +28,12 @@ type Metricer interface {
type Config struct {
// VM Configuration
VmType types.TraceType
VmBin string // Path to the vm executable to run when generating trace data
SnapshotFreq uint // Frequency of snapshots to create when executing (in VM instructions)
InfoFreq uint // Frequency of progress log messages (in VM instructions)
DebugInfo bool
VmType types.TraceType
VmBin string // Path to the vm executable to run when generating trace data
SnapshotFreq uint // Frequency of snapshots to create when executing (in VM instructions)
InfoFreq uint // Frequency of progress log messages (in VM instructions)
DebugInfo bool // Whether to record debug info from the execution
BinarySnapshots bool // Whether to use binary snapshots instead of JSON
// Host Configuration
L1 string
......@@ -82,13 +83,13 @@ func (e *Executor) GenerateProof(ctx context.Context, dir string, i uint64) erro
// The proof is stored at the specified directory.
func (e *Executor) DoGenerateProof(ctx context.Context, dir string, begin uint64, end uint64, extraVmArgs ...string) error {
snapshotDir := filepath.Join(dir, SnapsDir)
start, err := e.selectSnapshot(e.logger, snapshotDir, e.absolutePreState, begin)
start, err := e.selectSnapshot(e.logger, snapshotDir, e.absolutePreState, begin, e.cfg.BinarySnapshots)
if err != nil {
return fmt.Errorf("find starting snapshot: %w", err)
}
proofDir := filepath.Join(dir, utils.ProofsDir)
dataDir := PreimageDir(dir)
lastGeneratedState := filepath.Join(dir, FinalState)
lastGeneratedState := FinalStatePath(dir, e.cfg.BinarySnapshots)
args := []string{
"run",
"--input", start,
......@@ -98,7 +99,11 @@ func (e *Executor) DoGenerateProof(ctx context.Context, dir string, begin uint64
"--proof-at", "=" + strconv.FormatUint(end, 10),
"--proof-fmt", filepath.Join(proofDir, "%d.json.gz"),
"--snapshot-at", "%" + strconv.FormatUint(uint64(e.cfg.SnapshotFreq), 10),
"--snapshot-fmt", filepath.Join(snapshotDir, "%d.json.gz"),
}
if e.cfg.BinarySnapshots {
args = append(args, "--snapshot-fmt", filepath.Join(snapshotDir, "%d.bin.gz"))
} else {
args = append(args, "--snapshot-fmt", filepath.Join(snapshotDir, "%d.json.gz"))
}
if end < math.MaxUint64 {
args = append(args, "--stop-at", "="+strconv.FormatUint(end+1, 10))
......
......@@ -43,7 +43,7 @@ func TestGenerateProof(t *testing.T) {
captureExec := func(t *testing.T, cfg Config, proofAt uint64) (string, string, map[string]string) {
m := &stubVmMetrics{}
executor := NewExecutor(testlog.Logger(t, log.LevelInfo), m, cfg, NewOpProgramServerExecutor(), prestate, 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, binary bool) (string, error) {
return input, nil
}
var binary string
......@@ -82,7 +82,7 @@ func TestGenerateProof(t *testing.T) {
require.Equal(t, input, args["--input"])
require.Contains(t, args, "--meta")
require.Equal(t, "", args["--meta"])
require.Equal(t, filepath.Join(dir, FinalState), args["--output"])
require.Equal(t, FinalStatePath(dir, cfg.BinarySnapshots), args["--output"])
require.Equal(t, "=150000000", args["--proof-at"])
require.Equal(t, "=150000001", args["--stop-at"])
require.Equal(t, "%500", args["--snapshot-at"])
......@@ -128,6 +128,20 @@ func TestGenerateProof(t *testing.T) {
// so expect that it will be omitted. We'll ultimately want asterisc to execute until the program exits.
require.NotContains(t, args, "--stop-at")
})
t.Run("BinarySnapshots", func(t *testing.T) {
cfg.Network = "mainnet"
cfg.BinarySnapshots = true
_, _, args := captureExec(t, cfg, 100)
require.Equal(t, filepath.Join(dir, SnapsDir, "%d.bin.gz"), args["--snapshot-fmt"])
})
t.Run("JsonSnapshots", func(t *testing.T) {
cfg.Network = "mainnet"
cfg.BinarySnapshots = false
_, _, args := captureExec(t, cfg, 100)
require.Equal(t, filepath.Join(dir, SnapsDir, "%d.json.gz"), args["--snapshot-fmt"])
})
}
type stubVmMetrics struct {
......
......@@ -14,16 +14,26 @@ import (
"github.com/ethereum/go-ethereum/log"
)
type SnapshotSelect func(logger log.Logger, dir string, absolutePreState string, i uint64) (string, error)
type SnapshotSelect func(logger log.Logger, dir string, absolutePreState string, i uint64, binary bool) (string, error)
type CmdExecutor func(ctx context.Context, l log.Logger, binary string, args ...string) error
const (
SnapsDir = "snapshots"
PreimagesDir = "preimages"
FinalState = "final.json.gz"
SnapsDir = "snapshots"
PreimagesDir = "preimages"
finalStateJson = "final.json.gz"
finalStateBinary = "final.bin.gz"
)
var snapshotNameRegexp = regexp.MustCompile(`^[0-9]+\.json.gz$`)
func FinalStatePath(dir string, binarySnapshots bool) string {
filename := finalStateJson
if binarySnapshots {
filename = finalStateBinary
}
return filepath.Join(dir, filename)
}
var snapshotJsonNameRegexp = regexp.MustCompile(`^[0-9]+\.json\.gz$`)
var snapshotBinaryNameRegexp = regexp.MustCompile(`^[0-9]+\.bin\.gz$`)
func PreimageDir(dir string) string {
return filepath.Join(dir, PreimagesDir)
......@@ -43,7 +53,13 @@ func RunCmd(ctx context.Context, l log.Logger, binary string, args ...string) er
// FindStartingSnapshot finds the closest snapshot before the specified traceIndex in snapDir.
// If no suitable snapshot can be found it returns absolutePreState.
func FindStartingSnapshot(logger log.Logger, snapDir string, absolutePreState string, traceIndex uint64) (string, error) {
func FindStartingSnapshot(logger log.Logger, snapDir string, absolutePreState string, traceIndex uint64, binarySnapshots bool) (string, error) {
suffix := ".json.gz"
nameRegexp := snapshotJsonNameRegexp
if binarySnapshots {
suffix = ".bin.gz"
nameRegexp = snapshotBinaryNameRegexp
}
// Find the closest snapshot to start from
entries, err := os.ReadDir(snapDir)
if err != nil {
......@@ -59,11 +75,11 @@ func FindStartingSnapshot(logger log.Logger, snapDir string, absolutePreState st
continue
}
name := entry.Name()
if !snapshotNameRegexp.MatchString(name) {
if !nameRegexp.MatchString(name) {
logger.Warn("Unexpected file in snapshots dir", "parent", snapDir, "child", entry.Name())
continue
}
index, err := strconv.ParseUint(name[0:len(name)-len(".json.gz")], 10, 64)
index, err := strconv.ParseUint(name[0:len(name)-len(suffix)], 10, 64)
if err != nil {
logger.Error("Unable to parse trace index of snapshot file", "parent", snapDir, "child", entry.Name())
continue
......@@ -75,7 +91,7 @@ func FindStartingSnapshot(logger log.Logger, snapDir string, absolutePreState st
if bestSnap == 0 {
return absolutePreState, nil
}
startFrom := fmt.Sprintf("%v/%v.json.gz", snapDir, bestSnap)
startFrom := fmt.Sprintf("%v/%v%v", snapDir, bestSnap, suffix)
return startFrom, nil
}
......@@ -29,37 +29,41 @@ func createTraceProvider(
switch traceType {
case types.TraceTypeCannon:
vmConfig := vm.NewOpProgramServerExecutor()
prestate, err := getPrestate(prestateHash, cfg.CannonAbsolutePreStateBaseURL, cfg.CannonAbsolutePreState, dir)
stateConverter := cannon.NewStateConverter()
prestate, err := getPrestate(prestateHash, cfg.CannonAbsolutePreStateBaseURL, cfg.CannonAbsolutePreState, dir, stateConverter)
if err != nil {
return nil, err
}
prestateProvider := vm.NewPrestateProvider(prestate, cannon.NewStateConverter())
prestateProvider := vm.NewPrestateProvider(prestate, stateConverter)
return cannon.NewTraceProvider(logger, m, cfg.Cannon, vmConfig, prestateProvider, prestate, localInputs, dir, 42), nil
case types.TraceTypeAsterisc:
vmConfig := vm.NewOpProgramServerExecutor()
prestate, err := getPrestate(prestateHash, cfg.AsteriscAbsolutePreStateBaseURL, cfg.AsteriscAbsolutePreState, dir)
stateConverter := asterisc.NewStateConverter()
prestate, err := getPrestate(prestateHash, cfg.AsteriscAbsolutePreStateBaseURL, cfg.AsteriscAbsolutePreState, dir, stateConverter)
if err != nil {
return nil, err
}
prestateProvider := vm.NewPrestateProvider(prestate, asterisc.NewStateConverter())
prestateProvider := vm.NewPrestateProvider(prestate, stateConverter)
return asterisc.NewTraceProvider(logger, m, cfg.Asterisc, vmConfig, prestateProvider, prestate, localInputs, dir, 42), nil
case types.TraceTypeAsteriscKona:
vmConfig := vm.NewKonaServerExecutor()
prestate, err := getPrestate(prestateHash, cfg.AsteriscKonaAbsolutePreStateBaseURL, cfg.AsteriscKonaAbsolutePreState, dir)
stateConverter := asterisc.NewStateConverter()
prestate, err := getPrestate(prestateHash, cfg.AsteriscKonaAbsolutePreStateBaseURL, cfg.AsteriscKonaAbsolutePreState, dir, stateConverter)
if err != nil {
return nil, err
}
prestateProvider := vm.NewPrestateProvider(prestate, asterisc.NewStateConverter())
prestateProvider := vm.NewPrestateProvider(prestate, stateConverter)
return asterisc.NewTraceProvider(logger, m, cfg.AsteriscKona, vmConfig, prestateProvider, prestate, localInputs, dir, 42), nil
}
return nil, errors.New("invalid trace type")
}
func getPrestate(prestateHash common.Hash, prestateBaseUrl *url.URL, prestatePath string, dataDir string) (string, error) {
func getPrestate(prestateHash common.Hash, prestateBaseUrl *url.URL, prestatePath string, dataDir string, stateConverter vm.StateConverter) (string, error) {
prestateSource := prestates.NewPrestateSource(
prestateBaseUrl,
prestatePath,
filepath.Join(dataDir, "prestates"))
filepath.Join(dataDir, "prestates"),
stateConverter)
prestate, err := prestateSource.PrestatePath(prestateHash)
if err != nil {
......
......@@ -93,7 +93,7 @@ func TestBenchmarkCannon_FPP(t *testing.T) {
L2BlockNumber: l2ClaimBlockNumber,
}
debugfile := path.Join(t.TempDir(), "debug.json")
runCannon(t, ctx, sys, inputs, "sequencer", "--debug-info", debugfile)
runCannon(t, ctx, sys, inputs, "--debug-info", debugfile)
data, err := os.ReadFile(debugfile)
require.NoError(t, err)
var debuginfo mipsevm.DebugInfo
......
......@@ -2,13 +2,12 @@ package faultproofs
import (
"context"
"encoding/json"
"fmt"
"math"
"math/big"
"path/filepath"
"testing"
"github.com/ethereum-optimism/optimism/cannon/serialize"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
......@@ -25,7 +24,6 @@ import (
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/disputegame"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum-optimism/optimism/op-service/testlog"
)
......@@ -130,7 +128,7 @@ func TestPrecompiles(t *testing.T) {
L2OutputRoot: common.Hash(l2OutputRoot),
L2BlockNumber: l2ClaimBlockNumber,
}
runCannon(t, ctx, sys, inputs, "sequencer")
runCannon(t, ctx, sys, inputs)
})
t.Run("DisputePrecompile-"+test.name, func(t *testing.T) {
......@@ -242,10 +240,10 @@ func TestGranitePrecompiles(t *testing.T) {
L2OutputRoot: common.Hash(l2OutputRoot),
L2BlockNumber: l2ClaimBlockNumber,
}
runCannon(t, ctx, sys, inputs, "sequencer")
runCannon(t, ctx, sys, inputs)
}
func runCannon(t *testing.T, ctx context.Context, sys *op_e2e.System, inputs utils.LocalGameInputs, l2Node string, extraVmArgs ...string) {
func runCannon(t *testing.T, ctx context.Context, sys *op_e2e.System, inputs utils.LocalGameInputs, extraVmArgs ...string) {
l1Endpoint := sys.NodeEndpoint("l1").RPC()
l1Beacon := sys.L1BeaconEndpoint().RestHTTP()
rollupEndpoint := sys.RollupEndpoint("sequencer").RPC()
......@@ -263,23 +261,9 @@ func runCannon(t *testing.T, ctx context.Context, sys *op_e2e.System, inputs uti
err := executor.DoGenerateProof(ctx, proofsDir, math.MaxUint, math.MaxUint, extraVmArgs...)
require.NoError(t, err, "failed to generate proof")
state, err := parseState(filepath.Join(proofsDir, "final.json.gz"))
state, err := serialize.Load[singlethreaded.State](vm.FinalStatePath(proofsDir, cfg.Cannon.BinarySnapshots))
require.NoError(t, err, "failed to parse state")
require.True(t, state.Exited, "cannon did not exit")
require.Zero(t, state.ExitCode, "cannon failed with exit code %d", state.ExitCode)
t.Logf("Completed in %d steps", state.Step)
}
func parseState(path string) (*singlethreaded.State, error) {
file, err := ioutil.OpenDecompressed(path)
if err != nil {
return nil, fmt.Errorf("cannot open state file (%v): %w", path, err)
}
defer file.Close()
var state singlethreaded.State
err = json.NewDecoder(file).Decode(&state)
if err != nil {
return nil, fmt.Errorf("invalid mipsevm state (%v): %w", path, err)
}
return &state, nil
}
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