Commit 554b0da2 authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into aj/op-geth-1.12.0

parents 9f68e371 2eb8cee5
...@@ -861,6 +861,18 @@ jobs: ...@@ -861,6 +861,18 @@ jobs:
command: make fuzz command: make fuzz
working_directory: op-chain-ops working_directory: op-chain-ops
fuzz-cannon:
docker:
- image: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:latest
steps:
- checkout
- check-changed:
patterns: cannon,packages/contracts-bedrock/contracts/cannon
- run:
name: Fuzz
command: make fuzz
working_directory: cannon
depcheck: depcheck:
docker: docker:
- image: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:latest - image: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:latest
...@@ -1415,6 +1427,7 @@ workflows: ...@@ -1415,6 +1427,7 @@ workflows:
- go-mod-tidy - go-mod-tidy
- fuzz-op-node - fuzz-op-node
- fuzz-op-chain-ops - fuzz-op-chain-ops
- fuzz-cannon
- bedrock-markdown - bedrock-markdown
- go-lint: - go-lint:
name: op-batcher-lint name: op-batcher-lint
......
...@@ -23,8 +23,20 @@ test: elf ...@@ -23,8 +23,20 @@ test: elf
lint: lint:
golangci-lint run -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint --timeout 5m -e "errors.As" -e "errors.Is" golangci-lint run -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint --timeout 5m -e "errors.As" -e "errors.Is"
fuzz:
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallBrk ./mipsevm
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallClone ./mipsevm
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallMmap ./mipsevm
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallExitGroup ./mipsevm
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallFnctl ./mipsevm
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateHintRead ./mipsevm
go test -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStatePreimageRead ./mipsevm
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateHintWrite ./mipsevm
go test -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStatePreimageWrite ./mipsevm
.PHONY: \ .PHONY: \
cannon \ cannon \
clean \ clean \
test \ test \
lint lint \
fuzz
...@@ -13,6 +13,7 @@ import ( ...@@ -13,6 +13,7 @@ import (
"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/core/state"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/eth/tracers/logger"
...@@ -21,7 +22,7 @@ import ( ...@@ -21,7 +22,7 @@ import (
"github.com/ethereum-optimism/optimism/op-chain-ops/srcmap" "github.com/ethereum-optimism/optimism/op-chain-ops/srcmap"
) )
func testContractsSetup(t *testing.T) (*Contracts, *Addresses) { func testContractsSetup(t require.TestingT) (*Contracts, *Addresses) {
contracts, err := LoadContracts() contracts, err := LoadContracts()
require.NoError(t, err) require.NoError(t, err)
...@@ -48,6 +49,53 @@ func MarkdownTracer() vm.EVMLogger { ...@@ -48,6 +49,53 @@ func MarkdownTracer() vm.EVMLogger {
return logger.NewMarkdownLogger(&logger.Config{}, os.Stdout) return logger.NewMarkdownLogger(&logger.Config{}, os.Stdout)
} }
type MIPSEVM struct {
env *vm.EVM
evmState *state.StateDB
addrs *Addresses
}
func NewMIPSEVM(contracts *Contracts, addrs *Addresses) *MIPSEVM {
env, evmState := NewEVMEnv(contracts, addrs)
return &MIPSEVM{env, evmState, addrs}
}
func (m *MIPSEVM) SetTracer(tracer vm.EVMLogger) {
m.env.Config.Tracer = tracer
}
// Step is a pure function that computes the poststate from the VM state encoded in the StepWitness.
func (m *MIPSEVM) Step(t *testing.T, stepWitness *StepWitness) []byte {
sender := common.Address{0x13, 0x37}
startingGas := uint64(30_000_000)
// we take a snapshot so we can clean up the state, and isolate the logs of this instruction run.
snap := m.env.StateDB.Snapshot()
if stepWitness.HasPreimage() {
t.Logf("reading preimage key %x at offset %d", stepWitness.PreimageKey, stepWitness.PreimageOffset)
poInput, err := stepWitness.EncodePreimageOracleInput()
require.NoError(t, err, "encode preimage oracle input")
_, leftOverGas, err := m.env.Call(vm.AccountRef(m.addrs.Sender), m.addrs.Oracle, poInput, startingGas, big.NewInt(0))
require.NoErrorf(t, err, "evm should not fail, took %d gas", startingGas-leftOverGas)
}
input := stepWitness.EncodeStepInput()
ret, leftOverGas, err := m.env.Call(vm.AccountRef(sender), m.addrs.MIPS, input, startingGas, big.NewInt(0))
require.NoError(t, err, "evm should not fail")
require.Len(t, ret, 32, "expecting 32-byte state hash")
// remember state hash, to check it against state
postHash := common.Hash(*(*[32]byte)(ret))
logs := m.evmState.Logs()
require.Equal(t, 1, len(logs), "expecting a log with post-state")
evmPost := logs[0].Data
require.Equal(t, crypto.Keccak256Hash(evmPost), postHash, "logged state must be accurate")
m.env.StateDB.RevertToSnapshot(snap)
t.Logf("EVM step took %d gas, and returned stateHash %s", startingGas-leftOverGas, postHash)
return evmPost
}
func TestEVM(t *testing.T) { func TestEVM(t *testing.T) {
testFiles, err := os.ReadDir("open_mips_tests/test/bin") testFiles, err := os.ReadDir("open_mips_tests/test/bin")
require.NoError(t, err) require.NoError(t, err)
...@@ -55,7 +103,6 @@ func TestEVM(t *testing.T) { ...@@ -55,7 +103,6 @@ func TestEVM(t *testing.T) {
contracts, addrs := testContractsSetup(t) contracts, addrs := testContractsSetup(t)
var tracer vm.EVMLogger // no-tracer by default, but see SourceMapTracer and MarkdownTracer var tracer vm.EVMLogger // no-tracer by default, but see SourceMapTracer and MarkdownTracer
//tracer = SourceMapTracer(t, contracts, addrs) //tracer = SourceMapTracer(t, contracts, addrs)
sender := common.Address{0x13, 0x37}
for _, f := range testFiles { for _, f := range testFiles {
t.Run(f.Name(), func(t *testing.T) { t.Run(f.Name(), func(t *testing.T) {
...@@ -66,8 +113,8 @@ func TestEVM(t *testing.T) { ...@@ -66,8 +113,8 @@ func TestEVM(t *testing.T) {
// Short-circuit early for exit_group.bin // Short-circuit early for exit_group.bin
exitGroup := f.Name() == "exit_group.bin" exitGroup := f.Name() == "exit_group.bin"
env, evmState := NewEVMEnv(contracts, addrs) evm := NewMIPSEVM(contracts, addrs)
env.Config.Tracer = tracer evm.SetTracer(tracer)
fn := path.Join("open_mips_tests/test/bin", f.Name()) fn := path.Join("open_mips_tests/test/bin", f.Name())
programMem, err := os.ReadFile(fn) programMem, err := os.ReadFile(fn)
...@@ -79,58 +126,31 @@ func TestEVM(t *testing.T) { ...@@ -79,58 +126,31 @@ func TestEVM(t *testing.T) {
// set the return address ($ra) to jump into when test completes // set the return address ($ra) to jump into when test completes
state.Registers[31] = endAddr state.Registers[31] = endAddr
us := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr) goState := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
for i := 0; i < 1000; i++ { for i := 0; i < 1000; i++ {
if us.state.PC == endAddr { if goState.state.PC == endAddr {
break break
} }
if exitGroup && us.state.Exited { if exitGroup && goState.state.Exited {
break break
} }
insn := state.Memory.GetMemory(state.PC) insn := state.Memory.GetMemory(state.PC)
t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.PC, insn) t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.PC, insn)
stepWitness, err := us.Step(true) stepWitness, err := goState.Step(true)
require.NoError(t, err) require.NoError(t, err)
input := stepWitness.EncodeStepInput() evmPost := evm.Step(t, stepWitness)
startingGas := uint64(30_000_000)
// we take a snapshot so we can clean up the state, and isolate the logs of this instruction run.
snap := env.StateDB.Snapshot()
// prepare pre-image oracle data, if any
if stepWitness.HasPreimage() {
t.Logf("reading preimage key %x at offset %d", stepWitness.PreimageKey, stepWitness.PreimageOffset)
poInput, err := stepWitness.EncodePreimageOracleInput()
require.NoError(t, err, "encode preimage oracle input")
_, leftOverGas, err := env.Call(vm.AccountRef(addrs.Sender), addrs.Oracle, poInput, startingGas, big.NewInt(0))
require.NoErrorf(t, err, "evm should not fail, took %d gas", startingGas-leftOverGas)
}
ret, leftOverGas, err := env.Call(vm.AccountRef(sender), addrs.MIPS, input, startingGas, big.NewInt(0))
require.NoError(t, err, "evm should not fail")
require.Len(t, ret, 32, "expecting 32-byte state hash")
// remember state hash, to check it against state
postHash := common.Hash(*(*[32]byte)(ret))
logs := evmState.Logs()
require.Equal(t, 1, len(logs), "expecting a log with post-state")
evmPost := logs[0].Data
require.Equal(t, crypto.Keccak256Hash(evmPost), postHash, "logged state must be accurate")
env.StateDB.RevertToSnapshot(snap)
t.Logf("EVM step took %d gas, and returned stateHash %s", startingGas-leftOverGas, postHash)
// verify the post-state matches. // verify the post-state matches.
// TODO: maybe more readable to decode the evmPost state, and do attribute-wise comparison. // TODO: maybe more readable to decode the evmPost state, and do attribute-wise comparison.
uniPost := us.state.EncodeWitness() goPost := goState.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(uniPost).String(), hexutil.Bytes(evmPost).String(), require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM") "mipsevm produced different state than EVM")
} }
if exitGroup { if exitGroup {
require.NotEqual(t, uint32(endAddr), us.state.PC, "must not reach end") require.NotEqual(t, uint32(endAddr), goState.state.PC, "must not reach end")
require.True(t, us.state.Exited, "must set exited state") require.True(t, goState.state.Exited, "must set exited state")
require.Equal(t, uint8(1), us.state.ExitCode, "must exit with 1") require.Equal(t, uint8(1), goState.state.ExitCode, "must exit with 1")
} else { } else {
require.Equal(t, uint32(endAddr), state.PC, "must reach end") require.Equal(t, uint32(endAddr), state.PC, "must reach end")
// inspect test result // inspect test result
...@@ -160,8 +180,8 @@ func TestEVMFault(t *testing.T) { ...@@ -160,8 +180,8 @@ func TestEVMFault(t *testing.T) {
// set the return address ($ra) to jump into when test completes // set the return address ($ra) to jump into when test completes
state.Registers[31] = endAddr state.Registers[31] = endAddr
us := NewInstrumentedState(state, nil, os.Stdout, os.Stderr) goState := NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
require.Panics(t, func() { _, _ = us.Step(true) }, "must panic on illegal instruction") require.Panics(t, func() { _, _ = goState.Step(true) }, "must panic on illegal instruction")
insnProof := initialState.Memory.MerkleProof(0) insnProof := initialState.Memory.MerkleProof(0)
stepWitness := &StepWitness{ stepWitness := &StepWitness{
...@@ -181,7 +201,6 @@ func TestHelloEVM(t *testing.T) { ...@@ -181,7 +201,6 @@ func TestHelloEVM(t *testing.T) {
contracts, addrs := testContractsSetup(t) contracts, addrs := testContractsSetup(t)
var tracer vm.EVMLogger // no-tracer by default, but see SourceMapTracer and MarkdownTracer var tracer vm.EVMLogger // no-tracer by default, but see SourceMapTracer and MarkdownTracer
//tracer = SourceMapTracer(t, contracts, addrs) //tracer = SourceMapTracer(t, contracts, addrs)
sender := common.Address{0x13, 0x37}
elfProgram, err := elf.Open("../example/bin/hello.elf") elfProgram, err := elf.Open("../example/bin/hello.elf")
require.NoError(t, err, "open ELF file") require.NoError(t, err, "open ELF file")
...@@ -194,14 +213,11 @@ func TestHelloEVM(t *testing.T) { ...@@ -194,14 +213,11 @@ func TestHelloEVM(t *testing.T) {
require.NoError(t, PatchStack(state), "add initial stack") require.NoError(t, PatchStack(state), "add initial stack")
var stdOutBuf, stdErrBuf bytes.Buffer var stdOutBuf, stdErrBuf bytes.Buffer
us := NewInstrumentedState(state, nil, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr)) goState := NewInstrumentedState(state, nil, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr))
env, evmState := NewEVMEnv(contracts, addrs)
env.Config.Tracer = tracer
start := time.Now() start := time.Now()
for i := 0; i < 400_000; i++ { for i := 0; i < 400_000; i++ {
if us.state.Exited { if goState.state.Exited {
break break
} }
insn := state.Memory.GetMemory(state.PC) insn := state.Memory.GetMemory(state.PC)
...@@ -209,30 +225,16 @@ func TestHelloEVM(t *testing.T) { ...@@ -209,30 +225,16 @@ func TestHelloEVM(t *testing.T) {
t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.PC, insn) t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.PC, insn)
} }
stepWitness, err := us.Step(true) evm := NewMIPSEVM(contracts, addrs)
require.NoError(t, err) evm.SetTracer(tracer)
input := stepWitness.EncodeStepInput()
startingGas := uint64(30_000_000)
// we take a snapshot so we can clean up the state, and isolate the logs of this instruction run.
snap := env.StateDB.Snapshot()
ret, leftOverGas, err := env.Call(vm.AccountRef(sender), addrs.MIPS, input, startingGas, big.NewInt(0))
require.NoErrorf(t, err, "evm should not fail, took %d gas", startingGas-leftOverGas)
require.Len(t, ret, 32, "expecting 32-byte state hash")
// remember state hash, to check it against state
postHash := common.Hash(*(*[32]byte)(ret))
logs := evmState.Logs()
require.Equal(t, 1, len(logs), "expecting a log with post-state")
evmPost := logs[0].Data
require.Equal(t, crypto.Keccak256Hash(evmPost), postHash, "logged state must be accurate")
env.StateDB.RevertToSnapshot(snap)
//t.Logf("EVM step took %d gas, and returned stateHash %s", startingGas-leftOverGas, postHash)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
evmPost := evm.Step(t, stepWitness)
// verify the post-state matches. // verify the post-state matches.
// TODO: maybe more readable to decode the evmPost state, and do attribute-wise comparison. // TODO: maybe more readable to decode the evmPost state, and do attribute-wise comparison.
uniPost := us.state.EncodeWitness() goPost := goState.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(uniPost).String(), hexutil.Bytes(evmPost).String(), require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM") "mipsevm produced different state than EVM")
} }
end := time.Now() end := time.Now()
...@@ -264,13 +266,10 @@ func TestClaimEVM(t *testing.T) { ...@@ -264,13 +266,10 @@ func TestClaimEVM(t *testing.T) {
oracle, expectedStdOut, expectedStdErr := claimTestOracle(t) oracle, expectedStdOut, expectedStdErr := claimTestOracle(t)
var stdOutBuf, stdErrBuf bytes.Buffer var stdOutBuf, stdErrBuf bytes.Buffer
us := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr)) goState := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr))
env, evmState := NewEVMEnv(contracts, addrs)
env.Config.Tracer = tracer
for i := 0; i < 2000_000; i++ { for i := 0; i < 2000_000; i++ {
if us.state.Exited { if goState.state.Exited {
break break
} }
...@@ -279,32 +278,16 @@ func TestClaimEVM(t *testing.T) { ...@@ -279,32 +278,16 @@ func TestClaimEVM(t *testing.T) {
t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.PC, insn) t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.PC, insn)
} }
stepWitness, err := us.Step(true) stepWitness, err := goState.Step(true)
require.NoError(t, err) require.NoError(t, err)
input := stepWitness.EncodeStepInput()
startingGas := uint64(30_000_000)
// we take a snapshot so we can clean up the state, and isolate the logs of this instruction run.
snap := env.StateDB.Snapshot()
// prepare pre-image oracle data, if any evm := NewMIPSEVM(contracts, addrs)
if stepWitness.HasPreimage() { evm.SetTracer(tracer)
poInput, err := stepWitness.EncodePreimageOracleInput() evmPost := evm.Step(t, stepWitness)
require.NoError(t, err, "encode preimage oracle input")
_, leftOverGas, err := env.Call(vm.AccountRef(addrs.Sender), addrs.Oracle, poInput, startingGas, big.NewInt(0))
require.NoErrorf(t, err, "evm should not fail, took %d gas", startingGas-leftOverGas)
}
ret, leftOverGas, err := env.Call(vm.AccountRef(addrs.Sender), addrs.MIPS, input, startingGas, big.NewInt(0)) goPost := goState.state.EncodeWitness()
require.NoErrorf(t, err, "evm should not fail, took %d gas", startingGas-leftOverGas) require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
require.Len(t, ret, 32, "expecting 32-byte state hash") "mipsevm produced different state than EVM")
// remember state hash, to check it against state
postHash := common.Hash(*(*[32]byte)(ret))
logs := evmState.Logs()
require.Equal(t, 1, len(logs), "expecting a log with post-state")
evmPost := logs[0].Data
require.Equal(t, crypto.Keccak256Hash(evmPost), postHash, "logged state must be accurate")
env.StateDB.RevertToSnapshot(snap)
} }
require.True(t, state.Exited, "must complete program") require.True(t, state.Exited, "must complete program")
......
package mipsevm
import (
"os"
"testing"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
)
const syscallInsn = uint32(0x00_00_00_0c)
func FuzzStateSyscallBrk(f *testing.F) {
contracts, addrs := testContractsSetup(f)
f.Fuzz(func(t *testing.T, pc uint32, step uint64, preimageOffset uint32) {
pc = pc & 0xFF_FF_FF_FC // align PC
nextPC := pc + 4
state := &State{
PC: pc,
NextPC: nextPC,
LO: 0,
HI: 0,
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysBrk},
Step: step,
PreimageKey: common.Hash{},
PreimageOffset: preimageOffset,
}
state.Memory.SetMemory(pc, syscallInsn)
preStateRoot := state.Memory.MerkleRoot()
expectedRegisters := state.Registers
expectedRegisters[2] = 0x4000_0000
goState := NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, pc+4, state.PC)
require.Equal(t, nextPC+4, state.NextPC)
require.Equal(t, uint32(0), state.LO)
require.Equal(t, uint32(0), state.HI)
require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, expectedRegisters, state.Registers)
require.Equal(t, step+1, state.Step)
require.Equal(t, common.Hash{}, state.PreimageKey)
require.Equal(t, preimageOffset, state.PreimageOffset)
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness)
goPost := goState.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
func FuzzStateSyscallClone(f *testing.F) {
contracts, addrs := testContractsSetup(f)
f.Fuzz(func(t *testing.T, pc uint32, step uint64, preimageOffset uint32) {
pc = pc & 0xFF_FF_FF_FC // align PC
nextPC := pc + 4
state := &State{
PC: pc,
NextPC: nextPC,
LO: 0,
HI: 0,
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysClone},
Step: step,
PreimageOffset: preimageOffset,
}
state.Memory.SetMemory(pc, syscallInsn)
preStateRoot := state.Memory.MerkleRoot()
expectedRegisters := state.Registers
expectedRegisters[2] = 0x1
goState := NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, pc+4, state.PC)
require.Equal(t, nextPC+4, state.NextPC)
require.Equal(t, uint32(0), state.LO)
require.Equal(t, uint32(0), state.HI)
require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, expectedRegisters, state.Registers)
require.Equal(t, step+1, state.Step)
require.Equal(t, common.Hash{}, state.PreimageKey)
require.Equal(t, preimageOffset, state.PreimageOffset)
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness)
goPost := goState.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
func FuzzStateSyscallMmap(f *testing.F) {
contracts, addrs := testContractsSetup(f)
f.Fuzz(func(t *testing.T, addr uint32, siz uint32, heap uint32) {
state := &State{
PC: 0,
NextPC: 4,
LO: 0,
HI: 0,
Heap: heap,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysMmap, 4: addr, 5: siz},
Step: 0,
PreimageOffset: 0,
}
state.Memory.SetMemory(0, syscallInsn)
preStateRoot := state.Memory.MerkleRoot()
preStateRegisters := state.Registers
goState := NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.PC)
require.Equal(t, uint32(8), state.NextPC)
require.Equal(t, uint32(0), state.LO)
require.Equal(t, uint32(0), state.HI)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, uint64(1), state.Step)
require.Equal(t, common.Hash{}, state.PreimageKey)
require.Equal(t, uint32(0), state.PreimageOffset)
if addr == 0 {
expectedRegisters := preStateRegisters
expectedRegisters[2] = heap
require.Equal(t, expectedRegisters, state.Registers)
sizAlign := siz
if sizAlign&PageAddrMask != 0 { // adjust size to align with page size
sizAlign = siz + PageSize - (siz & PageAddrMask)
}
require.Equal(t, uint32(heap+sizAlign), state.Heap)
} else {
expectedRegisters := preStateRegisters
expectedRegisters[2] = addr
require.Equal(t, expectedRegisters, state.Registers)
require.Equal(t, uint32(heap), state.Heap)
}
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness)
goPost := goState.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
func FuzzStateSyscallExitGroup(f *testing.F) {
contracts, addrs := testContractsSetup(f)
f.Fuzz(func(t *testing.T, exitCode uint8, pc uint32, step uint64) {
pc = pc & 0xFF_FF_FF_FC // align PC
nextPC := pc + 4
state := &State{
PC: pc,
NextPC: nextPC,
LO: 0,
HI: 0,
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysExitGroup, 4: uint32(exitCode)},
Step: step,
PreimageOffset: 0,
}
state.Memory.SetMemory(pc, syscallInsn)
preStateRoot := state.Memory.MerkleRoot()
preStateRegisters := state.Registers
goState := NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, pc, state.PC)
require.Equal(t, nextPC, state.NextPC)
require.Equal(t, uint32(0), state.LO)
require.Equal(t, uint32(0), state.HI)
require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(exitCode), state.ExitCode)
require.Equal(t, true, state.Exited)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, preStateRegisters, state.Registers)
require.Equal(t, step+1, state.Step)
require.Equal(t, common.Hash{}, state.PreimageKey)
require.Equal(t, uint32(0), state.PreimageOffset)
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness)
goPost := goState.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
func FuzzStateSyscallFnctl(f *testing.F) {
contracts, addrs := testContractsSetup(f)
f.Fuzz(func(t *testing.T, fd uint32, cmd uint32) {
state := &State{
PC: 0,
NextPC: 4,
LO: 0,
HI: 0,
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysFcntl, 4: fd, 5: cmd},
Step: 0,
PreimageOffset: 0,
}
state.Memory.SetMemory(0, syscallInsn)
preStateRoot := state.Memory.MerkleRoot()
preStateRegisters := state.Registers
goState := NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.PC)
require.Equal(t, uint32(8), state.NextPC)
require.Equal(t, uint32(0), state.LO)
require.Equal(t, uint32(0), state.HI)
require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, uint64(1), state.Step)
require.Equal(t, common.Hash{}, state.PreimageKey)
require.Equal(t, uint32(0), state.PreimageOffset)
if cmd == 3 {
expectedRegisters := preStateRegisters
switch fd {
case fdStdin, fdPreimageRead, fdHintRead:
expectedRegisters[2] = 0
case fdStdout, fdStderr, fdPreimageWrite, fdHintWrite:
expectedRegisters[2] = 1
default:
expectedRegisters[2] = 0xFF_FF_FF_FF
expectedRegisters[7] = MipsEBADF
}
require.Equal(t, expectedRegisters, state.Registers)
} else {
expectedRegisters := preStateRegisters
expectedRegisters[2] = 0xFF_FF_FF_FF
expectedRegisters[7] = MipsEINVAL
require.Equal(t, expectedRegisters, state.Registers)
}
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness)
goPost := goState.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
func FuzzStateHintRead(f *testing.F) {
contracts, addrs := testContractsSetup(f)
f.Fuzz(func(t *testing.T, addr uint32, count uint32) {
preimageData := []byte("hello world")
state := &State{
PC: 0,
NextPC: 4,
LO: 0,
HI: 0,
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysRead, 4: fdHintRead, 5: addr, 6: count},
Step: 0,
PreimageKey: preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey(),
PreimageOffset: 0,
}
state.Memory.SetMemory(0, syscallInsn)
preStatePreimageKey := state.PreimageKey
preStateRoot := state.Memory.MerkleRoot()
expectedRegisters := state.Registers
expectedRegisters[2] = count
oracle := staticOracle(t, preimageData) // only used for hinting
goState := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.PC)
require.Equal(t, uint32(8), state.NextPC)
require.Equal(t, uint32(0), state.LO)
require.Equal(t, uint32(0), state.HI)
require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, uint64(1), state.Step)
require.Equal(t, preStatePreimageKey, state.PreimageKey)
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness)
goPost := goState.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
func FuzzStatePreimageRead(f *testing.F) {
contracts, addrs := testContractsSetup(f)
f.Fuzz(func(t *testing.T, addr uint32, count uint32, preimageOffset uint32) {
preimageData := []byte("hello world")
if preimageOffset >= uint32(len(preimageData)) {
t.SkipNow()
}
state := &State{
PC: 0,
NextPC: 4,
LO: 0,
HI: 0,
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysRead, 4: fdPreimageRead, 5: addr, 6: count},
Step: 0,
PreimageKey: preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey(),
PreimageOffset: preimageOffset,
}
state.Memory.SetMemory(0, syscallInsn)
preStatePreimageKey := state.PreimageKey
preStateRoot := state.Memory.MerkleRoot()
writeLen := count
if writeLen > 4 {
writeLen = 4
}
if preimageOffset+writeLen > uint32(8+len(preimageData)) {
writeLen = uint32(8+len(preimageData)) - preimageOffset
}
oracle := staticOracle(t, preimageData)
goState := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.True(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.PC)
require.Equal(t, uint32(8), state.NextPC)
require.Equal(t, uint32(0), state.LO)
require.Equal(t, uint32(0), state.HI)
require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
if writeLen > 0 {
// Memory may be unchanged if we're writing the first zero-valued 7 bytes of the pre-image.
//require.NotEqual(t, preStateRoot, state.Memory.MerkleRoot())
require.Greater(t, state.PreimageOffset, preimageOffset)
} else {
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, state.PreimageOffset, preimageOffset)
}
require.Equal(t, uint64(1), state.Step)
require.Equal(t, preStatePreimageKey, state.PreimageKey)
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness)
goPost := goState.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
func FuzzStateHintWrite(f *testing.F) {
contracts, addrs := testContractsSetup(f)
f.Fuzz(func(t *testing.T, addr uint32, count uint32) {
preimageData := []byte("hello world")
state := &State{
PC: 0,
NextPC: 4,
LO: 0,
HI: 0,
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysWrite, 4: fdHintWrite, 5: addr, 6: count},
Step: 0,
PreimageKey: preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey(),
PreimageOffset: 0,
// This is only used by mips.go. The reads a zeroed page-sized buffer when reading hint data from memory.
// We pre-allocate a buffer for the read hint data to be copied into.
LastHint: make(hexutil.Bytes, PageSize),
}
state.Memory.SetMemory(0, syscallInsn)
preStatePreimageKey := state.PreimageKey
preStateRoot := state.Memory.MerkleRoot()
expectedRegisters := state.Registers
expectedRegisters[2] = count
oracle := staticOracle(t, preimageData) // only used for hinting
goState := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.PC)
require.Equal(t, uint32(8), state.NextPC)
require.Equal(t, uint32(0), state.LO)
require.Equal(t, uint32(0), state.HI)
require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, uint64(1), state.Step)
require.Equal(t, preStatePreimageKey, state.PreimageKey)
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness)
goPost := goState.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
func FuzzStatePreimageWrite(f *testing.F) {
contracts, addrs := testContractsSetup(f)
f.Fuzz(func(t *testing.T, addr uint32, count uint32) {
preimageData := []byte("hello world")
state := &State{
PC: 0,
NextPC: 4,
LO: 0,
HI: 0,
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysWrite, 4: fdPreimageWrite, 5: addr, 6: count},
Step: 0,
PreimageKey: preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey(),
PreimageOffset: 128,
}
state.Memory.SetMemory(0, syscallInsn)
preStateRoot := state.Memory.MerkleRoot()
expectedRegisters := state.Registers
sz := 4 - (addr & 0x3)
if sz < count {
sz = count
}
expectedRegisters[2] = sz
oracle := staticOracle(t, preimageData)
goState := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.PC)
require.Equal(t, uint32(8), state.NextPC)
require.Equal(t, uint32(0), state.LO)
require.Equal(t, uint32(0), state.HI)
require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, uint64(1), state.Step)
require.Equal(t, uint32(0), state.PreimageOffset)
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness)
goPost := goState.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
...@@ -6,6 +6,16 @@ import ( ...@@ -6,6 +6,16 @@ import (
"io" "io"
) )
const (
sysMmap = 4090
sysBrk = 4045
sysClone = 4120
sysExitGroup = 4246
sysRead = 4003
sysWrite = 4004
sysFcntl = 4055
)
func (m *InstrumentedState) readPreimage(key [32]byte, offset uint32) (dat [32]byte, datLen uint32) { func (m *InstrumentedState) readPreimage(key [32]byte, offset uint32) (dat [32]byte, datLen uint32) {
preimage := m.lastPreimage preimage := m.lastPreimage
if key != m.lastPreimageKey { if key != m.lastPreimageKey {
...@@ -43,7 +53,7 @@ func (m *InstrumentedState) handleSyscall() error { ...@@ -43,7 +53,7 @@ func (m *InstrumentedState) handleSyscall() error {
//fmt.Printf("syscall: %d\n", syscallNum) //fmt.Printf("syscall: %d\n", syscallNum)
switch syscallNum { switch syscallNum {
case 4090: // mmap case sysMmap:
sz := a1 sz := a1
if sz&PageAddrMask != 0 { // adjust size to align with page size if sz&PageAddrMask != 0 { // adjust size to align with page size
sz += PageSize - (sz & PageAddrMask) sz += PageSize - (sz & PageAddrMask)
...@@ -56,15 +66,15 @@ func (m *InstrumentedState) handleSyscall() error { ...@@ -56,15 +66,15 @@ func (m *InstrumentedState) handleSyscall() error {
v0 = a0 v0 = a0
//fmt.Printf("mmap hint 0x%x size 0x%x\n", v0, sz) //fmt.Printf("mmap hint 0x%x size 0x%x\n", v0, sz)
} }
case 4045: // brk case sysBrk:
v0 = 0x40000000 v0 = 0x40000000
case 4120: // clone (not supported) case sysClone: // clone (not supported)
v0 = 1 v0 = 1
case 4246: // exit_group case sysExitGroup:
m.state.Exited = true m.state.Exited = true
m.state.ExitCode = uint8(a0) m.state.ExitCode = uint8(a0)
return nil return nil
case 4003: // read case sysRead:
// args: a0 = fd, a1 = addr, a2 = count // args: a0 = fd, a1 = addr, a2 = count
// returns: v0 = read, v1 = err code // returns: v0 = read, v1 = err code
switch a0 { switch a0 {
...@@ -98,7 +108,7 @@ func (m *InstrumentedState) handleSyscall() error { ...@@ -98,7 +108,7 @@ func (m *InstrumentedState) handleSyscall() error {
v0 = 0xFFffFFff v0 = 0xFFffFFff
v1 = MipsEBADF v1 = MipsEBADF
} }
case 4004: // write case sysWrite:
// args: a0 = fd, a1 = addr, a2 = count // args: a0 = fd, a1 = addr, a2 = count
// returns: v0 = written, v1 = err code // returns: v0 = written, v1 = err code
switch a0 { switch a0 {
...@@ -144,7 +154,7 @@ func (m *InstrumentedState) handleSyscall() error { ...@@ -144,7 +154,7 @@ func (m *InstrumentedState) handleSyscall() error {
v0 = 0xFFffFFff v0 = 0xFFffFFff
v1 = MipsEBADF v1 = MipsEBADF
} }
case 4055: // fcntl case sysFcntl:
// args: a0 = fd, a1 = cmd // args: a0 = fd, a1 = cmd
if a1 == 3 { // F_GETFL: get file descriptor flags if a1 == 3 { // F_GETFL: get file descriptor flags
switch a0 { switch a0 {
......
...@@ -2,6 +2,7 @@ package solc ...@@ -2,6 +2,7 @@ package solc
import ( import (
"encoding/json" "encoding/json"
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
) )
...@@ -36,7 +37,8 @@ type CompilerOutput struct { ...@@ -36,7 +37,8 @@ type CompilerOutput struct {
type CompilerOutputContracts map[string]CompilerOutputContract type CompilerOutputContracts map[string]CompilerOutputContract
// TODO(tynes): ignoring devdoc and userdoc for now // CompilerOutputContract represents the solc compiler output for a contract.
// Ignoring some fields such as devdoc and userdoc.
type CompilerOutputContract struct { type CompilerOutputContract struct {
Abi abi.ABI `json:"abi"` Abi abi.ABI `json:"abi"`
Evm CompilerOutputEvm `json:"evm"` Evm CompilerOutputEvm `json:"evm"`
...@@ -44,11 +46,24 @@ type CompilerOutputContract struct { ...@@ -44,11 +46,24 @@ type CompilerOutputContract struct {
StorageLayout StorageLayout `json:"storageLayout"` StorageLayout StorageLayout `json:"storageLayout"`
} }
// StorageLayout represents the solc compilers output storage layout for
// a contract.
type StorageLayout struct { type StorageLayout struct {
Storage []StorageLayoutEntry `json:"storage"` Storage []StorageLayoutEntry `json:"storage"`
Types map[string]StorageLayoutType `json:"types"` Types map[string]StorageLayoutType `json:"types"`
} }
// GetStorageLayoutEntry returns the StorageLayoutEntry where the label matches
// the provided name.
func (s *StorageLayout) GetStorageLayoutEntry(name string) (StorageLayoutEntry, error) {
for _, entry := range s.Storage {
if entry.Label == name {
return entry, nil
}
}
return StorageLayoutEntry{}, fmt.Errorf("%s not found", name)
}
type StorageLayoutEntry struct { type StorageLayoutEntry struct {
AstId uint `json:"astId"` AstId uint `json:"astId"`
Contract string `json:"contract"` Contract string `json:"contract"`
......
...@@ -169,6 +169,22 @@ type DeployConfig struct { ...@@ -169,6 +169,22 @@ type DeployConfig struct {
FundDevAccounts bool `json:"fundDevAccounts"` FundDevAccounts bool `json:"fundDevAccounts"`
} }
// Copy will deeply copy the DeployConfig. This does a JSON roundtrip to copy
// which makes it easier to maintain, we do not need efficiency in this case.
func (d *DeployConfig) Copy() *DeployConfig {
raw, err := json.Marshal(d)
if err != nil {
panic(err)
}
cpy := DeployConfig{}
if err = json.Unmarshal(raw, &cpy); err != nil {
panic(err)
}
return &cpy
}
// Check will ensure that the config is sane and return an error when it is not // Check will ensure that the config is sane and return an error when it is not
func (d *DeployConfig) Check() error { func (d *DeployConfig) Check() error {
if d.L1StartingBlockTag == nil { if d.L1StartingBlockTag == nil {
......
...@@ -48,3 +48,19 @@ func TestRegolithTimeAsOffset(t *testing.T) { ...@@ -48,3 +48,19 @@ func TestRegolithTimeAsOffset(t *testing.T) {
config := &DeployConfig{L2GenesisRegolithTimeOffset: &regolithOffset} config := &DeployConfig{L2GenesisRegolithTimeOffset: &regolithOffset}
require.Equal(t, uint64(1500+5000), *config.RegolithTime(5000)) require.Equal(t, uint64(1500+5000), *config.RegolithTime(5000))
} }
// TestCopy will copy a DeployConfig and ensure that the copy is equal to the original.
func TestCopy(t *testing.T) {
b, err := os.ReadFile("testdata/test-deploy-config-full.json")
require.NoError(t, err)
decoded := new(DeployConfig)
require.NoError(t, json.NewDecoder(bytes.NewReader(b)).Decode(decoded))
cpy := decoded.Copy()
require.EqualValues(t, decoded, cpy)
offset := hexutil.Uint64(100)
cpy.L2GenesisRegolithTimeOffset = &offset
require.NotEqual(t, decoded, cpy)
}
...@@ -15,7 +15,10 @@ import ( ...@@ -15,7 +15,10 @@ import (
var ( var (
l1EthRpc = "http://example.com:8545" l1EthRpc = "http://example.com:8545"
gameAddressValue = "0xaa00000000000000000000000000000000000000" gameAddressValue = "0xaa00000000000000000000000000000000000000"
cannonBin = "./bin/cannon"
cannonPreState = "./pre.json"
cannonDatadir = "./test_data" cannonDatadir = "./test_data"
cannonL2 = "http://example.com:9545"
alphabetTrace = "abcdefghijz" alphabetTrace = "abcdefghijz"
agreeWithProposedOutput = "true" agreeWithProposedOutput = "true"
gameDepth = "4" gameDepth = "4"
...@@ -23,13 +26,13 @@ var ( ...@@ -23,13 +26,13 @@ var (
func TestLogLevel(t *testing.T) { func TestLogLevel(t *testing.T) {
t.Run("RejectInvalid", func(t *testing.T) { t.Run("RejectInvalid", func(t *testing.T) {
verifyArgsInvalid(t, "unknown level: foo", addRequiredArgs("--log.level=foo")) verifyArgsInvalid(t, "unknown level: foo", addRequiredArgs(config.TraceTypeAlphabet, "--log.level=foo"))
}) })
for _, lvl := range []string{"trace", "debug", "info", "error", "crit"} { for _, lvl := range []string{"trace", "debug", "info", "error", "crit"} {
lvl := lvl lvl := lvl
t.Run("AcceptValid_"+lvl, func(t *testing.T) { t.Run("AcceptValid_"+lvl, func(t *testing.T) {
logger, _, err := runWithArgs(addRequiredArgs("--log.level", lvl)) logger, _, err := runWithArgs(addRequiredArgs(config.TraceTypeAlphabet, "--log.level", lvl))
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, logger) require.NotNil(t, logger)
}) })
...@@ -37,24 +40,29 @@ func TestLogLevel(t *testing.T) { ...@@ -37,24 +40,29 @@ func TestLogLevel(t *testing.T) {
} }
func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) { func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs()) cfg := configForArgs(t, addRequiredArgs(config.TraceTypeAlphabet))
defaultCfg := config.NewConfig(l1EthRpc, common.HexToAddress(gameAddressValue), config.TraceTypeAlphabet, alphabetTrace, cannonDatadir, true, 4) defaultCfg := config.NewConfig(l1EthRpc, common.HexToAddress(gameAddressValue), config.TraceTypeAlphabet, true, 4)
// Add in the extra CLI options required when using alphabet trace type
defaultCfg.AlphabetTrace = alphabetTrace
require.Equal(t, defaultCfg, cfg) require.Equal(t, defaultCfg, cfg)
} }
func TestDefaultConfigIsValid(t *testing.T) { func TestDefaultConfigIsValid(t *testing.T) {
cfg := config.NewConfig(l1EthRpc, common.HexToAddress(gameAddressValue), config.TraceTypeAlphabet, alphabetTrace, cannonDatadir, true, 4) cfg := config.NewConfig(l1EthRpc, common.HexToAddress(gameAddressValue), config.TraceTypeAlphabet, true, 4)
// Add in options that are required based on the specific trace type
// To avoid needing to specify unused options, these aren't included in the params for NewConfig
cfg.AlphabetTrace = alphabetTrace
require.NoError(t, cfg.Check()) require.NoError(t, cfg.Check())
} }
func TestL1ETHRPCAddress(t *testing.T) { func TestL1ETHRPCAddress(t *testing.T) {
t.Run("Required", func(t *testing.T) { t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag l1-eth-rpc is required", addRequiredArgsExcept("--l1-eth-rpc")) verifyArgsInvalid(t, "flag l1-eth-rpc is required", addRequiredArgsExcept(config.TraceTypeAlphabet, "--l1-eth-rpc"))
}) })
t.Run("Valid", func(t *testing.T) { t.Run("Valid", func(t *testing.T) {
url := "http://example.com:8888" url := "http://example.com:8888"
cfg := configForArgs(t, addRequiredArgsExcept("--l1-eth-rpc", "--l1-eth-rpc="+url)) cfg := configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--l1-eth-rpc", "--l1-eth-rpc="+url))
require.Equal(t, url, cfg.L1EthRpc) require.Equal(t, url, cfg.L1EthRpc)
require.Equal(t, url, cfg.TxMgrConfig.L1RPCURL) require.Equal(t, url, cfg.TxMgrConfig.L1RPCURL)
}) })
...@@ -62,74 +70,134 @@ func TestL1ETHRPCAddress(t *testing.T) { ...@@ -62,74 +70,134 @@ func TestL1ETHRPCAddress(t *testing.T) {
func TestTraceType(t *testing.T) { func TestTraceType(t *testing.T) {
t.Run("Required", func(t *testing.T) { t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag trace-type is required", addRequiredArgsExcept("--trace-type")) verifyArgsInvalid(t, "flag trace-type is required", addRequiredArgsExcept("", "--trace-type"))
}) })
for _, traceType := range config.TraceTypes { for _, traceType := range config.TraceTypes {
traceType := traceType traceType := traceType
t.Run("Valid_"+traceType.String(), func(t *testing.T) { t.Run("Valid_"+traceType.String(), func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgsExcept("--trace-type", "--trace-type", traceType.String())) cfg := configForArgs(t, addRequiredArgs(traceType))
require.Equal(t, traceType, cfg.TraceType) require.Equal(t, traceType, cfg.TraceType)
}) })
} }
t.Run("Invalid", func(t *testing.T) { t.Run("Invalid", func(t *testing.T) {
verifyArgsInvalid(t, "unknown trace type: \"foo\"", addRequiredArgsExcept("--trace-type", "--trace-type=foo")) verifyArgsInvalid(t, "unknown trace type: \"foo\"", addRequiredArgsExcept(config.TraceTypeAlphabet, "--trace-type", "--trace-type=foo"))
}) })
} }
func TestGameAddress(t *testing.T) { func TestGameAddress(t *testing.T) {
t.Run("Required", func(t *testing.T) { t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag game-address is required", addRequiredArgsExcept("--game-address")) verifyArgsInvalid(t, "flag game-address is required", addRequiredArgsExcept(config.TraceTypeAlphabet, "--game-address"))
}) })
t.Run("Valid", func(t *testing.T) { t.Run("Valid", func(t *testing.T) {
addr := common.Address{0xbb, 0xcc, 0xdd} addr := common.Address{0xbb, 0xcc, 0xdd}
cfg := configForArgs(t, addRequiredArgsExcept("--game-address", "--game-address="+addr.Hex())) cfg := configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--game-address", "--game-address="+addr.Hex()))
require.Equal(t, addr, cfg.GameAddress) require.Equal(t, addr, cfg.GameAddress)
}) })
t.Run("Invalid", func(t *testing.T) { t.Run("Invalid", func(t *testing.T) {
verifyArgsInvalid(t, "invalid address: foo", addRequiredArgsExcept("--game-address", "--game-address=foo")) verifyArgsInvalid(t, "invalid address: foo", addRequiredArgsExcept(config.TraceTypeAlphabet, "--game-address", "--game-address=foo"))
}) })
} }
func TestTxManagerFlagsSupported(t *testing.T) { func TestTxManagerFlagsSupported(t *testing.T) {
// Not a comprehensive list of flags, just enough to sanity check the txmgr.CLIFlags were defined // Not a comprehensive list of flags, just enough to sanity check the txmgr.CLIFlags were defined
cfg := configForArgs(t, addRequiredArgs("--"+txmgr.NumConfirmationsFlagName, "7")) cfg := configForArgs(t, addRequiredArgs(config.TraceTypeAlphabet, "--"+txmgr.NumConfirmationsFlagName, "7"))
require.Equal(t, uint64(7), cfg.TxMgrConfig.NumConfirmations) require.Equal(t, uint64(7), cfg.TxMgrConfig.NumConfirmations)
} }
func TestAgreeWithProposedOutput(t *testing.T) { func TestAgreeWithProposedOutput(t *testing.T) {
t.Run("MustBeProvided", func(t *testing.T) { t.Run("MustBeProvided", func(t *testing.T) {
verifyArgsInvalid(t, "flag agree-with-proposed-output is required", addRequiredArgsExcept("--agree-with-proposed-output")) verifyArgsInvalid(t, "flag agree-with-proposed-output is required", addRequiredArgsExcept(config.TraceTypeAlphabet, "--agree-with-proposed-output"))
}) })
t.Run("Enabled", func(t *testing.T) { t.Run("Enabled", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs("--agree-with-proposed-output")) cfg := configForArgs(t, addRequiredArgs(config.TraceTypeAlphabet, "--agree-with-proposed-output"))
require.True(t, cfg.AgreeWithProposedOutput) require.True(t, cfg.AgreeWithProposedOutput)
}) })
t.Run("EnabledWithArg", func(t *testing.T) { t.Run("EnabledWithArg", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs("--agree-with-proposed-output=true")) cfg := configForArgs(t, addRequiredArgs(config.TraceTypeAlphabet, "--agree-with-proposed-output=true"))
require.True(t, cfg.AgreeWithProposedOutput) require.True(t, cfg.AgreeWithProposedOutput)
}) })
t.Run("Disabled", func(t *testing.T) { t.Run("Disabled", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs("--agree-with-proposed-output=false")) cfg := configForArgs(t, addRequiredArgs(config.TraceTypeAlphabet, "--agree-with-proposed-output=false"))
require.False(t, cfg.AgreeWithProposedOutput) require.False(t, cfg.AgreeWithProposedOutput)
}) })
} }
func TestGameDepth(t *testing.T) { func TestGameDepth(t *testing.T) {
t.Run("Required", func(t *testing.T) { t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag game-depth is required", addRequiredArgsExcept("--game-depth")) verifyArgsInvalid(t, "flag game-depth is required", addRequiredArgsExcept(config.TraceTypeAlphabet, "--game-depth"))
}) })
t.Run("Valid", func(t *testing.T) { t.Run("Valid", func(t *testing.T) {
value := "4" value := "4"
cfg := configForArgs(t, addRequiredArgsExcept("--game-depth", "--game-depth="+value)) cfg := configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--game-depth", "--game-depth="+value))
require.Equal(t, value, fmt.Sprint(cfg.GameDepth)) require.Equal(t, value, fmt.Sprint(cfg.GameDepth))
}) })
} }
func TestCannonBin(t *testing.T) {
t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--cannon-bin"))
})
t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag cannon-bin is required", addRequiredArgsExcept(config.TraceTypeCannon, "--cannon-bin"))
})
t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgsExcept(config.TraceTypeCannon, "--cannon-bin", "--cannon-bin=./cannon"))
require.Equal(t, "./cannon", cfg.CannonBin)
})
}
func TestCannonAbsolutePrestate(t *testing.T) {
t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--cannon-prestate"))
})
t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag cannon-prestate is required", addRequiredArgsExcept(config.TraceTypeCannon, "--cannon-prestate"))
})
t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgsExcept(config.TraceTypeCannon, "--cannon-prestate", "--cannon-prestate=./pre.json"))
require.Equal(t, "./pre.json", cfg.CannonAbsolutePreState)
})
}
func TestCannonDataDir(t *testing.T) {
t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--cannon-datadir"))
})
t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag cannon-datadir is required", addRequiredArgsExcept(config.TraceTypeCannon, "--cannon-datadir"))
})
t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgsExcept(config.TraceTypeCannon, "--cannon-datadir", "--cannon-datadir=/foo/bar/cannon"))
require.Equal(t, "/foo/bar/cannon", cfg.CannonDatadir)
})
}
func TestCannonL2(t *testing.T) {
t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--cannon-l2"))
})
t.Run("RequiredForCannonTrace", func(t *testing.T) {
verifyArgsInvalid(t, "flag cannon-l2 is required", addRequiredArgsExcept(config.TraceTypeCannon, "--cannon-l2"))
})
t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs(config.TraceTypeCannon))
require.Equal(t, cannonL2, cfg.CannonL2)
})
}
func verifyArgsInvalid(t *testing.T, messageContains string, cliArgs []string) { func verifyArgsInvalid(t *testing.T, messageContains string, cliArgs []string) {
_, _, err := runWithArgs(cliArgs) _, _, err := runWithArgs(cliArgs)
require.ErrorContains(t, err, messageContains) require.ErrorContains(t, err, messageContains)
...@@ -153,28 +221,36 @@ func runWithArgs(cliArgs []string) (log.Logger, config.Config, error) { ...@@ -153,28 +221,36 @@ func runWithArgs(cliArgs []string) (log.Logger, config.Config, error) {
return logger, *cfg, err return logger, *cfg, err
} }
func addRequiredArgs(args ...string) []string { func addRequiredArgs(traceType config.TraceType, args ...string) []string {
req := requiredArgs() req := requiredArgs(traceType)
combined := toArgList(req) combined := toArgList(req)
return append(combined, args...) return append(combined, args...)
} }
func addRequiredArgsExcept(name string, optionalArgs ...string) []string { func addRequiredArgsExcept(traceType config.TraceType, name string, optionalArgs ...string) []string {
req := requiredArgs() req := requiredArgs(traceType)
delete(req, name) delete(req, name)
return append(toArgList(req), optionalArgs...) return append(toArgList(req), optionalArgs...)
} }
func requiredArgs() map[string]string { func requiredArgs(traceType config.TraceType) map[string]string {
return map[string]string{ args := map[string]string{
"--game-depth": gameDepth, "--game-depth": gameDepth,
"--agree-with-proposed-output": agreeWithProposedOutput, "--agree-with-proposed-output": agreeWithProposedOutput,
"--l1-eth-rpc": l1EthRpc, "--l1-eth-rpc": l1EthRpc,
"--game-address": gameAddressValue, "--game-address": gameAddressValue,
"--trace-type": "alphabet", "--trace-type": traceType.String(),
"--alphabet": alphabetTrace, }
"--cannon-datadir": cannonDatadir, switch traceType {
case config.TraceTypeAlphabet:
args["--alphabet"] = alphabetTrace
case config.TraceTypeCannon:
args["--cannon-bin"] = cannonBin
args["--cannon-prestate"] = cannonPreState
args["--cannon-datadir"] = cannonDatadir
args["--cannon-l2"] = cannonL2
} }
return args
} }
func toArgList(req map[string]string) []string { func toArgList(req map[string]string) []string {
......
...@@ -11,6 +11,9 @@ import ( ...@@ -11,6 +11,9 @@ import (
var ( var (
ErrMissingTraceType = errors.New("missing trace type") ErrMissingTraceType = errors.New("missing trace type")
ErrMissingCannonDatadir = errors.New("missing cannon datadir") ErrMissingCannonDatadir = errors.New("missing cannon datadir")
ErrMissingCannonL2 = errors.New("missing cannon L2")
ErrMissingCannonBin = errors.New("missing cannon bin")
ErrMissingCannonAbsolutePreState = errors.New("missing cannon absolute pre-state")
ErrMissingAlphabetTrace = errors.New("missing alphabet trace") ErrMissingAlphabetTrace = errors.New("missing alphabet trace")
ErrMissingL1EthRPC = errors.New("missing l1 eth rpc url") ErrMissingL1EthRPC = errors.New("missing l1 eth rpc url")
ErrMissingGameAddress = errors.New("missing game address") ErrMissingGameAddress = errors.New("missing game address")
...@@ -57,8 +60,15 @@ type Config struct { ...@@ -57,8 +60,15 @@ type Config struct {
GameDepth int // Depth of the game tree GameDepth int // Depth of the game tree
TraceType TraceType // Type of trace TraceType TraceType // Type of trace
// Specific to the alphabet trace provider
AlphabetTrace string // String for the AlphabetTraceProvider AlphabetTrace string // String for the AlphabetTraceProvider
CannonDatadir string // Cannon Data Directory for the CannonTraceProvider
// Specific to the cannon trace provider
CannonBin string // Path to the cannon executable to run when generating trace data
CannonAbsolutePreState string // File to load the absolute pre-state for Cannon traces from
CannonDatadir string // Cannon Data Directory
CannonL2 string // L2 RPC Url
TxMgrConfig txmgr.CLIConfig TxMgrConfig txmgr.CLIConfig
} }
...@@ -67,8 +77,6 @@ func NewConfig( ...@@ -67,8 +77,6 @@ func NewConfig(
l1EthRpc string, l1EthRpc string,
gameAddress common.Address, gameAddress common.Address,
traceType TraceType, traceType TraceType,
alphabetTrace string,
cannonDatadir string,
agreeWithProposedOutput bool, agreeWithProposedOutput bool,
gameDepth int, gameDepth int,
) Config { ) Config {
...@@ -80,8 +88,6 @@ func NewConfig( ...@@ -80,8 +88,6 @@ func NewConfig(
GameDepth: gameDepth, GameDepth: gameDepth,
TraceType: traceType, TraceType: traceType,
AlphabetTrace: alphabetTrace,
CannonDatadir: cannonDatadir,
TxMgrConfig: txmgr.NewCLIConfig(l1EthRpc), TxMgrConfig: txmgr.NewCLIConfig(l1EthRpc),
} }
...@@ -97,9 +103,20 @@ func (c Config) Check() error { ...@@ -97,9 +103,20 @@ func (c Config) Check() error {
if c.TraceType == "" { if c.TraceType == "" {
return ErrMissingTraceType return ErrMissingTraceType
} }
if c.TraceType == TraceTypeCannon && c.CannonDatadir == "" { if c.TraceType == TraceTypeCannon {
if c.CannonBin == "" {
return ErrMissingCannonBin
}
if c.CannonAbsolutePreState == "" {
return ErrMissingCannonAbsolutePreState
}
if c.CannonDatadir == "" {
return ErrMissingCannonDatadir return ErrMissingCannonDatadir
} }
if c.CannonL2 == "" {
return ErrMissingCannonL2
}
}
if c.TraceType == TraceTypeAlphabet && c.AlphabetTrace == "" { if c.TraceType == TraceTypeAlphabet && c.AlphabetTrace == "" {
return ErrMissingAlphabetTrace return ErrMissingAlphabetTrace
} }
......
...@@ -12,20 +12,37 @@ var ( ...@@ -12,20 +12,37 @@ var (
validL1EthRpc = "http://localhost:8545" validL1EthRpc = "http://localhost:8545"
validGameAddress = common.HexToAddress("0x7bdd3b028C4796eF0EAf07d11394d0d9d8c24139") validGameAddress = common.HexToAddress("0x7bdd3b028C4796eF0EAf07d11394d0d9d8c24139")
validAlphabetTrace = "abcdefgh" validAlphabetTrace = "abcdefgh"
validCannonBin = "./bin/cannon"
validCannonAbsolutPreState = "pre.json"
validCannonDatadir = "/tmp/cannon" validCannonDatadir = "/tmp/cannon"
validCannonL2 = "http://localhost:9545"
agreeWithProposedOutput = true agreeWithProposedOutput = true
gameDepth = 4 gameDepth = 4
) )
func validConfig(traceType TraceType) Config { func validConfig(traceType TraceType) Config {
cfg := NewConfig(validL1EthRpc, validGameAddress, traceType, validAlphabetTrace, validCannonDatadir, agreeWithProposedOutput, gameDepth) cfg := NewConfig(validL1EthRpc, validGameAddress, traceType, agreeWithProposedOutput, gameDepth)
switch traceType {
case TraceTypeAlphabet:
cfg.AlphabetTrace = validAlphabetTrace
case TraceTypeCannon:
cfg.CannonBin = validCannonBin
cfg.CannonAbsolutePreState = validCannonAbsolutPreState
cfg.CannonDatadir = validCannonDatadir
cfg.CannonL2 = validCannonL2
}
return cfg return cfg
} }
// TestValidConfigIsValid checks that the config provided by validConfig is actually valid // TestValidConfigIsValid checks that the config provided by validConfig is actually valid
func TestValidConfigIsValid(t *testing.T) { func TestValidConfigIsValid(t *testing.T) {
err := validConfig(TraceTypeCannon).Check() for _, traceType := range TraceTypes {
traceType := traceType
t.Run(traceType.String(), func(t *testing.T) {
err := validConfig(traceType).Check()
require.NoError(t, err) require.NoError(t, err)
})
}
} }
func TestTxMgrConfig(t *testing.T) { func TestTxMgrConfig(t *testing.T) {
...@@ -40,30 +57,40 @@ func TestL1EthRpcRequired(t *testing.T) { ...@@ -40,30 +57,40 @@ func TestL1EthRpcRequired(t *testing.T) {
config := validConfig(TraceTypeCannon) config := validConfig(TraceTypeCannon)
config.L1EthRpc = "" config.L1EthRpc = ""
require.ErrorIs(t, config.Check(), ErrMissingL1EthRPC) require.ErrorIs(t, config.Check(), ErrMissingL1EthRPC)
config.L1EthRpc = validL1EthRpc
require.NoError(t, config.Check())
} }
func TestGameAddressRequired(t *testing.T) { func TestGameAddressRequired(t *testing.T) {
config := validConfig(TraceTypeCannon) config := validConfig(TraceTypeCannon)
config.GameAddress = common.Address{} config.GameAddress = common.Address{}
require.ErrorIs(t, config.Check(), ErrMissingGameAddress) require.ErrorIs(t, config.Check(), ErrMissingGameAddress)
config.GameAddress = validGameAddress
require.NoError(t, config.Check())
} }
func TestAlphabetTraceRequired(t *testing.T) { func TestAlphabetTraceRequired(t *testing.T) {
config := validConfig(TraceTypeAlphabet) config := validConfig(TraceTypeAlphabet)
config.AlphabetTrace = "" config.AlphabetTrace = ""
require.ErrorIs(t, config.Check(), ErrMissingAlphabetTrace) require.ErrorIs(t, config.Check(), ErrMissingAlphabetTrace)
config.AlphabetTrace = validAlphabetTrace
require.NoError(t, config.Check())
} }
func TestCannonTraceRequired(t *testing.T) { func TestCannonBinRequired(t *testing.T) {
config := validConfig(TraceTypeCannon)
config.CannonBin = ""
require.ErrorIs(t, config.Check(), ErrMissingCannonBin)
}
func TestCannonAbsolutePreStateRequired(t *testing.T) {
config := validConfig(TraceTypeCannon)
config.CannonAbsolutePreState = ""
require.ErrorIs(t, config.Check(), ErrMissingCannonAbsolutePreState)
}
func TestCannonDatadirRequired(t *testing.T) {
config := validConfig(TraceTypeCannon) config := validConfig(TraceTypeCannon)
config.CannonDatadir = "" config.CannonDatadir = ""
require.ErrorIs(t, config.Check(), ErrMissingCannonDatadir) require.ErrorIs(t, config.Check(), ErrMissingCannonDatadir)
config.CannonDatadir = validCannonDatadir }
require.NoError(t, config.Check())
func TestCannonL2Required(t *testing.T) {
config := validConfig(TraceTypeCannon)
config.CannonL2 = ""
require.ErrorIs(t, config.Check(), ErrMissingCannonL2)
} }
...@@ -55,14 +55,29 @@ var ( ...@@ -55,14 +55,29 @@ var (
// Optional Flags // Optional Flags
AlphabetFlag = &cli.StringFlag{ AlphabetFlag = &cli.StringFlag{
Name: "alphabet", Name: "alphabet",
Usage: "Alphabet Trace (temporary)", Usage: "Correct Alphabet Trace (alphabet trace type only)",
EnvVars: prefixEnvVars("ALPHABET"), EnvVars: prefixEnvVars("ALPHABET"),
} }
CannonBinFlag = &cli.StringFlag{
Name: "cannon-bin",
Usage: "Path to cannon executable to use when generating trace data (cannon trace type only)",
EnvVars: prefixEnvVars("CANNON_BIN"),
}
CannonPreStateFlag = &cli.StringFlag{
Name: "cannon-prestate",
Usage: "Path to absolute prestate to use when generating trace data (cannon trace type only)",
EnvVars: prefixEnvVars("CANNON_PRESTATE"),
}
CannonDatadirFlag = &cli.StringFlag{ CannonDatadirFlag = &cli.StringFlag{
Name: "cannon-datadir", Name: "cannon-datadir",
Usage: "Cannon Data Directory", Usage: "Directory to store data generated by cannon (cannon trace type only)",
EnvVars: prefixEnvVars("CANNON_DATADIR"), EnvVars: prefixEnvVars("CANNON_DATADIR"),
} }
CannonL2Flag = &cli.StringFlag{
Name: "cannon-l2",
Usage: "L2 Address of L2 JSON-RPC endpoint to use (eth and debug namespace required) (cannon trace type only)",
EnvVars: prefixEnvVars("CANNON_L2"),
}
) )
// requiredFlags are checked by [CheckRequired] // requiredFlags are checked by [CheckRequired]
...@@ -77,7 +92,10 @@ var requiredFlags = []cli.Flag{ ...@@ -77,7 +92,10 @@ var requiredFlags = []cli.Flag{
// optionalFlags is a list of unchecked cli flags // optionalFlags is a list of unchecked cli flags
var optionalFlags = []cli.Flag{ var optionalFlags = []cli.Flag{
AlphabetFlag, AlphabetFlag,
CannonBinFlag,
CannonPreStateFlag,
CannonDatadirFlag, CannonDatadirFlag,
CannonL2Flag,
} }
func init() { func init() {
...@@ -99,8 +117,17 @@ func CheckRequired(ctx *cli.Context) error { ...@@ -99,8 +117,17 @@ func CheckRequired(ctx *cli.Context) error {
gameType := config.TraceType(strings.ToLower(ctx.String(TraceTypeFlag.Name))) gameType := config.TraceType(strings.ToLower(ctx.String(TraceTypeFlag.Name)))
switch gameType { switch gameType {
case config.TraceTypeCannon: case config.TraceTypeCannon:
if !ctx.IsSet(CannonBinFlag.Name) {
return fmt.Errorf("flag %s is required", CannonBinFlag.Name)
}
if !ctx.IsSet(CannonPreStateFlag.Name) {
return fmt.Errorf("flag %s is required", CannonPreStateFlag.Name)
}
if !ctx.IsSet(CannonDatadirFlag.Name) { if !ctx.IsSet(CannonDatadirFlag.Name) {
return fmt.Errorf("flag %s is required", "cannon-datadir") return fmt.Errorf("flag %s is required", CannonDatadirFlag.Name)
}
if !ctx.IsSet(CannonL2Flag.Name) {
return fmt.Errorf("flag %s is required", CannonL2Flag.Name)
} }
case config.TraceTypeAlphabet: case config.TraceTypeAlphabet:
if !ctx.IsSet(AlphabetFlag.Name) { if !ctx.IsSet(AlphabetFlag.Name) {
...@@ -132,7 +159,10 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) { ...@@ -132,7 +159,10 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
TraceType: traceTypeFlag, TraceType: traceTypeFlag,
GameAddress: dgfAddress, GameAddress: dgfAddress,
AlphabetTrace: ctx.String(AlphabetFlag.Name), AlphabetTrace: ctx.String(AlphabetFlag.Name),
CannonBin: ctx.String(CannonBinFlag.Name),
CannonAbsolutePreState: ctx.String(CannonPreStateFlag.Name),
CannonDatadir: ctx.String(CannonDatadirFlag.Name), CannonDatadir: ctx.String(CannonDatadirFlag.Name),
CannonL2: ctx.String(CannonL2Flag.Name),
AgreeWithProposedOutput: ctx.Bool(AgreeWithProposedOutputFlag.Name), AgreeWithProposedOutput: ctx.Bool(AgreeWithProposedOutputFlag.Name),
GameDepth: ctx.Int(GameDepthFlag.Name), GameDepth: ctx.Int(GameDepthFlag.Name),
TxMgrConfig: txMgrConfig, TxMgrConfig: txMgrConfig,
......
...@@ -109,7 +109,7 @@ func (s *L1Miner) ActL1IncludeTx(from common.Address) Action { ...@@ -109,7 +109,7 @@ func (s *L1Miner) ActL1IncludeTx(from common.Address) Action {
func (s *L1Miner) IncludeTx(t Testing, tx *types.Transaction) { func (s *L1Miner) IncludeTx(t Testing, tx *types.Transaction) {
from, err := s.l1Signer.Sender(tx) from, err := s.l1Signer.Sender(tx)
require.NoError(t, err) require.NoError(t, err)
s.log.Info("including tx", "nonce", tx.Nonce(), "from", from) s.log.Info("including tx", "nonce", tx.Nonce(), "from", from, "to", tx.To())
if tx.Gas() > s.l1BuildingHeader.GasLimit { if tx.Gas() > s.l1BuildingHeader.GasLimit {
t.Fatalf("tx consumes %d gas, more than available in L1 block %d", tx.Gas(), s.l1BuildingHeader.GasLimit) t.Fatalf("tx consumes %d gas, more than available in L1 block %d", tx.Gas(), s.l1BuildingHeader.GasLimit)
} }
......
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"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/core/types" "github.com/ethereum/go-ethereum/core/types"
...@@ -16,6 +17,7 @@ import ( ...@@ -16,6 +17,7 @@ import (
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-node/sources" "github.com/ethereum-optimism/optimism/op-node/sources"
"github.com/ethereum-optimism/optimism/op-proposer/metrics" "github.com/ethereum-optimism/optimism/op-proposer/metrics"
"github.com/ethereum-optimism/optimism/op-proposer/proposer" "github.com/ethereum-optimism/optimism/op-proposer/proposer"
...@@ -32,6 +34,7 @@ type L2Proposer struct { ...@@ -32,6 +34,7 @@ type L2Proposer struct {
log log.Logger log log.Logger
l1 *ethclient.Client l1 *ethclient.Client
driver *proposer.L2OutputSubmitter driver *proposer.L2OutputSubmitter
contract *bindings.L2OutputOracleCaller
address common.Address address common.Address
privKey *ecdsa.PrivateKey privKey *ecdsa.PrivateKey
contractAddr common.Address contractAddr common.Address
...@@ -56,7 +59,6 @@ func (f fakeTxMgr) Send(_ context.Context, _ txmgr.TxCandidate) (*types.Receipt, ...@@ -56,7 +59,6 @@ func (f fakeTxMgr) Send(_ context.Context, _ txmgr.TxCandidate) (*types.Receipt,
} }
func NewL2Proposer(t Testing, log log.Logger, cfg *ProposerCfg, l1 *ethclient.Client, rollupCl *sources.RollupClient) *L2Proposer { func NewL2Proposer(t Testing, log log.Logger, cfg *ProposerCfg, l1 *ethclient.Client, rollupCl *sources.RollupClient) *L2Proposer {
proposerCfg := proposer.Config{ proposerCfg := proposer.Config{
L2OutputOracleAddr: cfg.OutputOracleAddr, L2OutputOracleAddr: cfg.OutputOracleAddr,
PollInterval: time.Second, PollInterval: time.Second,
...@@ -70,12 +72,20 @@ func NewL2Proposer(t Testing, log log.Logger, cfg *ProposerCfg, l1 *ethclient.Cl ...@@ -70,12 +72,20 @@ func NewL2Proposer(t Testing, log log.Logger, cfg *ProposerCfg, l1 *ethclient.Cl
dr, err := proposer.NewL2OutputSubmitter(proposerCfg, log, metrics.NoopMetrics) dr, err := proposer.NewL2OutputSubmitter(proposerCfg, log, metrics.NoopMetrics)
require.NoError(t, err) require.NoError(t, err)
contract, err := bindings.NewL2OutputOracleCaller(cfg.OutputOracleAddr, l1)
require.NoError(t, err)
address := crypto.PubkeyToAddress(cfg.ProposerKey.PublicKey)
proposer, err := contract.PROPOSER(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, proposer, address, "PROPOSER must be the proposer's address")
return &L2Proposer{ return &L2Proposer{
log: log, log: log,
l1: l1, l1: l1,
driver: dr, driver: dr,
address: crypto.PubkeyToAddress(cfg.ProposerKey.PublicKey), contract: contract,
address: address,
privKey: cfg.ProposerKey, privKey: cfg.ProposerKey,
contractAddr: cfg.OutputOracleAddr, contractAddr: cfg.OutputOracleAddr,
} }
......
...@@ -231,6 +231,11 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) * ...@@ -231,6 +231,11 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) *
SystemConfigProxy: predeploys.DevSystemConfigAddr, SystemConfigProxy: predeploys.DevSystemConfigAddr,
} }
// Sanity check that the config is correct
require.Equal(t, deployParams.Secrets.Addresses().Batcher, deployParams.DeployConfig.BatchSenderAddress)
require.Equal(t, deployParams.Secrets.Addresses().SequencerP2P, deployParams.DeployConfig.P2PSequencerAddress)
require.Equal(t, deployParams.Secrets.Addresses().Proposer, deployParams.DeployConfig.L2OutputOracleProposer)
return &SetupData{ return &SetupData{
L1Cfg: l1Genesis, L1Cfg: l1Genesis,
L2Cfg: l2Genesis, L2Cfg: l2Genesis,
......
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