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

Merge branch 'develop' into feat/storage-layout-getter

parents 5adbe662 64649169
......@@ -861,6 +861,18 @@ jobs:
command: make fuzz
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:
docker:
- image: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:latest
......@@ -1415,6 +1427,7 @@ workflows:
- go-mod-tidy
- fuzz-op-node
- fuzz-op-chain-ops
- fuzz-cannon
- bedrock-markdown
- go-lint:
name: op-batcher-lint
......
......@@ -23,8 +23,20 @@ test: elf
lint:
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: \
cannon \
clean \
test \
lint
lint \
fuzz
......@@ -13,6 +13,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"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/crypto"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
......@@ -21,7 +22,7 @@ import (
"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()
require.NoError(t, err)
......@@ -48,6 +49,53 @@ func MarkdownTracer() vm.EVMLogger {
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) {
testFiles, err := os.ReadDir("open_mips_tests/test/bin")
require.NoError(t, err)
......@@ -55,7 +103,6 @@ func TestEVM(t *testing.T) {
contracts, addrs := testContractsSetup(t)
var tracer vm.EVMLogger // no-tracer by default, but see SourceMapTracer and MarkdownTracer
//tracer = SourceMapTracer(t, contracts, addrs)
sender := common.Address{0x13, 0x37}
for _, f := range testFiles {
t.Run(f.Name(), func(t *testing.T) {
......@@ -66,8 +113,8 @@ func TestEVM(t *testing.T) {
// Short-circuit early for exit_group.bin
exitGroup := f.Name() == "exit_group.bin"
env, evmState := NewEVMEnv(contracts, addrs)
env.Config.Tracer = tracer
evm := NewMIPSEVM(contracts, addrs)
evm.SetTracer(tracer)
fn := path.Join("open_mips_tests/test/bin", f.Name())
programMem, err := os.ReadFile(fn)
......@@ -79,58 +126,31 @@ func TestEVM(t *testing.T) {
// set the return address ($ra) to jump into when test completes
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++ {
if us.state.PC == endAddr {
if goState.state.PC == endAddr {
break
}
if exitGroup && us.state.Exited {
if exitGroup && goState.state.Exited {
break
}
insn := state.Memory.GetMemory(state.PC)
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)
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
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)
evmPost := evm.Step(t, stepWitness)
// verify the post-state matches.
// TODO: maybe more readable to decode the evmPost state, and do attribute-wise comparison.
uniPost := us.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(uniPost).String(), hexutil.Bytes(evmPost).String(),
goPost := goState.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
}
if exitGroup {
require.NotEqual(t, uint32(endAddr), us.state.PC, "must not reach end")
require.True(t, us.state.Exited, "must set exited state")
require.Equal(t, uint8(1), us.state.ExitCode, "must exit with 1")
require.NotEqual(t, uint32(endAddr), goState.state.PC, "must not reach end")
require.True(t, goState.state.Exited, "must set exited state")
require.Equal(t, uint8(1), goState.state.ExitCode, "must exit with 1")
} else {
require.Equal(t, uint32(endAddr), state.PC, "must reach end")
// inspect test result
......@@ -160,8 +180,8 @@ func TestEVMFault(t *testing.T) {
// set the return address ($ra) to jump into when test completes
state.Registers[31] = endAddr
us := NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
require.Panics(t, func() { _, _ = us.Step(true) }, "must panic on illegal instruction")
goState := NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
require.Panics(t, func() { _, _ = goState.Step(true) }, "must panic on illegal instruction")
insnProof := initialState.Memory.MerkleProof(0)
stepWitness := &StepWitness{
......@@ -181,7 +201,6 @@ func TestHelloEVM(t *testing.T) {
contracts, addrs := testContractsSetup(t)
var tracer vm.EVMLogger // no-tracer by default, but see SourceMapTracer and MarkdownTracer
//tracer = SourceMapTracer(t, contracts, addrs)
sender := common.Address{0x13, 0x37}
elfProgram, err := elf.Open("../example/bin/hello.elf")
require.NoError(t, err, "open ELF file")
......@@ -194,14 +213,11 @@ func TestHelloEVM(t *testing.T) {
require.NoError(t, PatchStack(state), "add initial stack")
var stdOutBuf, stdErrBuf bytes.Buffer
us := NewInstrumentedState(state, nil, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr))
env, evmState := NewEVMEnv(contracts, addrs)
env.Config.Tracer = tracer
goState := NewInstrumentedState(state, nil, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr))
start := time.Now()
for i := 0; i < 400_000; i++ {
if us.state.Exited {
if goState.state.Exited {
break
}
insn := state.Memory.GetMemory(state.PC)
......@@ -209,30 +225,16 @@ func TestHelloEVM(t *testing.T) {
t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.PC, insn)
}
stepWitness, err := us.Step(true)
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()
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)
evm := NewMIPSEVM(contracts, addrs)
evm.SetTracer(tracer)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
evmPost := evm.Step(t, stepWitness)
// verify the post-state matches.
// TODO: maybe more readable to decode the evmPost state, and do attribute-wise comparison.
uniPost := us.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(uniPost).String(), hexutil.Bytes(evmPost).String(),
goPost := goState.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
}
end := time.Now()
......@@ -264,13 +266,10 @@ func TestClaimEVM(t *testing.T) {
oracle, expectedStdOut, expectedStdErr := claimTestOracle(t)
var stdOutBuf, stdErrBuf bytes.Buffer
us := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr))
env, evmState := NewEVMEnv(contracts, addrs)
env.Config.Tracer = tracer
goState := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr))
for i := 0; i < 2000_000; i++ {
if us.state.Exited {
if goState.state.Exited {
break
}
......@@ -279,32 +278,16 @@ func TestClaimEVM(t *testing.T) {
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)
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
if stepWitness.HasPreimage() {
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)
}
evm := NewMIPSEVM(contracts, addrs)
evm.SetTracer(tracer)
evmPost := evm.Step(t, stepWitness)
ret, leftOverGas, err := env.Call(vm.AccountRef(addrs.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)
goPost := goState.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
}
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 (
"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) {
preimage := m.lastPreimage
if key != m.lastPreimageKey {
......@@ -43,7 +53,7 @@ func (m *InstrumentedState) handleSyscall() error {
//fmt.Printf("syscall: %d\n", syscallNum)
switch syscallNum {
case 4090: // mmap
case sysMmap:
sz := a1
if sz&PageAddrMask != 0 { // adjust size to align with page size
sz += PageSize - (sz & PageAddrMask)
......@@ -56,15 +66,15 @@ func (m *InstrumentedState) handleSyscall() error {
v0 = a0
//fmt.Printf("mmap hint 0x%x size 0x%x\n", v0, sz)
}
case 4045: // brk
case sysBrk:
v0 = 0x40000000
case 4120: // clone (not supported)
case sysClone: // clone (not supported)
v0 = 1
case 4246: // exit_group
case sysExitGroup:
m.state.Exited = true
m.state.ExitCode = uint8(a0)
return nil
case 4003: // read
case sysRead:
// args: a0 = fd, a1 = addr, a2 = count
// returns: v0 = read, v1 = err code
switch a0 {
......@@ -98,7 +108,7 @@ func (m *InstrumentedState) handleSyscall() error {
v0 = 0xFFffFFff
v1 = MipsEBADF
}
case 4004: // write
case sysWrite:
// args: a0 = fd, a1 = addr, a2 = count
// returns: v0 = written, v1 = err code
switch a0 {
......@@ -144,7 +154,7 @@ func (m *InstrumentedState) handleSyscall() error {
v0 = 0xFFffFFff
v1 = MipsEBADF
}
case 4055: // fcntl
case sysFcntl:
// args: a0 = fd, a1 = cmd
if a1 == 3 { // F_GETFL: get file descriptor flags
switch a0 {
......
......@@ -15,7 +15,10 @@ import (
var (
l1EthRpc = "http://example.com:8545"
gameAddressValue = "0xaa00000000000000000000000000000000000000"
cannonBin = "./bin/cannon"
cannonPreState = "./pre.json"
cannonDatadir = "./test_data"
cannonL2 = "http://example.com:9545"
alphabetTrace = "abcdefghijz"
agreeWithProposedOutput = "true"
gameDepth = "4"
......@@ -23,13 +26,13 @@ var (
func TestLogLevel(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"} {
lvl := lvl
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.NotNil(t, logger)
})
......@@ -37,24 +40,29 @@ func TestLogLevel(t *testing.T) {
}
func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs())
defaultCfg := config.NewConfig(l1EthRpc, common.HexToAddress(gameAddressValue), config.TraceTypeAlphabet, alphabetTrace, cannonDatadir, true, 4)
cfg := configForArgs(t, addRequiredArgs(config.TraceTypeAlphabet))
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)
}
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())
}
func TestL1ETHRPCAddress(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) {
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.TxMgrConfig.L1RPCURL)
})
......@@ -62,74 +70,134 @@ func TestL1ETHRPCAddress(t *testing.T) {
func TestTraceType(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 {
traceType := traceType
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)
})
}
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) {
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) {
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)
})
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) {
// 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)
}
func TestAgreeWithProposedOutput(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) {
cfg := configForArgs(t, addRequiredArgs("--agree-with-proposed-output"))
cfg := configForArgs(t, addRequiredArgs(config.TraceTypeAlphabet, "--agree-with-proposed-output"))
require.True(t, cfg.AgreeWithProposedOutput)
})
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)
})
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)
})
}
func TestGameDepth(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) {
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))
})
}
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) {
_, _, err := runWithArgs(cliArgs)
require.ErrorContains(t, err, messageContains)
......@@ -153,28 +221,36 @@ func runWithArgs(cliArgs []string) (log.Logger, config.Config, error) {
return logger, *cfg, err
}
func addRequiredArgs(args ...string) []string {
req := requiredArgs()
func addRequiredArgs(traceType config.TraceType, args ...string) []string {
req := requiredArgs(traceType)
combined := toArgList(req)
return append(combined, args...)
}
func addRequiredArgsExcept(name string, optionalArgs ...string) []string {
req := requiredArgs()
func addRequiredArgsExcept(traceType config.TraceType, name string, optionalArgs ...string) []string {
req := requiredArgs(traceType)
delete(req, name)
return append(toArgList(req), optionalArgs...)
}
func requiredArgs() map[string]string {
return map[string]string{
func requiredArgs(traceType config.TraceType) map[string]string {
args := map[string]string{
"--game-depth": gameDepth,
"--agree-with-proposed-output": agreeWithProposedOutput,
"--l1-eth-rpc": l1EthRpc,
"--game-address": gameAddressValue,
"--trace-type": "alphabet",
"--alphabet": alphabetTrace,
"--cannon-datadir": cannonDatadir,
"--trace-type": traceType.String(),
}
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 {
......
......@@ -11,6 +11,9 @@ import (
var (
ErrMissingTraceType = errors.New("missing trace type")
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")
ErrMissingL1EthRPC = errors.New("missing l1 eth rpc url")
ErrMissingGameAddress = errors.New("missing game address")
......@@ -57,8 +60,15 @@ type Config struct {
GameDepth int // Depth of the game tree
TraceType TraceType // Type of trace
// Specific to the alphabet trace provider
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
}
......@@ -67,8 +77,6 @@ func NewConfig(
l1EthRpc string,
gameAddress common.Address,
traceType TraceType,
alphabetTrace string,
cannonDatadir string,
agreeWithProposedOutput bool,
gameDepth int,
) Config {
......@@ -80,8 +88,6 @@ func NewConfig(
GameDepth: gameDepth,
TraceType: traceType,
AlphabetTrace: alphabetTrace,
CannonDatadir: cannonDatadir,
TxMgrConfig: txmgr.NewCLIConfig(l1EthRpc),
}
......@@ -97,9 +103,20 @@ func (c Config) Check() error {
if c.TraceType == "" {
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
}
if c.CannonL2 == "" {
return ErrMissingCannonL2
}
}
if c.TraceType == TraceTypeAlphabet && c.AlphabetTrace == "" {
return ErrMissingAlphabetTrace
}
......
......@@ -12,20 +12,37 @@ var (
validL1EthRpc = "http://localhost:8545"
validGameAddress = common.HexToAddress("0x7bdd3b028C4796eF0EAf07d11394d0d9d8c24139")
validAlphabetTrace = "abcdefgh"
validCannonBin = "./bin/cannon"
validCannonAbsolutPreState = "pre.json"
validCannonDatadir = "/tmp/cannon"
validCannonL2 = "http://localhost:9545"
agreeWithProposedOutput = true
gameDepth = 4
)
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
}
// TestValidConfigIsValid checks that the config provided by validConfig is actually valid
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)
})
}
}
func TestTxMgrConfig(t *testing.T) {
......@@ -40,30 +57,40 @@ func TestL1EthRpcRequired(t *testing.T) {
config := validConfig(TraceTypeCannon)
config.L1EthRpc = ""
require.ErrorIs(t, config.Check(), ErrMissingL1EthRPC)
config.L1EthRpc = validL1EthRpc
require.NoError(t, config.Check())
}
func TestGameAddressRequired(t *testing.T) {
config := validConfig(TraceTypeCannon)
config.GameAddress = common.Address{}
require.ErrorIs(t, config.Check(), ErrMissingGameAddress)
config.GameAddress = validGameAddress
require.NoError(t, config.Check())
}
func TestAlphabetTraceRequired(t *testing.T) {
config := validConfig(TraceTypeAlphabet)
config.AlphabetTrace = ""
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.CannonDatadir = ""
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 (
// Optional Flags
AlphabetFlag = &cli.StringFlag{
Name: "alphabet",
Usage: "Alphabet Trace (temporary)",
Usage: "Correct Alphabet Trace (alphabet trace type only)",
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{
Name: "cannon-datadir",
Usage: "Cannon Data Directory",
Usage: "Directory to store data generated by cannon (cannon trace type only)",
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]
......@@ -77,7 +92,10 @@ var requiredFlags = []cli.Flag{
// optionalFlags is a list of unchecked cli flags
var optionalFlags = []cli.Flag{
AlphabetFlag,
CannonBinFlag,
CannonPreStateFlag,
CannonDatadirFlag,
CannonL2Flag,
}
func init() {
......@@ -99,8 +117,17 @@ func CheckRequired(ctx *cli.Context) error {
gameType := config.TraceType(strings.ToLower(ctx.String(TraceTypeFlag.Name)))
switch gameType {
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) {
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:
if !ctx.IsSet(AlphabetFlag.Name) {
......@@ -132,7 +159,10 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
TraceType: traceTypeFlag,
GameAddress: dgfAddress,
AlphabetTrace: ctx.String(AlphabetFlag.Name),
CannonBin: ctx.String(CannonBinFlag.Name),
CannonAbsolutePreState: ctx.String(CannonPreStateFlag.Name),
CannonDatadir: ctx.String(CannonDatadirFlag.Name),
CannonL2: ctx.String(CannonL2Flag.Name),
AgreeWithProposedOutput: ctx.Bool(AgreeWithProposedOutputFlag.Name),
GameDepth: ctx.Int(GameDepthFlag.Name),
TxMgrConfig: txMgrConfig,
......
......@@ -109,7 +109,7 @@ func (s *L1Miner) ActL1IncludeTx(from common.Address) Action {
func (s *L1Miner) IncludeTx(t Testing, tx *types.Transaction) {
from, err := s.l1Signer.Sender(tx)
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 {
t.Fatalf("tx consumes %d gas, more than available in L1 block %d", tx.Gas(), s.l1BuildingHeader.GasLimit)
}
......
......@@ -7,6 +7,7 @@ import (
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
......@@ -15,6 +16,7 @@ import (
"github.com/ethereum/go-ethereum/params"
"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-proposer/metrics"
"github.com/ethereum-optimism/optimism/op-proposer/proposer"
......@@ -31,6 +33,7 @@ type L2Proposer struct {
log log.Logger
l1 *ethclient.Client
driver *proposer.L2OutputSubmitter
contract *bindings.L2OutputOracleCaller
address common.Address
privKey *ecdsa.PrivateKey
contractAddr common.Address
......@@ -55,7 +58,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 {
proposerCfg := proposer.Config{
L2OutputOracleAddr: cfg.OutputOracleAddr,
PollInterval: time.Second,
......@@ -69,12 +71,20 @@ func NewL2Proposer(t Testing, log log.Logger, cfg *ProposerCfg, l1 *ethclient.Cl
dr, err := proposer.NewL2OutputSubmitter(proposerCfg, log, metrics.NoopMetrics)
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{
log: log,
l1: l1,
driver: dr,
address: crypto.PubkeyToAddress(cfg.ProposerKey.PublicKey),
contract: contract,
address: address,
privKey: cfg.ProposerKey,
contractAddr: cfg.OutputOracleAddr,
}
......
......@@ -231,6 +231,11 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) *
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{
L1Cfg: l1Genesis,
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