Commit e9bc4efb authored by mbaxter's avatar mbaxter Committed by GitHub

cannon: Refactor and add test utils (#11573)

* cannon: Reorganize state mutator testutils

* cannon: Add some testutils to randomize state, standardize validation

* cannon: Attach ticket to TODOs

* cannon: Remove redundant register randomization

* cannon: Clarify testutil import names

* cannon: Clarify comment
parent 6e06fa53
......@@ -187,7 +187,7 @@ func HandleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint3
memTracker.TrackMemAccess(effAddr)
mem := memory.GetMemory(effAddr)
dat, datLen := preimageReader.ReadPreimage(preimageKey, preimageOffset)
//fmt.Printf("reading pre-image data: addr: %08x, offset: %d, datLen: %d, data: %x, key: %s count: %d\n", a1, m.state.PreimageOffset, datLen, dat[:datLen], m.state.PreimageKey, a2)
//fmt.Printf("reading pre-image data: addr: %08x, offset: %d, datLen: %d, data: %x, key: %s count: %d\n", a1, preimageOffset, datLen, dat[:datLen], preimageKey, a2)
alignment := a1 & 3
space := 4 - alignment
if space < datLen {
......
......@@ -42,7 +42,7 @@ type FPVMState interface {
// so a VM can start from any state without fetching prior pre-images,
// and instead just repeat the last hint on setup,
// to make sure pre-image requests can be served.
// The first 4 bytes are a uin32 length prefix.
// The first 4 bytes are a uint32 length prefix.
// Warning: the hint MAY NOT BE COMPLETE. I.e. this is buffered,
// and should only be read when len(LastHint) > 4 && uint32(LastHint[:4]) <= len(LastHint[4:])
GetLastHint() hexutil.Bytes
......
package testutil
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
)
type StateMutatorMultiThreaded struct {
state *multithreaded.State
}
var _ testutil.StateMutator = (*StateMutatorMultiThreaded)(nil)
func NewStateMutatorMultiThreaded(state *multithreaded.State) testutil.StateMutator {
return &StateMutatorMultiThreaded{state: state}
}
func (m *StateMutatorMultiThreaded) SetHI(val uint32) {
m.state.GetCurrentThread().Cpu.HI = val
}
func (m *StateMutatorMultiThreaded) SetLO(val uint32) {
m.state.GetCurrentThread().Cpu.LO = val
}
func (m *StateMutatorMultiThreaded) SetExitCode(val uint8) {
m.state.ExitCode = val
}
func (m *StateMutatorMultiThreaded) SetExited(val bool) {
m.state.Exited = val
}
func (m *StateMutatorMultiThreaded) SetPC(val uint32) {
thread := m.state.GetCurrentThread()
thread.Cpu.PC = val
}
func (m *StateMutatorMultiThreaded) SetHeap(val uint32) {
m.state.Heap = val
}
func (m *StateMutatorMultiThreaded) SetNextPC(val uint32) {
thread := m.state.GetCurrentThread()
thread.Cpu.NextPC = val
}
func (m *StateMutatorMultiThreaded) SetLastHint(val hexutil.Bytes) {
m.state.LastHint = val
}
func (m *StateMutatorMultiThreaded) SetPreimageKey(val common.Hash) {
m.state.PreimageKey = val
}
func (m *StateMutatorMultiThreaded) SetPreimageOffset(val uint32) {
m.state.PreimageOffset = val
}
func (m *StateMutatorMultiThreaded) SetStep(val uint64) {
m.state.Step = val
}
func (m *StateMutatorMultiThreaded) GetRegistersRef() *[32]uint32 {
return m.state.GetRegistersRef()
}
package testutil
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
)
type StateMutatorSingleThreaded struct {
state *singlethreaded.State
}
var _ testutil.StateMutator = (*StateMutatorSingleThreaded)(nil)
func NewStateMutatorSingleThreaded(state *singlethreaded.State) testutil.StateMutator {
return &StateMutatorSingleThreaded{state: state}
}
func (m *StateMutatorSingleThreaded) SetPC(val uint32) {
m.state.Cpu.PC = val
}
func (m *StateMutatorSingleThreaded) SetNextPC(val uint32) {
m.state.Cpu.NextPC = val
}
func (m *StateMutatorSingleThreaded) SetHI(val uint32) {
m.state.Cpu.HI = val
}
func (m *StateMutatorSingleThreaded) SetLO(val uint32) {
m.state.Cpu.LO = val
}
func (m *StateMutatorSingleThreaded) SetHeap(val uint32) {
m.state.Heap = val
}
func (m *StateMutatorSingleThreaded) SetExitCode(val uint8) {
m.state.ExitCode = val
}
func (m *StateMutatorSingleThreaded) SetExited(val bool) {
m.state.Exited = val
}
func (m *StateMutatorSingleThreaded) SetLastHint(val hexutil.Bytes) {
m.state.LastHint = val
}
func (m *StateMutatorSingleThreaded) SetPreimageKey(val common.Hash) {
m.state.PreimageKey = val
}
func (m *StateMutatorSingleThreaded) SetPreimageOffset(val uint32) {
m.state.PreimageOffset = val
}
func (m *StateMutatorSingleThreaded) SetStep(val uint64) {
m.state.Step = val
}
func (m *StateMutatorSingleThreaded) GetRegistersRef() *[32]uint32 {
return m.state.GetRegistersRef()
}
This diff is collapsed.
......@@ -4,7 +4,6 @@ import (
"os"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/require"
......@@ -17,37 +16,27 @@ func FuzzStateSyscallCloneMT(f *testing.F) {
v := GetMultiThreadedTestCase(f)
// t.Skip is causing linting check to fail, disable for now
//nolint:staticcheck
f.Fuzz(func(t *testing.T, pc uint32, step uint64, preimageOffset uint32) {
f.Fuzz(func(t *testing.T, seed int64) {
// TODO(cp-903) Customize test for multi-threaded vm
t.Skip("TODO - customize this test for MTCannon")
pc = pc & 0xFF_FF_FF_FC // align PC
nextPC := pc + 4
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(),
WithPC(pc), WithNextPC(nextPC), WithStep(step), WithPreimageOffset(preimageOffset))
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(seed))
state := goVm.GetState()
state.GetRegistersRef()[2] = exec.SysClone
state.GetMemory().SetMemory(state.GetPC(), syscallInsn)
step := state.GetStep()
state.GetMemory().SetMemory(pc, syscallInsn)
preStateRoot := state.GetMemory().MerkleRoot()
expectedRegisters := testutil.CopyRegisters(state)
expectedRegisters[2] = 0x1
expected := testutil.CreateExpectedState(state)
expected.Step += 1
expected.PC = state.GetCpu().NextPC
expected.NextPC = state.GetCpu().NextPC + 4
expected.Registers[2] = 0x1
expected.Registers[7] = 0
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, pc+4, state.GetCpu().PC)
require.Equal(t, nextPC+4, state.GetCpu().NextPC)
require.Equal(t, uint32(0), state.GetCpu().LO)
require.Equal(t, uint32(0), state.GetCpu().HI)
require.Equal(t, uint32(0), state.GetHeap())
require.Equal(t, uint8(0), state.GetExitCode())
require.Equal(t, false, state.GetExited())
require.Equal(t, preStateRoot, state.GetMemory().MerkleRoot())
require.Equal(t, expectedRegisters, state.GetRegistersRef())
require.Equal(t, step+1, state.GetStep())
require.Equal(t, common.Hash{}, state.GetPreimageKey())
require.Equal(t, preimageOffset, state.GetPreimageOffset())
expected.Validate(t, state)
evm := testutil.NewMIPSEVM(v.Contracts)
evmPost := evm.Step(t, stepWitness, step, v.StateHashFn)
......
......@@ -4,7 +4,6 @@ import (
"os"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/require"
......@@ -14,35 +13,25 @@ import (
func FuzzStateSyscallCloneST(f *testing.F) {
v := GetSingleThreadedTestCase(f)
f.Fuzz(func(t *testing.T, pc uint32, step uint64, preimageOffset uint32) {
pc = pc & 0xFF_FF_FF_FC // align PC
nextPC := pc + 4
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(),
WithPC(pc), WithNextPC(nextPC), WithStep(step), WithPreimageOffset(preimageOffset))
f.Fuzz(func(t *testing.T, seed int64) {
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(seed))
state := goVm.GetState()
state.GetRegistersRef()[2] = exec.SysClone
state.GetMemory().SetMemory(state.GetPC(), syscallInsn)
step := state.GetStep()
state.GetMemory().SetMemory(pc, syscallInsn)
preStateRoot := state.GetMemory().MerkleRoot()
expectedRegisters := testutil.CopyRegisters(state)
expectedRegisters[2] = 0x1
expected := testutil.CreateExpectedState(state)
expected.Step += 1
expected.PC = state.GetCpu().NextPC
expected.NextPC = state.GetCpu().NextPC + 4
expected.Registers[2] = 0x1
expected.Registers[7] = 0
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, pc+4, state.GetCpu().PC)
require.Equal(t, nextPC+4, state.GetCpu().NextPC)
require.Equal(t, uint32(0), state.GetCpu().LO)
require.Equal(t, uint32(0), state.GetCpu().HI)
require.Equal(t, uint32(0), state.GetHeap())
require.Equal(t, uint8(0), state.GetExitCode())
require.Equal(t, false, state.GetExited())
require.Equal(t, preStateRoot, state.GetMemory().MerkleRoot())
require.Equal(t, expectedRegisters, state.GetRegistersRef())
require.Equal(t, step+1, state.GetStep())
require.Equal(t, common.Hash{}, state.GetPreimageKey())
require.Equal(t, preimageOffset, state.GetPreimageOffset())
expected.Validate(t, state)
evm := testutil.NewMIPSEVM(v.Contracts)
evmPost := evm.Step(t, stepWitness, step, v.StateHashFn)
......
......@@ -3,155 +3,31 @@ package tests
import (
"io"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
mttestutil "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded/testutil"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
sttestutil "github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded/testutil"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
)
type StateMutator interface {
SetPC(pc uint32)
SetNextPC(nextPC uint32)
SetHeap(addr uint32)
SetLastHint(lastHint hexutil.Bytes)
SetPreimageKey(key common.Hash)
SetPreimageOffset(offset uint32)
SetStep(step uint64)
}
type singlethreadedMutator struct {
state *singlethreaded.State
}
var _ StateMutator = (*singlethreadedMutator)(nil)
func (m *singlethreadedMutator) SetPC(pc uint32) {
m.state.Cpu.PC = pc
}
func (m *singlethreadedMutator) SetNextPC(nextPC uint32) {
m.state.Cpu.NextPC = nextPC
}
func (m *singlethreadedMutator) SetHeap(addr uint32) {
m.state.Heap = addr
}
func (m *singlethreadedMutator) SetLastHint(lastHint hexutil.Bytes) {
m.state.LastHint = lastHint
}
func (m *singlethreadedMutator) SetPreimageKey(key common.Hash) {
m.state.PreimageKey = key
}
func (m *singlethreadedMutator) SetPreimageOffset(offset uint32) {
m.state.PreimageOffset = offset
}
func (m *singlethreadedMutator) SetStep(step uint64) {
m.state.Step = step
}
type multithreadedMutator struct {
state *multithreaded.State
}
var _ StateMutator = (*multithreadedMutator)(nil)
func (m *multithreadedMutator) SetPC(pc uint32) {
thread := m.state.GetCurrentThread()
thread.Cpu.PC = pc
}
func (m *multithreadedMutator) SetHeap(addr uint32) {
m.state.Heap = addr
}
func (m *multithreadedMutator) SetNextPC(nextPC uint32) {
thread := m.state.GetCurrentThread()
thread.Cpu.NextPC = nextPC
}
func (m *multithreadedMutator) SetLastHint(lastHint hexutil.Bytes) {
m.state.LastHint = lastHint
}
func (m *multithreadedMutator) SetPreimageKey(key common.Hash) {
m.state.PreimageKey = key
}
func (m *multithreadedMutator) SetPreimageOffset(offset uint32) {
m.state.PreimageOffset = offset
}
func (m *multithreadedMutator) SetStep(step uint64) {
m.state.Step = step
}
type VMOption func(vm StateMutator)
func WithPC(pc uint32) VMOption {
return func(state StateMutator) {
state.SetPC(pc)
}
}
func WithNextPC(nextPC uint32) VMOption {
return func(state StateMutator) {
state.SetNextPC(nextPC)
}
}
func WithHeap(addr uint32) VMOption {
return func(state StateMutator) {
state.SetHeap(addr)
}
}
func WithLastHint(lastHint hexutil.Bytes) VMOption {
return func(state StateMutator) {
state.SetLastHint(lastHint)
}
}
func WithPreimageKey(key common.Hash) VMOption {
return func(state StateMutator) {
state.SetPreimageKey(key)
}
}
func WithPreimageOffset(offset uint32) VMOption {
return func(state StateMutator) {
state.SetPreimageOffset(offset)
}
}
func WithStep(step uint64) VMOption {
return func(state StateMutator) {
state.SetStep(step)
}
}
type VMFactory func(po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger, opts ...VMOption) mipsevm.FPVM
type VMFactory func(po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger, opts ...testutil.StateOption) mipsevm.FPVM
func singleThreadedVmFactory(po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger, opts ...VMOption) mipsevm.FPVM {
func singleThreadedVmFactory(po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger, opts ...testutil.StateOption) mipsevm.FPVM {
state := singlethreaded.CreateEmptyState()
mutator := &singlethreadedMutator{state: state}
mutator := sttestutil.NewStateMutatorSingleThreaded(state)
for _, opt := range opts {
opt(mutator)
}
return singlethreaded.NewInstrumentedState(state, po, stdOut, stdErr, nil)
}
func multiThreadedVmFactory(po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger, opts ...VMOption) mipsevm.FPVM {
func multiThreadedVmFactory(po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger, opts ...testutil.StateOption) mipsevm.FPVM {
state := multithreaded.CreateEmptyState()
mutator := &multithreadedMutator{state: state}
mutator := mttestutil.NewStateMutatorMultiThreaded(state)
for _, opt := range opts {
opt(mutator)
}
......
......@@ -145,3 +145,19 @@ func SelectOracleFixture(t *testing.T, programName string) mipsevm.PreimageOracl
return nil
}
}
type HintTrackingOracle struct {
hints [][]byte
}
func (t *HintTrackingOracle) Hint(v []byte) {
t.hints = append(t.hints, v)
}
func (t *HintTrackingOracle) GetPreimage(k [32]byte) []byte {
return nil
}
func (t *HintTrackingOracle) Hints() [][]byte {
return t.hints
}
package testutil
import (
"fmt"
"math/rand"
"slices"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
)
func RandomRegisters(seed int64) [32]uint32 {
func CopyRegisters(state mipsevm.FPVMState) *[32]uint32 {
copy := new([32]uint32)
*copy = *state.GetRegistersRef()
return copy
}
type StateMutator interface {
SetPreimageKey(val common.Hash)
SetPreimageOffset(val uint32)
SetPC(val uint32)
SetNextPC(val uint32)
SetHI(val uint32)
SetLO(val uint32)
SetHeap(addr uint32)
SetExitCode(val uint8)
SetExited(val bool)
SetStep(val uint64)
SetLastHint(val hexutil.Bytes)
GetRegistersRef() *[32]uint32
}
type StateOption func(state StateMutator)
func WithPC(pc uint32) StateOption {
return func(state StateMutator) {
state.SetPC(pc)
}
}
func WithNextPC(nextPC uint32) StateOption {
return func(state StateMutator) {
state.SetNextPC(nextPC)
}
}
func WithHeap(addr uint32) StateOption {
return func(state StateMutator) {
state.SetHeap(addr)
}
}
func WithLastHint(lastHint hexutil.Bytes) StateOption {
return func(state StateMutator) {
state.SetLastHint(lastHint)
}
}
func WithPreimageKey(key common.Hash) StateOption {
return func(state StateMutator) {
state.SetPreimageKey(key)
}
}
func WithPreimageOffset(offset uint32) StateOption {
return func(state StateMutator) {
state.SetPreimageOffset(offset)
}
}
func WithStep(step uint64) StateOption {
return func(state StateMutator) {
state.SetStep(step)
}
}
func WithRandomization(seed int64) StateOption {
return func(mut StateMutator) {
RandomizeState(seed, mut)
}
}
func RandomizeState(seed int64, mut StateMutator) {
r := rand.New(rand.NewSource(seed))
var registers [32]uint32
// Memory-align random pc and leave room for nextPC
pc := r.Uint32() & 0xFF_FF_FF_FC // Align address
if pc >= 0xFF_FF_FF_FC {
// Leave room to set and then increment nextPC
pc = 0xFF_FF_FF_FC - 8
}
// Set random step, but leave room to increment
step := r.Uint64()
if step == ^uint64(0) {
step -= 1
}
mut.SetPreimageKey(randHash(r))
mut.SetPreimageOffset(r.Uint32())
mut.SetPC(pc)
mut.SetNextPC(pc + 4)
mut.SetHI(r.Uint32())
mut.SetLO(r.Uint32())
mut.SetHeap(r.Uint32())
mut.SetStep(step)
mut.SetLastHint(randHint(r))
*mut.GetRegistersRef() = *randRegisters(r)
}
type ExpectedState struct {
PreimageKey common.Hash
PreimageOffset uint32
PC uint32
NextPC uint32
HI uint32
LO uint32
Heap uint32
ExitCode uint8
Exited bool
Step uint64
LastHint hexutil.Bytes
Registers [32]uint32
MemoryRoot common.Hash
}
func CreateExpectedState(fromState mipsevm.FPVMState) *ExpectedState {
return &ExpectedState{
PreimageKey: fromState.GetPreimageKey(),
PreimageOffset: fromState.GetPreimageOffset(),
PC: fromState.GetPC(),
NextPC: fromState.GetCpu().NextPC,
HI: fromState.GetCpu().HI,
LO: fromState.GetCpu().LO,
Heap: fromState.GetHeap(),
ExitCode: fromState.GetExitCode(),
Exited: fromState.GetExited(),
Step: fromState.GetStep(),
LastHint: fromState.GetLastHint(),
Registers: *fromState.GetRegistersRef(),
MemoryRoot: fromState.GetMemory().MerkleRoot(),
}
}
type StateValidationFlags int
// TODO(cp-983) - Remove these validation hacks
const (
SkipMemoryValidation StateValidationFlags = iota
SkipHintValidation
SkipPreimageKeyValidation
)
func (e *ExpectedState) Validate(t testing.TB, actualState mipsevm.FPVMState, flags ...StateValidationFlags) {
if !slices.Contains(flags, SkipPreimageKeyValidation) {
require.Equal(t, e.PreimageKey, actualState.GetPreimageKey(), fmt.Sprintf("Expect preimageKey = %v", e.PreimageKey))
}
require.Equal(t, e.PreimageOffset, actualState.GetPreimageOffset(), fmt.Sprintf("Expect preimageOffset = %v", e.PreimageOffset))
require.Equal(t, e.PC, actualState.GetCpu().PC, fmt.Sprintf("Expect PC = 0x%x", e.PC))
require.Equal(t, e.NextPC, actualState.GetCpu().NextPC, fmt.Sprintf("Expect nextPC = 0x%x", e.NextPC))
require.Equal(t, e.HI, actualState.GetCpu().HI, fmt.Sprintf("Expect HI = 0x%x", e.HI))
require.Equal(t, e.LO, actualState.GetCpu().LO, fmt.Sprintf("Expect LO = 0x%x", e.LO))
require.Equal(t, e.Heap, actualState.GetHeap(), fmt.Sprintf("Expect heap = 0x%x", e.Heap))
require.Equal(t, e.ExitCode, actualState.GetExitCode(), fmt.Sprintf("Expect exitCode = 0x%x", e.ExitCode))
require.Equal(t, e.Exited, actualState.GetExited(), fmt.Sprintf("Expect exited = %v", e.Exited))
require.Equal(t, e.Step, actualState.GetStep(), fmt.Sprintf("Expect step = %d", e.Step))
if !slices.Contains(flags, SkipHintValidation) {
require.Equal(t, e.LastHint, actualState.GetLastHint(), fmt.Sprintf("Expect lastHint = %v", e.LastHint))
}
require.Equal(t, e.Registers, *actualState.GetRegistersRef(), fmt.Sprintf("Expect registers = %v", e.Registers))
if !slices.Contains(flags, SkipMemoryValidation) {
require.Equal(t, e.MemoryRoot, common.Hash(actualState.GetMemory().MerkleRoot()), fmt.Sprintf("Expect memory root = %v", e.MemoryRoot))
}
}
func randHash(r *rand.Rand) common.Hash {
var bytes [32]byte
_, err := r.Read(bytes[:])
if err != nil {
panic(err)
}
return bytes
}
func randHint(r *rand.Rand) []byte {
count := r.Intn(10)
bytes := make([]byte, count)
_, err := r.Read(bytes[:])
if err != nil {
panic(err)
}
return bytes
}
func randRegisters(r *rand.Rand) *[32]uint32 {
registers := new([32]uint32)
for i := 0; i < 32; i++ {
registers[i] = r.Uint32()
}
return registers
}
func CopyRegisters(state mipsevm.FPVMState) *[32]uint32 {
copy := new([32]uint32)
*copy = *state.GetRegistersRef()
return copy
func RandomBytes(t require.TestingT, seed int64, length uint32) []byte {
r := rand.New(rand.NewSource(seed))
randBytes := make([]byte, length)
if _, err := r.Read(randBytes); err != nil {
require.NoError(t, err)
}
return randBytes
}
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