Commit 4584c976 authored by mbaxter's avatar mbaxter Committed by GitHub

cannon: Modularize mipsevm packages (#11135)

* cannon: Isolate test utilities, rework types to avoid circular dependencies

* cannon: Rollback changes to MIPSEVM.EncodePreimageOracleInput

* cannon: Move open_mips_tests to tests subpackage

* cannon: Update some paths to open_mips_tests

* cannon: Collect oracle utils, move generic tests to tests package

* cannon: Move common state constants and utilities to core pkg

* cannon: Move state.go and state_mt.go into impls package

* cannon: Create a memory subpackage

* cannon: Create exec pkg for mips logic

* cannon: Wrap remaining core files in isolated pkgs

* cannon: Create a few more subpackages

* cannon: Rename state_mt to state

* cannon: Move hex utility

* cannon: Pull loading functionality out of patch.go

* cannon: Remove 'MT' prefixes on multi_threaded/state.go symbols

* cannon: Add compile-time PreimageOracle check back in

* cannon: Undo excessive nesting in core package

* cannon: Reorganize single- and multithreaded pkgs

* cannon: Rename test_util pkg to testutil

* cannon: Move instrumented_state logic into singlethreaded pkg

* cannon: Fix test paths

* cannon: Move core pkgs back to root mipsevm pkg

* cannon: Flatten util pkg back into main mipsevm pkg

* cannon: Cleanup - don't use vmstate for mipsevm pkg

* cannon: Add godoc for FPVMState.GetRegisters()

* cannon: Cleanup - fix pkg name, fn visibility
parent f5221f4d
......@@ -162,11 +162,11 @@ jobs:
steps:
- checkout
- check-changed:
patterns: cannon/mipsevm/open_mips_tests/test
patterns: cannon/mipsevm/tests/open_mips_tests/test
- run:
name: Build MIPS test vectors
command: python3 maketests.py && git diff --exit-code
working_directory: cannon/mipsevm/open_mips_tests
working_directory: cannon/mipsevm/tests/open_mips_tests
pnpm-monorepo:
docker:
......
......@@ -6,7 +6,8 @@ import (
"github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
"github.com/ethereum-optimism/optimism/op-service/jsonutil"
)
......@@ -46,16 +47,16 @@ func LoadELF(ctx *cli.Context) error {
if elfProgram.Machine != elf.EM_MIPS {
return fmt.Errorf("ELF is not big-endian MIPS R3000, but got %q", elfProgram.Machine.String())
}
state, err := mipsevm.LoadELF(elfProgram, mipsevm.CreateInitialState)
state, err := program.LoadELF(elfProgram, singlethreaded.CreateInitialState)
if err != nil {
return fmt.Errorf("failed to load ELF data into VM state: %w", err)
}
for _, typ := range ctx.StringSlice(LoadELFPatchFlag.Name) {
switch typ {
case "stack":
err = mipsevm.PatchStack(state)
err = program.PatchStack(state)
case "go":
err = mipsevm.PatchGo(elfProgram, state)
err = program.PatchGo(elfProgram, state)
default:
return fmt.Errorf("unrecognized form of patching: %q", typ)
}
......@@ -63,14 +64,14 @@ func LoadELF(ctx *cli.Context) error {
return fmt.Errorf("failed to apply patch %s: %w", typ, err)
}
}
meta, err := mipsevm.MakeMetadata(elfProgram)
meta, err := program.MakeMetadata(elfProgram)
if err != nil {
return fmt.Errorf("failed to compute program metadata: %w", err)
}
if err := jsonutil.WriteJSON[*mipsevm.Metadata](ctx.Path(LoadELFMetaFlag.Name), meta, OutFilePerm); err != nil {
if err := jsonutil.WriteJSON[*program.Metadata](ctx.Path(LoadELFMetaFlag.Name), meta, OutFilePerm); err != nil {
return fmt.Errorf("failed to output metadata: %w", err)
}
return jsonutil.WriteJSON[*mipsevm.State](ctx.Path(LoadELFOutFlag.Name), state, OutFilePerm)
return jsonutil.WriteJSON[*singlethreaded.State](ctx.Path(LoadELFOutFlag.Name), state, OutFilePerm)
}
var LoadELFCommand = &cli.Command{
......
......@@ -13,11 +13,12 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2"
"github.com/pkg/profile"
"github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum-optimism/optimism/op-service/jsonutil"
)
......@@ -350,12 +351,12 @@ func Run(ctx *cli.Context) error {
snapshotAt := ctx.Generic(RunSnapshotAtFlag.Name).(*StepMatcherFlag).Matcher()
infoAt := ctx.Generic(RunInfoAtFlag.Name).(*StepMatcherFlag).Matcher()
var meta *mipsevm.Metadata
var meta *program.Metadata
if metaPath := ctx.Path(RunMetaFlag.Name); metaPath == "" {
l.Info("no metadata file specified, defaulting to empty metadata")
meta = &mipsevm.Metadata{Symbols: nil} // provide empty metadata by default
meta = &program.Metadata{Symbols: nil} // provide empty metadata by default
} else {
if m, err := jsonutil.LoadJSON[mipsevm.Metadata](metaPath); err != nil {
if m, err := jsonutil.LoadJSON[program.Metadata](metaPath); err != nil {
return fmt.Errorf("failed to load metadata: %w", err)
} else {
meta = m
......@@ -365,7 +366,7 @@ func Run(ctx *cli.Context) error {
var vm mipsevm.FPVM
var debugProgram bool
if vmType == cannonVMType {
cannon, err := mipsevm.NewInstrumentedStateFromFile(ctx.Path(RunInputFlag.Name), po, outLog, errLog)
cannon, err := singlethreaded.NewInstrumentedStateFromFile(ctx.Path(RunInputFlag.Name), po, outLog, errLog)
if err != nil {
return err
}
......
......@@ -4,9 +4,10 @@ import (
"fmt"
"os"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-service/jsonutil"
"github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
"github.com/ethereum-optimism/optimism/op-service/jsonutil"
)
var (
......@@ -26,7 +27,7 @@ var (
func Witness(ctx *cli.Context) error {
input := ctx.Path(WitnessInputFlag.Name)
output := ctx.Path(WitnessOutputFlag.Name)
state, err := jsonutil.LoadJSON[mipsevm.State](input)
state, err := jsonutil.LoadJSON[singlethreaded.State](input)
if err != nil {
return fmt.Errorf("invalid input state (%v): %w", input, err)
}
......
package mipsevm
type DebugInfo struct {
Pages int `json:"pages"`
NumPreimageRequests int `json:"num_preimage_requests"`
TotalPreimageSize int `json:"total_preimage_size"`
}
package mipsevm
package exec
import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
)
type StackTracker interface {
pushStack(target uint32)
popStack()
PushStack(target uint32)
PopStack()
}
func getInstructionDetails(pc uint32, memory *Memory) (insn, opcode, fun uint32) {
func GetInstructionDetails(pc uint32, memory *memory.Memory) (insn, opcode, fun uint32) {
insn = memory.GetMemory(pc)
opcode = insn >> 26 // First 6-bits
fun = insn & 0x3f // Last 6-bits
......@@ -13,7 +18,7 @@ func getInstructionDetails(pc uint32, memory *Memory) (insn, opcode, fun uint32)
return insn, opcode, fun
}
func execMipsCoreStepLogic(cpu *CpuScalars, registers *[32]uint32, memory *Memory, insn, opcode, fun uint32, memTracker MemTracker, stackTracker StackTracker) error {
func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memory *memory.Memory, insn, opcode, fun uint32, memTracker memory.MemTracker, stackTracker StackTracker) error {
// j-type j/jal
if opcode == 2 || opcode == 3 {
linkReg := uint32(0)
......@@ -22,8 +27,8 @@ func execMipsCoreStepLogic(cpu *CpuScalars, registers *[32]uint32, memory *Memor
}
// Take top 4 bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset
target := (cpu.NextPC & 0xF0000000) | ((insn & 0x03FFFFFF) << 2)
stackTracker.pushStack(target)
return handleJump(cpu, registers, linkReg, target)
stackTracker.PushStack(target)
return HandleJump(cpu, registers, linkReg, target)
}
// register fetch
......@@ -46,7 +51,7 @@ func execMipsCoreStepLogic(cpu *CpuScalars, registers *[32]uint32, memory *Memor
rt = insn & 0xFFFF
} else {
// SignExtImm
rt = signExtend(insn&0xFFFF, 16)
rt = SignExtend(insn&0xFFFF, 16)
}
} else if opcode >= 0x28 || opcode == 0x22 || opcode == 0x26 {
// store rt value with store
......@@ -57,7 +62,7 @@ func execMipsCoreStepLogic(cpu *CpuScalars, registers *[32]uint32, memory *Memor
}
if (opcode >= 4 && opcode < 8) || opcode == 1 {
return handleBranch(cpu, registers, opcode, insn, rtReg, rs)
return HandleBranch(cpu, registers, opcode, insn, rtReg, rs)
}
storeAddr := uint32(0xFF_FF_FF_FF)
......@@ -66,7 +71,7 @@ func execMipsCoreStepLogic(cpu *CpuScalars, registers *[32]uint32, memory *Memor
mem := uint32(0)
if opcode >= 0x20 {
// M[R[rs]+SignExtImm]
rs += signExtend(insn&0xFFFF, 16)
rs += SignExtend(insn&0xFFFF, 16)
addr := rs & 0xFFFFFFFC
memTracker(addr)
mem = memory.GetMemory(addr)
......@@ -79,7 +84,7 @@ func execMipsCoreStepLogic(cpu *CpuScalars, registers *[32]uint32, memory *Memor
}
// ALU
val := executeMipsInstruction(insn, opcode, fun, rs, rt, mem)
val := ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem)
if opcode == 0 && fun >= 8 && fun < 0x1c {
if fun == 8 || fun == 9 { // jr/jalr
......@@ -87,21 +92,21 @@ func execMipsCoreStepLogic(cpu *CpuScalars, registers *[32]uint32, memory *Memor
if fun == 9 {
linkReg = rdReg
}
stackTracker.popStack()
return handleJump(cpu, registers, linkReg, rs)
stackTracker.PopStack()
return HandleJump(cpu, registers, linkReg, rs)
}
if fun == 0xa { // movz
return handleRd(cpu, registers, rdReg, rs, rt == 0)
return HandleRd(cpu, registers, rdReg, rs, rt == 0)
}
if fun == 0xb { // movn
return handleRd(cpu, registers, rdReg, rs, rt != 0)
return HandleRd(cpu, registers, rdReg, rs, rt != 0)
}
// lo and hi registers
// can write back
if fun >= 0x10 && fun < 0x1c {
return handleHiLo(cpu, registers, fun, rs, rt, rdReg)
return HandleHiLo(cpu, registers, fun, rs, rt, rdReg)
}
}
......@@ -117,10 +122,10 @@ func execMipsCoreStepLogic(cpu *CpuScalars, registers *[32]uint32, memory *Memor
}
// write back the value to destination register
return handleRd(cpu, registers, rdReg, val, true)
return HandleRd(cpu, registers, rdReg, val, true)
}
func executeMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
func ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
if opcode == 0 || (opcode >= 8 && opcode < 0xF) {
// transform ArithLogI to SPECIAL
switch opcode {
......@@ -147,13 +152,13 @@ func executeMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
return rt >> ((insn >> 6) & 0x1F)
case 0x03: // sra
shamt := (insn >> 6) & 0x1F
return signExtend(rt>>shamt, 32-shamt)
return SignExtend(rt>>shamt, 32-shamt)
case 0x04: // sllv
return rt << (rs & 0x1F)
case 0x06: // srlv
return rt >> (rs & 0x1F)
case 0x07: // srav
return signExtend(rt>>rs, 32-rs)
return SignExtend(rt>>rs, 32-rs)
// functs in range [0x8, 0x1b] are handled specially by other functions
case 0x08: // jr
return rs
......@@ -234,9 +239,9 @@ func executeMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
case 0x0F: // lui
return rt << 16
case 0x20: // lb
return signExtend((mem>>(24-(rs&3)*8))&0xFF, 8)
return SignExtend((mem>>(24-(rs&3)*8))&0xFF, 8)
case 0x21: // lh
return signExtend((mem>>(16-(rs&2)*8))&0xFFFF, 16)
return SignExtend((mem>>(16-(rs&2)*8))&0xFFFF, 16)
case 0x22: // lwl
val := mem << ((rs & 3) * 8)
mask := uint32(0xFFFFFFFF) << ((rs & 3) * 8)
......@@ -280,7 +285,7 @@ func executeMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
panic("invalid instruction")
}
func signExtend(dat uint32, idx uint32) uint32 {
func SignExtend(dat uint32, idx uint32) uint32 {
isSigned := (dat >> (idx - 1)) != 0
signed := ((uint32(1) << (32 - idx)) - 1) << idx
mask := (uint32(1) << idx) - 1
......@@ -291,7 +296,7 @@ func signExtend(dat uint32, idx uint32) uint32 {
}
}
func handleBranch(cpu *CpuScalars, registers *[32]uint32, opcode uint32, insn uint32, rtReg uint32, rs uint32) error {
func HandleBranch(cpu *mipsevm.CpuScalars, registers *[32]uint32, opcode uint32, insn uint32, rtReg uint32, rs uint32) error {
if cpu.NextPC != cpu.PC+4 {
panic("branch in delay slot")
}
......@@ -318,14 +323,14 @@ func handleBranch(cpu *CpuScalars, registers *[32]uint32, opcode uint32, insn ui
prevPC := cpu.PC
cpu.PC = cpu.NextPC // execute the delay slot first
if shouldBranch {
cpu.NextPC = prevPC + 4 + (signExtend(insn&0xFFFF, 16) << 2) // then continue with the instruction the branch jumps to.
cpu.NextPC = prevPC + 4 + (SignExtend(insn&0xFFFF, 16) << 2) // then continue with the instruction the branch jumps to.
} else {
cpu.NextPC = cpu.NextPC + 4 // branch not taken
}
return nil
}
func handleHiLo(cpu *CpuScalars, registers *[32]uint32, fun uint32, rs uint32, rt uint32, storeReg uint32) error {
func HandleHiLo(cpu *mipsevm.CpuScalars, registers *[32]uint32, fun uint32, rs uint32, rt uint32, storeReg uint32) error {
val := uint32(0)
switch fun {
case 0x10: // mfhi
......@@ -361,7 +366,7 @@ func handleHiLo(cpu *CpuScalars, registers *[32]uint32, fun uint32, rs uint32, r
return nil
}
func handleJump(cpu *CpuScalars, registers *[32]uint32, linkReg uint32, dest uint32) error {
func HandleJump(cpu *mipsevm.CpuScalars, registers *[32]uint32, linkReg uint32, dest uint32) error {
if cpu.NextPC != cpu.PC+4 {
panic("jump in delay slot")
}
......@@ -374,7 +379,7 @@ func handleJump(cpu *CpuScalars, registers *[32]uint32, linkReg uint32, dest uin
return nil
}
func handleRd(cpu *CpuScalars, registers *[32]uint32, storeReg uint32, val uint32, conditional bool) error {
func HandleRd(cpu *mipsevm.CpuScalars, registers *[32]uint32, storeReg uint32, val uint32, conditional bool) error {
if storeReg >= 32 {
panic("invalid register")
}
......
package mipsevm
package exec
import (
"encoding/binary"
......@@ -6,26 +6,29 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
)
const (
sysMmap = 4090
sysBrk = 4045
sysClone = 4120
sysExitGroup = 4246
sysRead = 4003
sysWrite = 4004
sysFcntl = 4055
SysMmap = 4090
SysBrk = 4045
SysClone = 4120
SysExitGroup = 4246
SysRead = 4003
SysWrite = 4004
SysFcntl = 4055
)
const (
fdStdin = 0
fdStdout = 1
fdStderr = 2
fdHintRead = 3
fdHintWrite = 4
fdPreimageRead = 5
fdPreimageWrite = 6
FdStdin = 0
FdStdout = 1
FdStderr = 2
FdHintRead = 3
FdHintWrite = 4
FdPreimageRead = 5
FdPreimageWrite = 6
)
const (
......@@ -35,7 +38,7 @@ const (
type PreimageReader func(key [32]byte, offset uint32) (dat [32]byte, datLen uint32)
func getSyscallArgs(registers *[32]uint32) (syscallNum, a0, a1, a2 uint32) {
func GetSyscallArgs(registers *[32]uint32) (syscallNum, a0, a1, a2 uint32) {
syscallNum = registers[2] // v0
a0 = registers[4]
......@@ -45,13 +48,13 @@ func getSyscallArgs(registers *[32]uint32) (syscallNum, a0, a1, a2 uint32) {
return syscallNum, a0, a1, a2
}
func handleSysMmap(a0, a1, heap uint32) (v0, v1, newHeap uint32) {
func HandleSysMmap(a0, a1, heap uint32) (v0, v1, newHeap uint32) {
v1 = uint32(0)
newHeap = heap
sz := a1
if sz&PageAddrMask != 0 { // adjust size to align with page size
sz += PageSize - (sz & PageAddrMask)
if sz&memory.PageAddrMask != 0 { // adjust size to align with page size
sz += memory.PageSize - (sz & memory.PageAddrMask)
}
if a0 == 0 {
v0 = heap
......@@ -65,7 +68,7 @@ func handleSysMmap(a0, a1, heap uint32) (v0, v1, newHeap uint32) {
return v0, v1, newHeap
}
func handleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint32, preimageReader PreimageReader, memory *Memory, memTracker MemTracker) (v0, v1, newPreimageOffset uint32) {
func HandleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint32, preimageReader PreimageReader, memory *memory.Memory, memTracker memory.MemTracker) (v0, v1, newPreimageOffset uint32) {
// args: a0 = fd, a1 = addr, a2 = count
// returns: v0 = read, v1 = err code
v0 = uint32(0)
......@@ -73,9 +76,9 @@ func handleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint3
newPreimageOffset = preimageOffset
switch a0 {
case fdStdin:
case FdStdin:
// leave v0 and v1 zero: read nothing, no error
case fdPreimageRead: // pre-image oracle
case FdPreimageRead: // pre-image oracle
effAddr := a1 & 0xFFffFFfc
memTracker(effAddr)
mem := memory.GetMemory(effAddr)
......@@ -96,7 +99,7 @@ func handleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint3
newPreimageOffset += datLen
v0 = datLen
//fmt.Printf("read %d pre-image bytes, new offset: %d, eff addr: %08x mem: %08x\n", datLen, m.state.PreimageOffset, effAddr, outMem)
case fdHintRead: // hint response
case FdHintRead: // hint response
// don't actually read into memory, just say we read it all, we ignore the result anyway
v0 = a2
default:
......@@ -107,7 +110,7 @@ func handleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint3
return v0, v1, newPreimageOffset
}
func handleSysWrite(a0, a1, a2 uint32, lastHint hexutil.Bytes, preimageKey [32]byte, preimageOffset uint32, oracle PreimageOracle, memory *Memory, memTracker MemTracker, stdOut, stdErr io.Writer) (v0, v1 uint32, newLastHint hexutil.Bytes, newPreimageKey common.Hash, newPreimageOffset uint32) {
func HandleSysWrite(a0, a1, a2 uint32, lastHint hexutil.Bytes, preimageKey [32]byte, preimageOffset uint32, oracle mipsevm.PreimageOracle, memory *memory.Memory, memTracker memory.MemTracker, stdOut, stdErr io.Writer) (v0, v1 uint32, newLastHint hexutil.Bytes, newPreimageKey common.Hash, newPreimageOffset uint32) {
// args: a0 = fd, a1 = addr, a2 = count
// returns: v0 = written, v1 = err code
v1 = uint32(0)
......@@ -116,13 +119,13 @@ func handleSysWrite(a0, a1, a2 uint32, lastHint hexutil.Bytes, preimageKey [32]b
newPreimageOffset = preimageOffset
switch a0 {
case fdStdout:
case FdStdout:
_, _ = io.Copy(stdOut, memory.ReadMemoryRange(a1, a2))
v0 = a2
case fdStderr:
case FdStderr:
_, _ = io.Copy(stdErr, memory.ReadMemoryRange(a1, a2))
v0 = a2
case fdHintWrite:
case FdHintWrite:
hintData, _ := io.ReadAll(memory.ReadMemoryRange(a1, a2))
lastHint = append(lastHint, hintData...)
for len(lastHint) >= 4 { // process while there is enough data to check if there are any hints
......@@ -137,7 +140,7 @@ func handleSysWrite(a0, a1, a2 uint32, lastHint hexutil.Bytes, preimageKey [32]b
}
newLastHint = lastHint
v0 = a2
case fdPreimageWrite:
case FdPreimageWrite:
effAddr := a1 & 0xFFffFFfc
memTracker(effAddr)
mem := memory.GetMemory(effAddr)
......@@ -163,15 +166,15 @@ func handleSysWrite(a0, a1, a2 uint32, lastHint hexutil.Bytes, preimageKey [32]b
return v0, v1, newLastHint, newPreimageKey, newPreimageOffset
}
func handleSysFcntl(a0, a1 uint32) (v0, v1 uint32) {
func HandleSysFcntl(a0, a1 uint32) (v0, v1 uint32) {
// args: a0 = fd, a1 = cmd
v1 = uint32(0)
if a1 == 3 { // F_GETFL: get file descriptor flags
switch a0 {
case fdStdin, fdPreimageRead, fdHintRead:
case FdStdin, FdPreimageRead, FdHintRead:
v0 = 0 // O_RDONLY
case fdStdout, fdStderr, fdPreimageWrite, fdHintWrite:
case FdStdout, FdStderr, FdPreimageWrite, FdHintWrite:
v0 = 1 // O_WRONLY
default:
v0 = 0xFFffFFff
......@@ -185,7 +188,7 @@ func handleSysFcntl(a0, a1 uint32) (v0, v1 uint32) {
return v0, v1
}
func handleSyscallUpdates(cpu *CpuScalars, registers *[32]uint32, v0, v1 uint32) {
func HandleSyscallUpdates(cpu *mipsevm.CpuScalars, registers *[32]uint32, v0, v1 uint32) {
registers[2] = v0
registers[7] = v1
......
package mipsevm
import "fmt"
// HexU32 to lazy-format integer attributes for logging
type HexU32 uint32
func (v HexU32) String() string {
return fmt.Sprintf("%08x", uint32(v))
}
func (v HexU32) MarshalText() ([]byte, error) {
return []byte(v.String()), nil
}
package mipsevm
import "github.com/ethereum/go-ethereum/common"
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
)
type FPVMState interface {
GetMemory() *Memory
GetMemory() *memory.Memory
// GetPC returns the currently executing program counter
GetPC() uint32
// GetRegisters returns the currently active registers
GetRegisters() *[32]uint32
// GetStep returns the current VM step
GetStep() uint64
......
package mipsevm
package memory
import (
"encoding/binary"
......@@ -335,3 +335,5 @@ func (m *Memory) Usage() string {
// KiB, MiB, GiB, TiB, ...
return fmt.Sprintf("%.1f %ciB", float64(total)/float64(div), "KMGTPE"[exp])
}
type MemTracker func(addr uint32)
package mipsevm
package memory
import (
"bytes"
......
package mipsevm
package multithreaded
import (
"encoding/binary"
......@@ -7,6 +7,9 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
)
// SERIALIZED_THREAD_SIZE is the size of a serialized ThreadState object
......@@ -28,7 +31,7 @@ type ThreadState struct {
FutexAddr uint32 `json:"futexAddr"`
FutexVal uint32 `json:"futexVal"`
FutexTimeoutStep uint64 `json:"futexTimeoutStep"`
Cpu CpuScalars `json:"cpu"`
Cpu mipsevm.CpuScalars `json:"cpu"`
Registers [32]uint32 `json:"registers"`
}
......@@ -37,7 +40,7 @@ func (t *ThreadState) serializeThread() []byte {
out = binary.BigEndian.AppendUint32(out, t.ThreadId)
out = append(out, t.ExitCode)
out = AppendBoolToWitness(out, t.Exited)
out = mipsevm.AppendBoolToWitness(out, t.Exited)
out = binary.BigEndian.AppendUint32(out, t.FutexAddr)
out = binary.BigEndian.AppendUint32(out, t.FutexVal)
out = binary.BigEndian.AppendUint64(out, t.FutexTimeoutStep)
......@@ -64,26 +67,26 @@ func computeThreadRoot(prevStackRoot common.Hash, threadToPush *ThreadState) com
return crypto.Keccak256Hash(hashData)
}
// MT_STATE_WITNESS_SIZE is the size of the state witness encoding in bytes.
const MT_STATE_WITNESS_SIZE = 163
// STATE_WITNESS_SIZE is the size of the state witness encoding in bytes.
const STATE_WITNESS_SIZE = 163
const (
MEMROOT_MT_WITNESS_OFFSET = 0
PREIMAGE_KEY_MT_WITNESS_OFFSET = MEMROOT_MT_WITNESS_OFFSET + 32
PREIMAGE_OFFSET_MT_WITNESS_OFFSET = PREIMAGE_KEY_MT_WITNESS_OFFSET + 32
HEAP_MT_WITNESS_OFFSET = PREIMAGE_OFFSET_MT_WITNESS_OFFSET + 4
EXITCODE_MT_WITNESS_OFFSET = HEAP_MT_WITNESS_OFFSET + 4
EXITED_MT_WITNESS_OFFSET = EXITCODE_MT_WITNESS_OFFSET + 1
STEP_MT_WITNESS_OFFSET = EXITED_MT_WITNESS_OFFSET + 1
STEPS_SINCE_CONTEXT_SWITCH_MT_WITNESS_OFFSET = STEP_MT_WITNESS_OFFSET + 8
WAKEUP_MT_WITNESS_OFFSET = STEPS_SINCE_CONTEXT_SWITCH_MT_WITNESS_OFFSET + 8
TRAVERSE_RIGHT_MT_WITNESS_OFFSET = WAKEUP_MT_WITNESS_OFFSET + 4
LEFT_THREADS_ROOT_MT_WITNESS_OFFSET = TRAVERSE_RIGHT_MT_WITNESS_OFFSET + 1
RIGHT_THREADS_ROOT_MT_WITNESS_OFFSET = LEFT_THREADS_ROOT_MT_WITNESS_OFFSET + 32
THREAD_ID_MT_WITNESS_OFFSET = RIGHT_THREADS_ROOT_MT_WITNESS_OFFSET + 32
MEMROOT_WITNESS_OFFSET = 0
PREIMAGE_KEY_WITNESS_OFFSET = MEMROOT_WITNESS_OFFSET + 32
PREIMAGE_OFFSET_WITNESS_OFFSET = PREIMAGE_KEY_WITNESS_OFFSET + 32
HEAP_WITNESS_OFFSET = PREIMAGE_OFFSET_WITNESS_OFFSET + 4
EXITCODE_WITNESS_OFFSET = HEAP_WITNESS_OFFSET + 4
EXITED_WITNESS_OFFSET = EXITCODE_WITNESS_OFFSET + 1
STEP_WITNESS_OFFSET = EXITED_WITNESS_OFFSET + 1
STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET = STEP_WITNESS_OFFSET + 8
WAKEUP_WITNESS_OFFSET = STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET + 8
TRAVERSE_RIGHT_WITNESS_OFFSET = WAKEUP_WITNESS_OFFSET + 4
LEFT_THREADS_ROOT_WITNESS_OFFSET = TRAVERSE_RIGHT_WITNESS_OFFSET + 1
RIGHT_THREADS_ROOT_WITNESS_OFFSET = LEFT_THREADS_ROOT_WITNESS_OFFSET + 32
THREAD_ID_WITNESS_OFFSET = RIGHT_THREADS_ROOT_WITNESS_OFFSET + 32
)
type MTState struct {
Memory *Memory `json:"memory"`
type State struct {
Memory *memory.Memory `json:"memory"`
PreimageKey common.Hash `json:"preimageKey"`
PreimageOffset uint32 `json:"preimageOffset"` // note that the offset includes the 8-byte length prefix
......@@ -113,13 +116,13 @@ type MTState struct {
LastHint hexutil.Bytes `json:"lastHint,omitempty"`
}
func CreateEmptyMTState() *MTState {
func CreateEmptyState() *State {
initThreadId := uint32(0)
initThread := ThreadState{
ThreadId: initThreadId,
ExitCode: 0,
Exited: false,
Cpu: CpuScalars{
Cpu: mipsevm.CpuScalars{
PC: 0,
NextPC: 0,
LO: 0,
......@@ -131,8 +134,8 @@ func CreateEmptyMTState() *MTState {
Registers: [32]uint32{},
}
return &MTState{
Memory: NewMemory(),
return &State{
Memory: memory.NewMemory(),
Heap: 0,
ExitCode: 0,
Exited: false,
......@@ -145,8 +148,8 @@ func CreateEmptyMTState() *MTState {
}
}
func CreateInitialMTState(pc, heapStart uint32) *MTState {
state := CreateEmptyMTState()
func CreateInitialState(pc, heapStart uint32) *State {
state := CreateEmptyState()
currentThread := state.getCurrentThread()
currentThread.Cpu.PC = pc
currentThread.Cpu.NextPC = pc + 4
......@@ -155,7 +158,7 @@ func CreateInitialMTState(pc, heapStart uint32) *MTState {
return state
}
func (s *MTState) getCurrentThread() *ThreadState {
func (s *State) getCurrentThread() *ThreadState {
activeStack := s.getActiveThreadStack()
activeStackSize := len(activeStack)
......@@ -166,9 +169,7 @@ func (s *MTState) getCurrentThread() *ThreadState {
return &activeStack[activeStackSize-1]
}
type ThreadMutator func(thread *ThreadState)
func (s *MTState) getActiveThreadStack() []ThreadState {
func (s *State) getActiveThreadStack() []ThreadState {
var activeStack []ThreadState
if s.TraverseRight {
activeStack = s.RightThreadStack
......@@ -179,15 +180,15 @@ func (s *MTState) getActiveThreadStack() []ThreadState {
return activeStack
}
func (s *MTState) getRightThreadStackRoot() common.Hash {
func (s *State) getRightThreadStackRoot() common.Hash {
return s.calculateThreadStackRoot(s.RightThreadStack)
}
func (s *MTState) getLeftThreadStackRoot() common.Hash {
func (s *State) getLeftThreadStackRoot() common.Hash {
return s.calculateThreadStackRoot(s.LeftThreadStack)
}
func (s *MTState) calculateThreadStackRoot(stack []ThreadState) common.Hash {
func (s *State) calculateThreadStackRoot(stack []ThreadState) common.Hash {
curRoot := EmptyThreadsRoot
for _, thread := range stack {
curRoot = computeThreadRoot(curRoot, &thread)
......@@ -196,44 +197,49 @@ func (s *MTState) calculateThreadStackRoot(stack []ThreadState) common.Hash {
return curRoot
}
func (s *MTState) PreemptThread() {
func (s *State) PreemptThread() {
// TODO(CP-903)
panic("Not Implemented")
}
func (s *MTState) PushThread(thread *ThreadState) {
func (s *State) PushThread(thread *ThreadState) {
// TODO(CP-903)
panic("Not Implemented")
}
func (s *MTState) GetPC() uint32 {
func (s *State) GetPC() uint32 {
activeThread := s.getCurrentThread()
return activeThread.Cpu.PC
}
func (s *MTState) GetExitCode() uint8 { return s.ExitCode }
func (s *State) GetRegisters() *[32]uint32 {
activeThread := s.getCurrentThread()
return &activeThread.Registers
}
func (s *State) GetExitCode() uint8 { return s.ExitCode }
func (s *MTState) GetExited() bool { return s.Exited }
func (s *State) GetExited() bool { return s.Exited }
func (s *MTState) GetStep() uint64 { return s.Step }
func (s *State) GetStep() uint64 { return s.Step }
func (s *MTState) VMStatus() uint8 {
return vmStatus(s.Exited, s.ExitCode)
func (s *State) VMStatus() uint8 {
return mipsevm.VmStatus(s.Exited, s.ExitCode)
}
func (s *MTState) GetMemory() *Memory {
func (s *State) GetMemory() *memory.Memory {
return s.Memory
}
func (s *MTState) EncodeWitness() ([]byte, common.Hash) {
out := make([]byte, 0, MT_STATE_WITNESS_SIZE)
func (s *State) EncodeWitness() ([]byte, common.Hash) {
out := make([]byte, 0, STATE_WITNESS_SIZE)
memRoot := s.Memory.MerkleRoot()
out = append(out, memRoot[:]...)
out = append(out, s.PreimageKey[:]...)
out = binary.BigEndian.AppendUint32(out, s.PreimageOffset)
out = binary.BigEndian.AppendUint32(out, s.Heap)
out = append(out, s.ExitCode)
out = AppendBoolToWitness(out, s.Exited)
out = mipsevm.AppendBoolToWitness(out, s.Exited)
out = binary.BigEndian.AppendUint64(out, s.Step)
out = binary.BigEndian.AppendUint64(out, s.StepsSinceLastContextSwitch)
......@@ -241,31 +247,31 @@ func (s *MTState) EncodeWitness() ([]byte, common.Hash) {
leftStackRoot := s.getLeftThreadStackRoot()
rightStackRoot := s.getRightThreadStackRoot()
out = AppendBoolToWitness(out, s.TraverseRight)
out = mipsevm.AppendBoolToWitness(out, s.TraverseRight)
out = append(out, (leftStackRoot)[:]...)
out = append(out, (rightStackRoot)[:]...)
out = binary.BigEndian.AppendUint32(out, s.NextThreadId)
return out, mtStateHashFromWitness(out)
return out, stateHashFromWitness(out)
}
type MTStateWitness []byte
type StateWitness []byte
func (sw MTStateWitness) StateHash() (common.Hash, error) {
if len(sw) != MT_STATE_WITNESS_SIZE {
return common.Hash{}, fmt.Errorf("Invalid witness length. Got %d, expected %d", len(sw), MT_STATE_WITNESS_SIZE)
func (sw StateWitness) StateHash() (common.Hash, error) {
if len(sw) != STATE_WITNESS_SIZE {
return common.Hash{}, fmt.Errorf("Invalid witness length. Got %d, expected %d", len(sw), STATE_WITNESS_SIZE)
}
return mtStateHashFromWitness(sw), nil
return stateHashFromWitness(sw), nil
}
func mtStateHashFromWitness(sw []byte) common.Hash {
if len(sw) != MT_STATE_WITNESS_SIZE {
func stateHashFromWitness(sw []byte) common.Hash {
if len(sw) != STATE_WITNESS_SIZE {
panic("Invalid witness length")
}
hash := crypto.Keccak256Hash(sw)
exitCode := sw[EXITCODE_MT_WITNESS_OFFSET]
exited := sw[EXITED_MT_WITNESS_OFFSET]
status := vmStatus(exited == 1, exitCode)
exitCode := sw[EXITCODE_WITNESS_OFFSET]
exited := sw[EXITED_WITNESS_OFFSET]
status := mipsevm.VmStatus(exited == 1, exitCode)
hash[0] = status
return hash
}
package mipsevm
package multithreaded
import (
"debug/elf"
......@@ -7,9 +7,12 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
)
func setWitnessField(witness MTStateWitness, fieldOffset int, fieldData []byte) {
func setWitnessField(witness StateWitness, fieldOffset int, fieldData []byte) {
start := fieldOffset
end := fieldOffset + len(fieldData)
copy(witness[start:end], fieldData)
......@@ -17,7 +20,7 @@ func setWitnessField(witness MTStateWitness, fieldOffset int, fieldData []byte)
// Run through all permutations of `exited` / `exitCode` and ensure that the
// correct witness, state hash, and VM Status is produced.
func TestMTState_EncodeWitness(t *testing.T) {
func TestState_EncodeWitness(t *testing.T) {
cases := []struct {
exited bool
exitCode uint8
......@@ -38,7 +41,7 @@ func TestMTState_EncodeWitness(t *testing.T) {
step := uint64(33)
stepsSinceContextSwitch := uint64(123)
for _, c := range cases {
state := CreateEmptyMTState()
state := CreateEmptyState()
state.Exited = c.exited
state.ExitCode = c.exitCode
state.PreimageKey = preimageKey
......@@ -52,38 +55,38 @@ func TestMTState_EncodeWitness(t *testing.T) {
rightStackRoot := EmptyThreadsRoot
// Set up expected witness
expectedWitness := make(MTStateWitness, MT_STATE_WITNESS_SIZE)
setWitnessField(expectedWitness, MEMROOT_MT_WITNESS_OFFSET, memRoot[:])
setWitnessField(expectedWitness, PREIMAGE_KEY_MT_WITNESS_OFFSET, preimageKey[:])
setWitnessField(expectedWitness, PREIMAGE_OFFSET_MT_WITNESS_OFFSET, []byte{0, 0, 0, byte(preimageOffset)})
setWitnessField(expectedWitness, HEAP_MT_WITNESS_OFFSET, []byte{0, 0, 0, byte(heap)})
setWitnessField(expectedWitness, EXITCODE_MT_WITNESS_OFFSET, []byte{c.exitCode})
expectedWitness := make(StateWitness, STATE_WITNESS_SIZE)
setWitnessField(expectedWitness, MEMROOT_WITNESS_OFFSET, memRoot[:])
setWitnessField(expectedWitness, PREIMAGE_KEY_WITNESS_OFFSET, preimageKey[:])
setWitnessField(expectedWitness, PREIMAGE_OFFSET_WITNESS_OFFSET, []byte{0, 0, 0, byte(preimageOffset)})
setWitnessField(expectedWitness, HEAP_WITNESS_OFFSET, []byte{0, 0, 0, byte(heap)})
setWitnessField(expectedWitness, EXITCODE_WITNESS_OFFSET, []byte{c.exitCode})
if c.exited {
setWitnessField(expectedWitness, EXITED_MT_WITNESS_OFFSET, []byte{1})
setWitnessField(expectedWitness, EXITED_WITNESS_OFFSET, []byte{1})
}
setWitnessField(expectedWitness, STEP_MT_WITNESS_OFFSET, []byte{0, 0, 0, 0, 0, 0, 0, byte(step)})
setWitnessField(expectedWitness, STEPS_SINCE_CONTEXT_SWITCH_MT_WITNESS_OFFSET, []byte{0, 0, 0, 0, 0, 0, 0, byte(stepsSinceContextSwitch)})
setWitnessField(expectedWitness, WAKEUP_MT_WITNESS_OFFSET, []byte{0xFF, 0xFF, 0xFF, 0xFF})
setWitnessField(expectedWitness, TRAVERSE_RIGHT_MT_WITNESS_OFFSET, []byte{0})
setWitnessField(expectedWitness, LEFT_THREADS_ROOT_MT_WITNESS_OFFSET, leftStackRoot[:])
setWitnessField(expectedWitness, RIGHT_THREADS_ROOT_MT_WITNESS_OFFSET, rightStackRoot[:])
setWitnessField(expectedWitness, THREAD_ID_MT_WITNESS_OFFSET, []byte{0, 0, 0, 1})
setWitnessField(expectedWitness, STEP_WITNESS_OFFSET, []byte{0, 0, 0, 0, 0, 0, 0, byte(step)})
setWitnessField(expectedWitness, STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET, []byte{0, 0, 0, 0, 0, 0, 0, byte(stepsSinceContextSwitch)})
setWitnessField(expectedWitness, WAKEUP_WITNESS_OFFSET, []byte{0xFF, 0xFF, 0xFF, 0xFF})
setWitnessField(expectedWitness, TRAVERSE_RIGHT_WITNESS_OFFSET, []byte{0})
setWitnessField(expectedWitness, LEFT_THREADS_ROOT_WITNESS_OFFSET, leftStackRoot[:])
setWitnessField(expectedWitness, RIGHT_THREADS_ROOT_WITNESS_OFFSET, rightStackRoot[:])
setWitnessField(expectedWitness, THREAD_ID_WITNESS_OFFSET, []byte{0, 0, 0, 1})
// Validate witness
actualWitness, actualStateHash := state.EncodeWitness()
require.Equal(t, len(actualWitness), MT_STATE_WITNESS_SIZE, "Incorrect witness size")
require.Equal(t, len(actualWitness), STATE_WITNESS_SIZE, "Incorrect witness size")
require.EqualValues(t, expectedWitness[:], actualWitness[:], "Incorrect witness")
// Validate witness hash
expectedStateHash := crypto.Keccak256Hash(actualWitness)
expectedStateHash[0] = vmStatus(c.exited, c.exitCode)
expectedStateHash[0] = mipsevm.VmStatus(c.exited, c.exitCode)
require.Equal(t, expectedStateHash, actualStateHash, "Incorrect state hash")
}
}
func TestMTState_JSONCodec(t *testing.T) {
elfProgram, err := elf.Open("../example/bin/hello.elf")
func TestState_JSONCodec(t *testing.T) {
elfProgram, err := elf.Open("../../example/bin/hello.elf")
require.NoError(t, err, "open ELF file")
state, err := LoadELF(elfProgram, CreateInitialMTState)
state, err := program.LoadELF(elfProgram, CreateInitialState)
require.NoError(t, err, "load ELF into state")
// Set a few additional fields
state.PreimageKey = crypto.Keccak256Hash([]byte{1, 2, 3, 4})
......@@ -98,7 +101,7 @@ func TestMTState_JSONCodec(t *testing.T) {
stateJSON, err := json.Marshal(state)
require.NoError(t, err)
var newState *MTState
var newState *State
err = json.Unmarshal(stateJSON, &newState)
require.NoError(t, err)
......@@ -118,7 +121,7 @@ func TestMTState_JSONCodec(t *testing.T) {
require.Equal(t, state.LastHint, newState.LastHint)
}
func TestMTState_EmptyThreadsRoot(t *testing.T) {
func TestState_EmptyThreadsRoot(t *testing.T) {
data := [64]byte{}
expectedEmptyRoot := crypto.Keccak256Hash(data[:])
......
package mipsevm
type PreimageOracle interface {
Hint(v []byte)
GetPreimage(k [32]byte) []byte
}
package program
import (
"bytes"
"debug/elf"
"fmt"
"io"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
)
const HEAP_START = 0x05000000
type CreateFPVMState[T mipsevm.FPVMState] func(pc, heapStart uint32) T
func LoadELF[T mipsevm.FPVMState](f *elf.File, initState CreateFPVMState[T]) (T, error) {
var empty T
s := initState(uint32(f.Entry), HEAP_START)
for i, prog := range f.Progs {
if prog.Type == 0x70000003 { // MIPS_ABIFLAGS
continue
}
r := io.Reader(io.NewSectionReader(prog, 0, int64(prog.Filesz)))
if prog.Filesz != prog.Memsz {
if prog.Type == elf.PT_LOAD {
if prog.Filesz < prog.Memsz {
r = io.MultiReader(r, bytes.NewReader(make([]byte, prog.Memsz-prog.Filesz)))
} else {
return empty, fmt.Errorf("invalid PT_LOAD program segment %d, file size (%d) > mem size (%d)", i, prog.Filesz, prog.Memsz)
}
} else {
return empty, fmt.Errorf("program segment %d has different file size (%d) than mem size (%d): filling for non PT_LOAD segments is not supported", i, prog.Filesz, prog.Memsz)
}
}
if prog.Vaddr+prog.Memsz >= uint64(1<<32) {
return empty, fmt.Errorf("program %d out of 32-bit mem range: %x - %x (size: %x)", i, prog.Vaddr, prog.Vaddr+prog.Memsz, prog.Memsz)
}
if prog.Vaddr+prog.Memsz >= HEAP_START {
return empty, fmt.Errorf("program %d overlaps with heap: %x - %x (size: %x). The heap start offset must be reconfigured", i, prog.Vaddr, prog.Vaddr+prog.Memsz, prog.Memsz)
}
if err := s.GetMemory().SetMemoryRange(uint32(prog.Vaddr), r); err != nil {
return empty, fmt.Errorf("failed to read program segment %d: %w", i, err)
}
}
return s, nil
}
package mipsevm
package program
import (
"debug/elf"
......@@ -64,14 +64,3 @@ func (m *Metadata) SymbolMatcher(name string) func(addr uint32) bool {
return false
}
}
// HexU32 to lazy-format integer attributes for logging
type HexU32 uint32
func (v HexU32) String() string {
return fmt.Sprintf("%08x", uint32(v))
}
func (v HexU32) MarshalText() ([]byte, error) {
return []byte(v.String()), nil
}
package mipsevm
package program
import (
"bytes"
"debug/elf"
"encoding/binary"
"fmt"
"io"
)
const HEAP_START = 0x05000000
type CreateFPVMState[T FPVMState] func(pc, heapStart uint32) T
func LoadELF[T FPVMState](f *elf.File, initState CreateFPVMState[T]) (T, error) {
var empty T
s := initState(uint32(f.Entry), HEAP_START)
for i, prog := range f.Progs {
if prog.Type == 0x70000003 { // MIPS_ABIFLAGS
continue
}
r := io.Reader(io.NewSectionReader(prog, 0, int64(prog.Filesz)))
if prog.Filesz != prog.Memsz {
if prog.Type == elf.PT_LOAD {
if prog.Filesz < prog.Memsz {
r = io.MultiReader(r, bytes.NewReader(make([]byte, prog.Memsz-prog.Filesz)))
} else {
return empty, fmt.Errorf("invalid PT_LOAD program segment %d, file size (%d) > mem size (%d)", i, prog.Filesz, prog.Memsz)
}
} else {
return empty, fmt.Errorf("program segment %d has different file size (%d) than mem size (%d): filling for non PT_LOAD segments is not supported", i, prog.Filesz, prog.Memsz)
}
}
if prog.Vaddr+prog.Memsz >= uint64(1<<32) {
return empty, fmt.Errorf("program %d out of 32-bit mem range: %x - %x (size: %x)", i, prog.Vaddr, prog.Vaddr+prog.Memsz, prog.Memsz)
}
if prog.Vaddr+prog.Memsz >= HEAP_START {
return empty, fmt.Errorf("program %d overlaps with heap: %x - %x (size: %x). The heap start offset must be reconfigured", i, prog.Vaddr, prog.Vaddr+prog.Memsz, prog.Memsz)
}
if err := s.GetMemory().SetMemoryRange(uint32(prog.Vaddr), r); err != nil {
return empty, fmt.Errorf("failed to read program segment %d: %w", i, err)
}
}
return s, nil
}
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
)
func PatchGo(f *elf.File, st *State) error {
func PatchGo(f *elf.File, st mipsevm.FPVMState) error {
symbols, err := f.Symbols()
if err != nil {
return fmt.Errorf("failed to read symbols data, cannot patch program: %w", err)
......@@ -77,14 +39,14 @@ func PatchGo(f *elf.File, st *State) error {
// MIPS32 patch: ret (pseudo instruction)
// 03e00008 = jr $ra = ret (pseudo instruction)
// 00000000 = nop (executes with delay-slot, but does nothing)
if err := st.Memory.SetMemoryRange(uint32(s.Value), bytes.NewReader([]byte{
if err := st.GetMemory().SetMemoryRange(uint32(s.Value), bytes.NewReader([]byte{
0x03, 0xe0, 0x00, 0x08,
0, 0, 0, 0,
})); err != nil {
return fmt.Errorf("failed to patch Go runtime.gcenable: %w", err)
}
case "runtime.MemProfileRate":
if err := st.Memory.SetMemoryRange(uint32(s.Value), bytes.NewReader(make([]byte, 4))); err != nil { // disable mem profiling, to avoid a lot of unnecessary floating point ops
if err := st.GetMemory().SetMemoryRange(uint32(s.Value), bytes.NewReader(make([]byte, 4))); err != nil { // disable mem profiling, to avoid a lot of unnecessary floating point ops
return err
}
}
......@@ -92,19 +54,19 @@ func PatchGo(f *elf.File, st *State) error {
return nil
}
func PatchStack(st *State) error {
func PatchStack(st mipsevm.FPVMState) error {
// setup stack pointer
sp := uint32(0x7f_ff_d0_00)
// allocate 1 page for the initial stack data, and 16KB = 4 pages for the stack to grow
if err := st.Memory.SetMemoryRange(sp-4*PageSize, bytes.NewReader(make([]byte, 5*PageSize))); err != nil {
if err := st.GetMemory().SetMemoryRange(sp-4*memory.PageSize, bytes.NewReader(make([]byte, 5*memory.PageSize))); err != nil {
return fmt.Errorf("failed to allocate page for stack content")
}
st.Registers[29] = sp
st.GetRegisters()[29] = sp
storeMem := func(addr uint32, v uint32) {
var dat [4]byte
binary.BigEndian.PutUint32(dat[:], v)
_ = st.Memory.SetMemoryRange(addr, bytes.NewReader(dat[:]))
_ = st.GetMemory().SetMemoryRange(addr, bytes.NewReader(dat[:]))
}
// init argc, argv, aux on stack
......@@ -117,7 +79,7 @@ func PatchStack(st *State) error {
storeMem(sp+4*7, sp+4*9) // auxv[3] = address of 16 bytes containing random value
storeMem(sp+4*8, 0) // auxv[term] = 0
_ = st.Memory.SetMemoryRange(sp+4*9, bytes.NewReader([]byte("4;byfairdiceroll"))) // 16 bytes of "randomness"
_ = st.GetMemory().SetMemoryRange(sp+4*9, bytes.NewReader([]byte("4;byfairdiceroll"))) // 16 bytes of "randomness"
return nil
}
package mipsevm
package singlethreaded
import (
"errors"
"io"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
"github.com/ethereum-optimism/optimism/op-service/jsonutil"
)
type PreimageOracle interface {
Hint(v []byte)
GetPreimage(k [32]byte) []byte
}
type Debug struct {
stack []uint32
caller []uint32
meta *Metadata
meta *program.Metadata
}
type InstrumentedState struct {
......@@ -41,7 +38,7 @@ type InstrumentedState struct {
debugEnabled bool
}
func NewInstrumentedState(state *State, po PreimageOracle, stdOut, stdErr io.Writer) *InstrumentedState {
func NewInstrumentedState(state *State, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer) *InstrumentedState {
return &InstrumentedState{
state: state,
stdOut: stdOut,
......@@ -50,7 +47,7 @@ func NewInstrumentedState(state *State, po PreimageOracle, stdOut, stdErr io.Wri
}
}
func NewInstrumentedStateFromFile(stateFile string, po PreimageOracle, stdOut, stdErr io.Writer) (*InstrumentedState, error) {
func NewInstrumentedStateFromFile(stateFile string, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer) (*InstrumentedState, error) {
state, err := jsonutil.LoadJSON[State](stateFile)
if err != nil {
return nil, err
......@@ -63,7 +60,7 @@ func NewInstrumentedStateFromFile(stateFile string, po PreimageOracle, stdOut, s
}, nil
}
func (m *InstrumentedState) InitDebug(meta *Metadata) error {
func (m *InstrumentedState) InitDebug(meta *program.Metadata) error {
if meta == nil {
return errors.New("metadata is nil")
}
......@@ -72,7 +69,7 @@ func (m *InstrumentedState) InitDebug(meta *Metadata) error {
return nil
}
func (m *InstrumentedState) Step(proof bool) (wit *StepWitness, err error) {
func (m *InstrumentedState) Step(proof bool) (wit *mipsevm.StepWitness, err error) {
m.memProofEnabled = proof
m.lastMemAccess = ^uint32(0)
m.lastPreimageOffset = ^uint32(0)
......@@ -80,7 +77,7 @@ func (m *InstrumentedState) Step(proof bool) (wit *StepWitness, err error) {
if proof {
insnProof := m.state.Memory.MerkleProof(m.state.Cpu.PC)
encodedWitness, stateHash := m.state.EncodeWitness()
wit = &StepWitness{
wit = &mipsevm.StepWitness{
State: encodedWitness,
StateHash: stateHash,
ProofData: insnProof[:],
......@@ -106,26 +103,20 @@ func (m *InstrumentedState) LastPreimage() ([32]byte, []byte, uint32) {
return m.lastPreimageKey, m.lastPreimage, m.lastPreimageOffset
}
func (m *InstrumentedState) GetState() FPVMState {
func (m *InstrumentedState) GetState() mipsevm.FPVMState {
return m.state
}
func (m *InstrumentedState) GetDebugInfo() *DebugInfo {
return &DebugInfo{
func (m *InstrumentedState) GetDebugInfo() *mipsevm.DebugInfo {
return &mipsevm.DebugInfo{
Pages: m.state.Memory.PageCount(),
NumPreimageRequests: m.preimageOracle.numPreimageRequests,
TotalPreimageSize: m.preimageOracle.totalPreimageSize,
}
}
type DebugInfo struct {
Pages int `json:"pages"`
NumPreimageRequests int `json:"num_preimage_requests"`
TotalPreimageSize int `json:"total_preimage_size"`
}
type trackingOracle struct {
po PreimageOracle
po mipsevm.PreimageOracle
totalPreimageSize int
numPreimageRequests int
}
......
package singlethreaded
import (
"bytes"
"io"
"os"
"path"
"testing"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
)
func TestState(t *testing.T) {
testFiles, err := os.ReadDir("../tests/open_mips_tests/test/bin")
require.NoError(t, err)
for _, f := range testFiles {
t.Run(f.Name(), func(t *testing.T) {
oracle := testutil.SelectOracleFixture(t, f.Name())
// Short-circuit early for exit_group.bin
exitGroup := f.Name() == "exit_group.bin"
// TODO: currently tests are compiled as flat binary objects
// We can use more standard tooling to compile them to ELF files and get remove maketests.py
fn := path.Join("../tests/open_mips_tests/test/bin", f.Name())
//elfProgram, err := elf.Open()
//require.NoError(t, err, "must load test ELF binary")
//state, err := LoadELF(elfProgram)
//require.NoError(t, err, "must load ELF into state")
programMem, err := os.ReadFile(fn)
require.NoError(t, err)
state := &State{Cpu: mipsevm.CpuScalars{PC: 0, NextPC: 4}, Memory: memory.NewMemory()}
err = state.Memory.SetMemoryRange(0, bytes.NewReader(programMem))
require.NoError(t, err, "load program into state")
// set the return address ($ra) to jump into when test completes
state.Registers[31] = testutil.EndAddr
us := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
for i := 0; i < 1000; i++ {
if us.state.Cpu.PC == testutil.EndAddr {
break
}
if exitGroup && us.state.Exited {
break
}
_, err := us.Step(false)
require.NoError(t, err)
}
if exitGroup {
require.NotEqual(t, uint32(testutil.EndAddr), us.state.Cpu.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")
} else {
require.Equal(t, uint32(testutil.EndAddr), us.state.Cpu.PC, "must reach end")
done, result := state.Memory.GetMemory(testutil.BaseAddrEnd+4), state.Memory.GetMemory(testutil.BaseAddrEnd+8)
// inspect test result
require.Equal(t, done, uint32(1), "must be done")
require.Equal(t, result, uint32(1), "must have success result")
}
})
}
}
func TestHello(t *testing.T) {
state := testutil.LoadELFProgram(t, "../../example/bin/hello.elf", CreateInitialState)
var stdOutBuf, stdErrBuf bytes.Buffer
us := NewInstrumentedState(state, nil, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr))
for i := 0; i < 400_000; i++ {
if us.state.Exited {
break
}
_, err := us.Step(false)
require.NoError(t, err)
}
require.True(t, state.Exited, "must complete program")
require.Equal(t, uint8(0), state.ExitCode, "exit with 0")
require.Equal(t, "hello world!\n", stdOutBuf.String(), "stdout says hello")
require.Equal(t, "", stdErrBuf.String(), "stderr silent")
}
func TestClaim(t *testing.T) {
state := testutil.LoadELFProgram(t, "../../example/bin/claim.elf", CreateInitialState)
oracle, expectedStdOut, expectedStdErr := testutil.ClaimTestOracle(t)
var stdOutBuf, stdErrBuf bytes.Buffer
us := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr))
for i := 0; i < 2000_000; i++ {
if us.GetState().GetExited() {
break
}
_, err := us.Step(false)
require.NoError(t, err)
}
require.True(t, state.Exited, "must complete program")
require.Equal(t, uint8(0), state.ExitCode, "exit with 0")
require.Equal(t, expectedStdOut, stdOutBuf.String(), "stdout")
require.Equal(t, expectedStdErr, stdErrBuf.String(), "stderr")
}
func TestAlloc(t *testing.T) {
t.Skip("TODO(client-pod#906): Currently fails on Single threaded Cannon. Re-enable for the MT FPVM")
state := testutil.LoadELFProgram(t, "../example/bin/alloc.elf", CreateInitialState)
const numAllocs = 100 // where each alloc is a 32 MiB chunk
oracle := testutil.AllocOracle(t, numAllocs)
// completes in ~870 M steps
us := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
for i := 0; i < 20_000_000_000; i++ {
if us.GetState().GetExited() {
break
}
_, err := us.Step(false)
require.NoError(t, err)
if state.Step%10_000_000 == 0 {
t.Logf("Completed %d steps", state.Step)
}
}
t.Logf("Completed in %d steps", state.Step)
require.True(t, state.Exited, "must complete program")
require.Equal(t, uint8(0), state.ExitCode, "exit with 0")
require.Less(t, state.Memory.PageCount()*memory.PageSize, 1*1024*1024*1024, "must not allocate more than 1 GiB")
}
package mipsevm
package singlethreaded
import (
"encoding/binary"
......@@ -6,9 +6,9 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
type MemTracker func(addr uint32)
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
)
func (m *InstrumentedState) readPreimage(key [32]byte, offset uint32) (dat [32]byte, datLen uint32) {
preimage := m.lastPreimage
......@@ -37,46 +37,46 @@ func (m *InstrumentedState) trackMemAccess(effAddr uint32) {
}
func (m *InstrumentedState) handleSyscall() error {
syscallNum, a0, a1, a2 := getSyscallArgs(&m.state.Registers)
syscallNum, a0, a1, a2 := exec.GetSyscallArgs(&m.state.Registers)
v0 := uint32(0)
v1 := uint32(0)
//fmt.Printf("syscall: %d\n", syscallNum)
switch syscallNum {
case sysMmap:
case exec.SysMmap:
var newHeap uint32
v0, v1, newHeap = handleSysMmap(a0, a1, m.state.Heap)
v0, v1, newHeap = exec.HandleSysMmap(a0, a1, m.state.Heap)
m.state.Heap = newHeap
case sysBrk:
case exec.SysBrk:
v0 = 0x40000000
case sysClone: // clone (not supported)
case exec.SysClone: // clone (not supported)
v0 = 1
case sysExitGroup:
case exec.SysExitGroup:
m.state.Exited = true
m.state.ExitCode = uint8(a0)
return nil
case sysRead:
case exec.SysRead:
var newPreimageOffset uint32
v0, v1, newPreimageOffset = handleSysRead(a0, a1, a2, m.state.PreimageKey, m.state.PreimageOffset, m.readPreimage, m.state.Memory, m.trackMemAccess)
v0, v1, newPreimageOffset = exec.HandleSysRead(a0, a1, a2, m.state.PreimageKey, m.state.PreimageOffset, m.readPreimage, m.state.Memory, m.trackMemAccess)
m.state.PreimageOffset = newPreimageOffset
case sysWrite:
case exec.SysWrite:
var newLastHint hexutil.Bytes
var newPreimageKey common.Hash
var newPreimageOffset uint32
v0, v1, newLastHint, newPreimageKey, newPreimageOffset = handleSysWrite(a0, a1, a2, m.state.LastHint, m.state.PreimageKey, m.state.PreimageOffset, m.preimageOracle, m.state.Memory, m.trackMemAccess, m.stdOut, m.stdErr)
v0, v1, newLastHint, newPreimageKey, newPreimageOffset = exec.HandleSysWrite(a0, a1, a2, m.state.LastHint, m.state.PreimageKey, m.state.PreimageOffset, m.preimageOracle, m.state.Memory, m.trackMemAccess, m.stdOut, m.stdErr)
m.state.LastHint = newLastHint
m.state.PreimageKey = newPreimageKey
m.state.PreimageOffset = newPreimageOffset
case sysFcntl:
v0, v1 = handleSysFcntl(a0, a1)
case exec.SysFcntl:
v0, v1 = exec.HandleSysFcntl(a0, a1)
}
handleSyscallUpdates(&m.state.Cpu, &m.state.Registers, v0, v1)
exec.HandleSyscallUpdates(&m.state.Cpu, &m.state.Registers, v0, v1)
return nil
}
func (m *InstrumentedState) pushStack(target uint32) {
func (m *InstrumentedState) PushStack(target uint32) {
if !m.debugEnabled {
return
}
......@@ -84,7 +84,7 @@ func (m *InstrumentedState) pushStack(target uint32) {
m.debug.caller = append(m.debug.caller, m.state.Cpu.PC)
}
func (m *InstrumentedState) popStack() {
func (m *InstrumentedState) PopStack() {
if !m.debugEnabled {
return
}
......@@ -125,7 +125,7 @@ func (m *InstrumentedState) mipsStep() error {
}
m.state.Step += 1
// instruction fetch
insn, opcode, fun := getInstructionDetails(m.state.Cpu.PC, m.state.Memory)
insn, opcode, fun := exec.GetInstructionDetails(m.state.Cpu.PC, m.state.Memory)
// Handle syscall separately
// syscall (can read and write)
......@@ -134,5 +134,5 @@ func (m *InstrumentedState) mipsStep() error {
}
// Exec the rest of the step logic
return execMipsCoreStepLogic(&m.state.Cpu, &m.state.Registers, m.state.Memory, insn, opcode, fun, m.trackMemAccess, m)
return exec.ExecMipsCoreStepLogic(&m.state.Cpu, &m.state.Registers, m.state.Memory, insn, opcode, fun, m.trackMemAccess, m)
}
package singlethreaded
import (
"encoding/binary"
"encoding/json"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
)
// STATE_WITNESS_SIZE is the size of the state witness encoding in bytes.
const STATE_WITNESS_SIZE = 226
type State struct {
Memory *memory.Memory `json:"memory"`
PreimageKey common.Hash `json:"preimageKey"`
PreimageOffset uint32 `json:"preimageOffset"` // note that the offset includes the 8-byte length prefix
Cpu mipsevm.CpuScalars `json:"cpu"`
Heap uint32 `json:"heap"` // to handle mmap growth
ExitCode uint8 `json:"exit"`
Exited bool `json:"exited"`
Step uint64 `json:"step"`
Registers [32]uint32 `json:"registers"`
// LastHint is optional metadata, and not part of the VM state itself.
// It is used to remember the last pre-image hint,
// 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.
// 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:])
LastHint hexutil.Bytes `json:"lastHint,omitempty"`
}
func CreateEmptyState() *State {
return &State{
Cpu: mipsevm.CpuScalars{
PC: 0,
NextPC: 0,
LO: 0,
HI: 0,
},
Heap: 0,
Registers: [32]uint32{},
Memory: memory.NewMemory(),
ExitCode: 0,
Exited: false,
Step: 0,
}
}
func CreateInitialState(pc, heapStart uint32) *State {
state := CreateEmptyState()
state.Cpu.PC = pc
state.Cpu.NextPC = pc + 4
state.Heap = heapStart
return state
}
type stateMarshaling struct {
Memory *memory.Memory `json:"memory"`
PreimageKey common.Hash `json:"preimageKey"`
PreimageOffset uint32 `json:"preimageOffset"`
PC uint32 `json:"pc"`
NextPC uint32 `json:"nextPC"`
LO uint32 `json:"lo"`
HI uint32 `json:"hi"`
Heap uint32 `json:"heap"`
ExitCode uint8 `json:"exit"`
Exited bool `json:"exited"`
Step uint64 `json:"step"`
Registers [32]uint32 `json:"registers"`
LastHint hexutil.Bytes `json:"lastHint,omitempty"`
}
func (s *State) MarshalJSON() ([]byte, error) { // nosemgrep
sm := &stateMarshaling{
Memory: s.Memory,
PreimageKey: s.PreimageKey,
PreimageOffset: s.PreimageOffset,
PC: s.Cpu.PC,
NextPC: s.Cpu.NextPC,
LO: s.Cpu.LO,
HI: s.Cpu.HI,
Heap: s.Heap,
ExitCode: s.ExitCode,
Exited: s.Exited,
Step: s.Step,
Registers: s.Registers,
LastHint: s.LastHint,
}
return json.Marshal(sm)
}
func (s *State) UnmarshalJSON(data []byte) error {
sm := new(stateMarshaling)
if err := json.Unmarshal(data, sm); err != nil {
return err
}
s.Memory = sm.Memory
s.PreimageKey = sm.PreimageKey
s.PreimageOffset = sm.PreimageOffset
s.Cpu.PC = sm.PC
s.Cpu.NextPC = sm.NextPC
s.Cpu.LO = sm.LO
s.Cpu.HI = sm.HI
s.Heap = sm.Heap
s.ExitCode = sm.ExitCode
s.Exited = sm.Exited
s.Step = sm.Step
s.Registers = sm.Registers
s.LastHint = sm.LastHint
return nil
}
func (s *State) GetPC() uint32 { return s.Cpu.PC }
func (s *State) GetRegisters() *[32]uint32 { return &s.Registers }
func (s *State) GetExitCode() uint8 { return s.ExitCode }
func (s *State) GetExited() bool { return s.Exited }
func (s *State) GetStep() uint64 { return s.Step }
func (s *State) VMStatus() uint8 {
return mipsevm.VmStatus(s.Exited, s.ExitCode)
}
func (s *State) GetMemory() *memory.Memory {
return s.Memory
}
func (s *State) EncodeWitness() ([]byte, common.Hash) {
out := make([]byte, 0, STATE_WITNESS_SIZE)
memRoot := s.Memory.MerkleRoot()
out = append(out, memRoot[:]...)
out = append(out, s.PreimageKey[:]...)
out = binary.BigEndian.AppendUint32(out, s.PreimageOffset)
out = binary.BigEndian.AppendUint32(out, s.Cpu.PC)
out = binary.BigEndian.AppendUint32(out, s.Cpu.NextPC)
out = binary.BigEndian.AppendUint32(out, s.Cpu.LO)
out = binary.BigEndian.AppendUint32(out, s.Cpu.HI)
out = binary.BigEndian.AppendUint32(out, s.Heap)
out = append(out, s.ExitCode)
out = mipsevm.AppendBoolToWitness(out, s.Exited)
out = binary.BigEndian.AppendUint64(out, s.Step)
for _, r := range s.Registers {
out = binary.BigEndian.AppendUint32(out, r)
}
return out, stateHashFromWitness(out)
}
type StateWitness []byte
func (sw StateWitness) StateHash() (common.Hash, error) {
if len(sw) != STATE_WITNESS_SIZE {
return common.Hash{}, fmt.Errorf("Invalid witness length. Got %d, expected %d", len(sw), STATE_WITNESS_SIZE)
}
return stateHashFromWitness(sw), nil
}
func GetStateHashFn() mipsevm.HashFn {
return func(sw []byte) (common.Hash, error) {
return StateWitness(sw).StateHash()
}
}
func stateHashFromWitness(sw []byte) common.Hash {
if len(sw) != STATE_WITNESS_SIZE {
panic("Invalid witness length")
}
hash := crypto.Keccak256Hash(sw)
offset := 32*2 + 4*6
exitCode := sw[offset]
exited := sw[offset+1]
status := mipsevm.VmStatus(exited == 1, exitCode)
hash[0] = status
return hash
}
package singlethreaded
import (
"debug/elf"
"testing"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
)
// Run through all permutations of `exited` / `exitCode` and ensure that the
// correct witness, state hash, and VM Status is produced.
func TestStateHash(t *testing.T) {
cases := []struct {
exited bool
exitCode uint8
}{
{exited: false, exitCode: 0},
{exited: false, exitCode: 1},
{exited: false, exitCode: 2},
{exited: false, exitCode: 3},
{exited: true, exitCode: 0},
{exited: true, exitCode: 1},
{exited: true, exitCode: 2},
{exited: true, exitCode: 3},
}
exitedOffset := 32*2 + 4*6
for _, c := range cases {
state := &State{
Memory: memory.NewMemory(),
Exited: c.exited,
ExitCode: c.exitCode,
}
actualWitness, actualStateHash := state.EncodeWitness()
require.Equal(t, len(actualWitness), STATE_WITNESS_SIZE, "Incorrect witness size")
expectedWitness := make(StateWitness, 226)
memRoot := state.Memory.MerkleRoot()
copy(expectedWitness[:32], memRoot[:])
expectedWitness[exitedOffset] = c.exitCode
var exited uint8
if c.exited {
exited = 1
}
expectedWitness[exitedOffset+1] = uint8(exited)
require.EqualValues(t, expectedWitness[:], actualWitness[:], "Incorrect witness")
expectedStateHash := crypto.Keccak256Hash(actualWitness)
expectedStateHash[0] = mipsevm.VmStatus(c.exited, c.exitCode)
require.Equal(t, expectedStateHash, actualStateHash, "Incorrect state hash")
}
}
func TestStateJSONCodec(t *testing.T) {
elfProgram, err := elf.Open("../../example/bin/hello.elf")
require.NoError(t, err, "open ELF file")
state, err := program.LoadELF(elfProgram, CreateInitialState)
require.NoError(t, err, "load ELF into state")
stateJSON, err := state.MarshalJSON()
require.NoError(t, err)
newState := new(State)
require.NoError(t, newState.UnmarshalJSON(stateJSON))
require.Equal(t, state.PreimageKey, newState.PreimageKey)
require.Equal(t, state.PreimageOffset, newState.PreimageOffset)
require.Equal(t, state.Cpu, newState.Cpu)
require.Equal(t, state.Heap, newState.Heap)
require.Equal(t, state.ExitCode, newState.ExitCode)
require.Equal(t, state.Exited, newState.Exited)
require.Equal(t, state.Memory.MerkleRoot(), newState.Memory.MerkleRoot())
require.Equal(t, state.Registers, newState.Registers)
require.Equal(t, state.Step, newState.Step)
}
package mipsevm
import (
"encoding/binary"
"encoding/json"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
)
// STATE_WITNESS_SIZE is the size of the state witness encoding in bytes.
const STATE_WITNESS_SIZE = 226
type CpuScalars struct {
PC uint32 `json:"pc"`
NextPC uint32 `json:"nextPC"`
......@@ -20,154 +7,6 @@ type CpuScalars struct {
HI uint32 `json:"hi"`
}
type State struct {
Memory *Memory `json:"memory"`
PreimageKey common.Hash `json:"preimageKey"`
PreimageOffset uint32 `json:"preimageOffset"` // note that the offset includes the 8-byte length prefix
Cpu CpuScalars `json:"cpu"`
Heap uint32 `json:"heap"` // to handle mmap growth
ExitCode uint8 `json:"exit"`
Exited bool `json:"exited"`
Step uint64 `json:"step"`
Registers [32]uint32 `json:"registers"`
// LastHint is optional metadata, and not part of the VM state itself.
// It is used to remember the last pre-image hint,
// 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.
// 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:])
LastHint hexutil.Bytes `json:"lastHint,omitempty"`
}
func CreateEmptyState() *State {
return &State{
Cpu: CpuScalars{
PC: 0,
NextPC: 0,
LO: 0,
HI: 0,
},
Heap: 0,
Registers: [32]uint32{},
Memory: NewMemory(),
ExitCode: 0,
Exited: false,
Step: 0,
}
}
func CreateInitialState(pc, heapStart uint32) *State {
state := CreateEmptyState()
state.Cpu.PC = pc
state.Cpu.NextPC = pc + 4
state.Heap = heapStart
return state
}
type stateMarshaling struct {
Memory *Memory `json:"memory"`
PreimageKey common.Hash `json:"preimageKey"`
PreimageOffset uint32 `json:"preimageOffset"`
PC uint32 `json:"pc"`
NextPC uint32 `json:"nextPC"`
LO uint32 `json:"lo"`
HI uint32 `json:"hi"`
Heap uint32 `json:"heap"`
ExitCode uint8 `json:"exit"`
Exited bool `json:"exited"`
Step uint64 `json:"step"`
Registers [32]uint32 `json:"registers"`
LastHint hexutil.Bytes `json:"lastHint,omitempty"`
}
func (s *State) MarshalJSON() ([]byte, error) { // nosemgrep
sm := &stateMarshaling{
Memory: s.Memory,
PreimageKey: s.PreimageKey,
PreimageOffset: s.PreimageOffset,
PC: s.Cpu.PC,
NextPC: s.Cpu.NextPC,
LO: s.Cpu.LO,
HI: s.Cpu.HI,
Heap: s.Heap,
ExitCode: s.ExitCode,
Exited: s.Exited,
Step: s.Step,
Registers: s.Registers,
LastHint: s.LastHint,
}
return json.Marshal(sm)
}
func (s *State) UnmarshalJSON(data []byte) error {
sm := new(stateMarshaling)
if err := json.Unmarshal(data, sm); err != nil {
return err
}
s.Memory = sm.Memory
s.PreimageKey = sm.PreimageKey
s.PreimageOffset = sm.PreimageOffset
s.Cpu.PC = sm.PC
s.Cpu.NextPC = sm.NextPC
s.Cpu.LO = sm.LO
s.Cpu.HI = sm.HI
s.Heap = sm.Heap
s.ExitCode = sm.ExitCode
s.Exited = sm.Exited
s.Step = sm.Step
s.Registers = sm.Registers
s.LastHint = sm.LastHint
return nil
}
func (s *State) GetPC() uint32 { return s.Cpu.PC }
func (s *State) GetExitCode() uint8 { return s.ExitCode }
func (s *State) GetExited() bool { return s.Exited }
func (s *State) GetStep() uint64 { return s.Step }
func (s *State) VMStatus() uint8 {
return vmStatus(s.Exited, s.ExitCode)
}
func (s *State) GetMemory() *Memory {
return s.Memory
}
func (s *State) EncodeWitness() ([]byte, common.Hash) {
out := make([]byte, 0, STATE_WITNESS_SIZE)
memRoot := s.Memory.MerkleRoot()
out = append(out, memRoot[:]...)
out = append(out, s.PreimageKey[:]...)
out = binary.BigEndian.AppendUint32(out, s.PreimageOffset)
out = binary.BigEndian.AppendUint32(out, s.Cpu.PC)
out = binary.BigEndian.AppendUint32(out, s.Cpu.NextPC)
out = binary.BigEndian.AppendUint32(out, s.Cpu.LO)
out = binary.BigEndian.AppendUint32(out, s.Cpu.HI)
out = binary.BigEndian.AppendUint32(out, s.Heap)
out = append(out, s.ExitCode)
out = AppendBoolToWitness(out, s.Exited)
out = binary.BigEndian.AppendUint64(out, s.Step)
for _, r := range s.Registers {
out = binary.BigEndian.AppendUint32(out, r)
}
return out, stateHashFromWitness(out)
}
type StateWitness []byte
const (
VMStatusValid = 0
VMStatusInvalid = 1
......@@ -175,27 +14,7 @@ const (
VMStatusUnfinished = 3
)
func (sw StateWitness) StateHash() (common.Hash, error) {
if len(sw) != STATE_WITNESS_SIZE {
return common.Hash{}, fmt.Errorf("Invalid witness length. Got %d, expected %d", len(sw), STATE_WITNESS_SIZE)
}
return stateHashFromWitness(sw), nil
}
func stateHashFromWitness(sw []byte) common.Hash {
if len(sw) != STATE_WITNESS_SIZE {
panic("Invalid witness length")
}
hash := crypto.Keccak256Hash(sw)
offset := 32*2 + 4*6
exitCode := sw[offset]
exited := sw[offset+1]
status := vmStatus(exited == 1, exitCode)
hash[0] = status
return hash
}
func vmStatus(exited bool, exitCode uint8) uint8 {
func VmStatus(exited bool, exitCode uint8) uint8 {
if !exited {
return VMStatusUnfinished
}
......
package mipsevm
package tests
import (
"bytes"
"debug/elf"
"errors"
"fmt"
"io"
"math"
"math/big"
"os"
"path"
"testing"
"time"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
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/core/state"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
)
func testContractsSetup(t require.TestingT) (*Artifacts, *Addresses) {
artifacts, err := LoadArtifacts()
func testContractsSetup(t require.TestingT) (*testutil.Artifacts, *testutil.Addresses) {
artifacts, err := testutil.LoadArtifacts()
require.NoError(t, err)
addrs := &Addresses{
addrs := &testutil.Addresses{
MIPS: common.Address{0: 0xff, 19: 1},
Oracle: common.Address{0: 0xff, 19: 2},
Sender: common.Address{0x13, 0x37},
......@@ -37,173 +36,42 @@ func testContractsSetup(t require.TestingT) (*Artifacts, *Addresses) {
return artifacts, addrs
}
func MarkdownTracer() vm.EVMLogger {
return logger.NewMarkdownLogger(&logger.Config{}, os.Stdout)
}
func logStepFailureAtCleanup(t *testing.T, mipsEvm *MIPSEVM) {
t.Cleanup(func() {
if t.Failed() {
// Note: For easier debugging of a failing step, see MIPS.t.sol#test_step_debug_succeeds()
t.Logf("Failed while executing step %d with input: %x", mipsEvm.lastStep, mipsEvm.lastStepInput)
}
})
}
type MIPSEVM struct {
env *vm.EVM
evmState *state.StateDB
addrs *Addresses
localOracle PreimageOracle
artifacts *Artifacts
// Track step execution for logging purposes
lastStep uint64
lastStepInput []byte
}
func NewMIPSEVM(artifacts *Artifacts, addrs *Addresses) *MIPSEVM {
env, evmState := NewEVMEnv(artifacts, addrs)
return &MIPSEVM{env, evmState, addrs, nil, artifacts, math.MaxUint64, nil}
}
func (m *MIPSEVM) SetTracer(tracer vm.EVMLogger) {
m.env.Config.Tracer = tracer
}
func (m *MIPSEVM) SetLocalOracle(oracle PreimageOracle) {
m.localOracle = oracle
}
// 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, step uint64) []byte {
m.lastStep = step
m.lastStepInput = nil
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 := encodePreimageOracleInput(t, stepWitness, LocalContext{}, m.localOracle, m.artifacts.Oracle)
require.NoError(t, err, "encode preimage oracle input")
_, leftOverGas, err := m.env.Call(vm.AccountRef(sender), m.addrs.Oracle, poInput, startingGas, common.U2560)
require.NoErrorf(t, err, "evm should not fail, took %d gas", startingGas-leftOverGas)
}
input := encodeStepInput(t, stepWitness, LocalContext{}, m.artifacts.MIPS)
m.lastStepInput = input
ret, leftOverGas, err := m.env.Call(vm.AccountRef(sender), m.addrs.MIPS, input, startingGas, common.U2560)
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
stateHash, err := StateWitness(evmPost).StateHash()
require.NoError(t, err, "state hash could not be computed")
require.Equal(t, stateHash, postHash, "logged state must be accurate")
m.env.StateDB.RevertToSnapshot(snap)
t.Logf("EVM step %d took %d gas, and returned stateHash %s", step, startingGas-leftOverGas, postHash)
return evmPost
}
func encodeStepInput(t *testing.T, wit *StepWitness, localContext LocalContext, mips *foundry.Artifact) []byte {
input, err := mips.ABI.Pack("step", wit.State, wit.ProofData, localContext)
require.NoError(t, err)
return input
}
func encodePreimageOracleInput(t *testing.T, wit *StepWitness, localContext LocalContext, localOracle PreimageOracle, oracle *foundry.Artifact) ([]byte, error) {
if wit.PreimageKey == ([32]byte{}) {
return nil, errors.New("cannot encode pre-image oracle input, witness has no pre-image to proof")
}
switch preimage.KeyType(wit.PreimageKey[0]) {
case preimage.LocalKeyType:
if len(wit.PreimageValue) > 32+8 {
return nil, fmt.Errorf("local pre-image exceeds maximum size of 32 bytes with key 0x%x", wit.PreimageKey)
}
preimagePart := wit.PreimageValue[8:]
var tmp [32]byte
copy(tmp[:], preimagePart)
input, err := oracle.ABI.Pack("loadLocalData",
new(big.Int).SetBytes(wit.PreimageKey[1:]),
localContext,
tmp,
new(big.Int).SetUint64(uint64(len(preimagePart))),
new(big.Int).SetUint64(uint64(wit.PreimageOffset)),
)
require.NoError(t, err)
return input, nil
case preimage.Keccak256KeyType:
input, err := oracle.ABI.Pack(
"loadKeccak256PreimagePart",
new(big.Int).SetUint64(uint64(wit.PreimageOffset)),
wit.PreimageValue[8:])
require.NoError(t, err)
return input, nil
case preimage.PrecompileKeyType:
if localOracle == nil {
return nil, fmt.Errorf("local oracle is required for precompile preimages")
}
preimage := localOracle.GetPreimage(preimage.Keccak256Key(wit.PreimageKey).PreimageKey())
precompile := common.BytesToAddress(preimage[:20])
callInput := preimage[20:]
input, err := oracle.ABI.Pack(
"loadPrecompilePreimagePart",
new(big.Int).SetUint64(uint64(wit.PreimageOffset)),
precompile,
callInput,
)
require.NoError(t, err)
return input, nil
default:
return nil, fmt.Errorf("unsupported pre-image type %d, cannot prepare preimage with key %x offset %d for oracle",
wit.PreimageKey[0], wit.PreimageKey, wit.PreimageOffset)
}
}
func TestEVM(t *testing.T) {
testFiles, err := os.ReadDir("open_mips_tests/test/bin")
require.NoError(t, err)
contracts, addrs := testContractsSetup(t)
var tracer vm.EVMLogger // no-tracer by default, but MarkdownTracer
var tracer vm.EVMLogger // no-tracer by default, but test_util.MarkdownTracer
for _, f := range testFiles {
t.Run(f.Name(), func(t *testing.T) {
oracle := selectOracleFixture(t, f.Name())
oracle := testutil.SelectOracleFixture(t, f.Name())
// Short-circuit early for exit_group.bin
exitGroup := f.Name() == "exit_group.bin"
evm := NewMIPSEVM(contracts, addrs)
evm := testutil.NewMIPSEVM(contracts, addrs)
evm.SetTracer(tracer)
evm.SetLocalOracle(oracle)
logStepFailureAtCleanup(t, evm)
testutil.LogStepFailureAtCleanup(t, evm)
fn := path.Join("open_mips_tests/test/bin", f.Name())
programMem, err := os.ReadFile(fn)
require.NoError(t, err)
state := &State{Cpu: CpuScalars{PC: 0, NextPC: 4}, Memory: NewMemory()}
state := &singlethreaded.State{Cpu: mipsevm.CpuScalars{PC: 0, NextPC: 4}, Memory: memory.NewMemory()}
err = state.Memory.SetMemoryRange(0, bytes.NewReader(programMem))
require.NoError(t, err, "load program into state")
// set the return address ($ra) to jump into when test completes
state.Registers[31] = endAddr
state.Registers[31] = testutil.EndAddr
goState := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
goState := singlethreaded.NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
for i := 0; i < 1000; i++ {
curStep := goState.state.Step
if goState.state.Cpu.PC == endAddr {
curStep := goState.GetState().GetStep()
if goState.GetState().GetPC() == testutil.EndAddr {
break
}
if exitGroup && goState.state.Exited {
if exitGroup && goState.GetState().GetExited() {
break
}
insn := state.Memory.GetMemory(state.Cpu.PC)
......@@ -211,21 +79,21 @@ func TestEVM(t *testing.T) {
stepWitness, err := goState.Step(true)
require.NoError(t, err)
evmPost := evm.Step(t, stepWitness, curStep)
evmPost := evm.Step(t, stepWitness, curStep, singlethreaded.GetStateHashFn())
// verify the post-state matches.
// TODO: maybe more readable to decode the evmPost state, and do attribute-wise comparison.
goPost, _ := goState.state.EncodeWitness()
goPost, _ := goState.GetState().EncodeWitness()
require.Equalf(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM at step %d", state.Step)
}
if exitGroup {
require.NotEqual(t, uint32(endAddr), goState.state.Cpu.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")
require.NotEqual(t, uint32(testutil.EndAddr), goState.GetState().GetPC(), "must not reach end")
require.True(t, goState.GetState().GetExited(), "must set exited state")
require.Equal(t, uint8(1), goState.GetState().GetExitCode(), "must exit with 1")
} else {
require.Equal(t, uint32(endAddr), state.Cpu.PC, "must reach end")
require.Equal(t, uint32(testutil.EndAddr), state.Cpu.PC, "must reach end")
// inspect test result
done, result := state.Memory.GetMemory(baseAddrEnd+4), state.Memory.GetMemory(baseAddrEnd+8)
done, result := state.Memory.GetMemory(testutil.BaseAddrEnd+4), state.Memory.GetMemory(testutil.BaseAddrEnd+8)
require.Equal(t, done, uint32(1), "must be done")
require.Equal(t, result, uint32(1), "must have success result")
}
......@@ -251,20 +119,20 @@ func TestEVMSingleStep(t *testing.T) {
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
state := &State{Cpu: CpuScalars{PC: tt.pc, NextPC: tt.nextPC}, Memory: NewMemory()}
state := &singlethreaded.State{Cpu: mipsevm.CpuScalars{PC: tt.pc, NextPC: tt.nextPC}, Memory: memory.NewMemory()}
state.Memory.SetMemory(tt.pc, tt.insn)
curStep := state.Step
us := NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
us := singlethreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
stepWitness, err := us.Step(true)
require.NoError(t, err)
evm := NewMIPSEVM(contracts, addrs)
evm := testutil.NewMIPSEVM(contracts, addrs)
evm.SetTracer(tracer)
logStepFailureAtCleanup(t, evm)
testutil.LogStepFailureAtCleanup(t, evm)
evmPost := evm.Step(t, stepWitness, curStep)
goPost, _ := us.state.EncodeWitness()
evmPost := evm.Step(t, stepWitness, curStep, singlethreaded.GetStateHashFn())
goPost, _ := us.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
......@@ -422,11 +290,11 @@ func TestEVMSysWriteHint(t *testing.T) {
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
oracle := hintTrackingOracle{}
state := &State{Cpu: CpuScalars{PC: 0, NextPC: 4}, Memory: NewMemory()}
state := &singlethreaded.State{Cpu: mipsevm.CpuScalars{PC: 0, NextPC: 4}, Memory: memory.NewMemory()}
state.LastHint = tt.lastHint
state.Registers[2] = sysWrite
state.Registers[4] = fdHintWrite
state.Registers[2] = exec.SysWrite
state.Registers[4] = exec.FdHintWrite
state.Registers[5] = uint32(tt.memOffset)
state.Registers[6] = uint32(tt.bytesToWrite)
......@@ -435,17 +303,17 @@ func TestEVMSysWriteHint(t *testing.T) {
state.Memory.SetMemory(0, insn)
curStep := state.Step
us := NewInstrumentedState(state, &oracle, os.Stdout, os.Stderr)
us := singlethreaded.NewInstrumentedState(state, &oracle, os.Stdout, os.Stderr)
stepWitness, err := us.Step(true)
require.NoError(t, err)
require.Equal(t, tt.expectedHints, oracle.hints)
evm := NewMIPSEVM(contracts, addrs)
evm := testutil.NewMIPSEVM(contracts, addrs)
evm.SetTracer(tracer)
logStepFailureAtCleanup(t, evm)
testutil.LogStepFailureAtCleanup(t, evm)
evmPost := evm.Step(t, stepWitness, curStep)
goPost, _ := us.state.EncodeWitness()
evmPost := evm.Step(t, stepWitness, curStep, singlethreaded.GetStateHashFn())
goPost, _ := us.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
......@@ -454,10 +322,10 @@ func TestEVMSysWriteHint(t *testing.T) {
func TestEVMFault(t *testing.T) {
contracts, addrs := testContractsSetup(t)
var tracer vm.EVMLogger // no-tracer by default, but see MarkdownTracer
var tracer vm.EVMLogger // no-tracer by default, but see test_util.MarkdownTracer
sender := common.Address{0x13, 0x37}
env, evmState := NewEVMEnv(contracts, addrs)
env, evmState := testutil.NewEVMEnv(contracts, addrs)
env.Config.Tracer = tracer
cases := []struct {
......@@ -472,23 +340,23 @@ func TestEVMFault(t *testing.T) {
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
state := &State{Cpu: CpuScalars{PC: 0, NextPC: tt.nextPC}, Memory: NewMemory()}
initialState := &State{Cpu: CpuScalars{PC: 0, NextPC: tt.nextPC}, Memory: state.Memory}
state := &singlethreaded.State{Cpu: mipsevm.CpuScalars{PC: 0, NextPC: tt.nextPC}, Memory: memory.NewMemory()}
initialState := &singlethreaded.State{Cpu: mipsevm.CpuScalars{PC: 0, NextPC: tt.nextPC}, Memory: state.Memory}
state.Memory.SetMemory(0, tt.insn)
// set the return address ($ra) to jump into when test completes
state.Registers[31] = endAddr
state.Registers[31] = testutil.EndAddr
us := NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
us := singlethreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
require.Panics(t, func() { _, _ = us.Step(true) })
insnProof := initialState.Memory.MerkleProof(0)
encodedWitness, _ := initialState.EncodeWitness()
stepWitness := &StepWitness{
stepWitness := &mipsevm.StepWitness{
State: encodedWitness,
ProofData: insnProof[:],
}
input := encodeStepInput(t, stepWitness, LocalContext{}, contracts.MIPS)
input := testutil.EncodeStepInput(t, stepWitness, mipsevm.LocalContext{}, contracts.MIPS)
startingGas := uint64(30_000_000)
_, _, err := env.Call(vm.AccountRef(sender), addrs.MIPS, input, startingGas, common.U2560)
......@@ -501,28 +369,28 @@ func TestEVMFault(t *testing.T) {
func TestHelloEVM(t *testing.T) {
contracts, addrs := testContractsSetup(t)
var tracer vm.EVMLogger // no-tracer by default, but see MarkdownTracer
evm := NewMIPSEVM(contracts, addrs)
var tracer vm.EVMLogger // no-tracer by default, but see test_util.MarkdownTracer
evm := testutil.NewMIPSEVM(contracts, addrs)
evm.SetTracer(tracer)
logStepFailureAtCleanup(t, evm)
testutil.LogStepFailureAtCleanup(t, evm)
elfProgram, err := elf.Open("../example/bin/hello.elf")
elfProgram, err := elf.Open("../../example/bin/hello.elf")
require.NoError(t, err, "open ELF file")
state, err := LoadELF(elfProgram, CreateInitialState)
state, err := program.LoadELF(elfProgram, singlethreaded.CreateInitialState)
require.NoError(t, err, "load ELF into state")
err = PatchGo(elfProgram, state)
err = program.PatchGo(elfProgram, state)
require.NoError(t, err, "apply Go runtime patches")
require.NoError(t, PatchStack(state), "add initial stack")
require.NoError(t, program.PatchStack(state), "add initial stack")
var stdOutBuf, stdErrBuf bytes.Buffer
goState := NewInstrumentedState(state, nil, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr))
goState := singlethreaded.NewInstrumentedState(state, nil, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr))
start := time.Now()
for i := 0; i < 400_000; i++ {
curStep := goState.state.Step
if goState.state.Exited {
curStep := goState.GetState().GetStep()
if goState.GetState().GetExited() {
break
}
insn := state.Memory.GetMemory(state.Cpu.PC)
......@@ -532,10 +400,10 @@ func TestHelloEVM(t *testing.T) {
stepWitness, err := goState.Step(true)
require.NoError(t, err)
evmPost := evm.Step(t, stepWitness, curStep)
evmPost := evm.Step(t, stepWitness, curStep, singlethreaded.GetStateHashFn())
// verify the post-state matches.
// TODO: maybe more readable to decode the evmPost state, and do attribute-wise comparison.
goPost, _ := goState.state.EncodeWitness()
goPost, _ := goState.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
}
......@@ -552,29 +420,29 @@ func TestHelloEVM(t *testing.T) {
func TestClaimEVM(t *testing.T) {
contracts, addrs := testContractsSetup(t)
var tracer vm.EVMLogger // no-tracer by default, but see MarkdownTracer
evm := NewMIPSEVM(contracts, addrs)
var tracer vm.EVMLogger // no-tracer by default, but see test_util.MarkdownTracer
evm := testutil.NewMIPSEVM(contracts, addrs)
evm.SetTracer(tracer)
logStepFailureAtCleanup(t, evm)
testutil.LogStepFailureAtCleanup(t, evm)
elfProgram, err := elf.Open("../example/bin/claim.elf")
elfProgram, err := elf.Open("../../example/bin/claim.elf")
require.NoError(t, err, "open ELF file")
state, err := LoadELF(elfProgram, CreateInitialState)
state, err := program.LoadELF(elfProgram, singlethreaded.CreateInitialState)
require.NoError(t, err, "load ELF into state")
err = PatchGo(elfProgram, state)
err = program.PatchGo(elfProgram, state)
require.NoError(t, err, "apply Go runtime patches")
require.NoError(t, PatchStack(state), "add initial stack")
require.NoError(t, program.PatchStack(state), "add initial stack")
oracle, expectedStdOut, expectedStdErr := claimTestOracle(t)
oracle, expectedStdOut, expectedStdErr := testutil.ClaimTestOracle(t)
var stdOutBuf, stdErrBuf bytes.Buffer
goState := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr))
goState := singlethreaded.NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr))
for i := 0; i < 2000_000; i++ {
curStep := goState.state.Step
if goState.state.Exited {
curStep := goState.GetState().GetStep()
if goState.GetState().GetExited() {
break
}
......@@ -586,9 +454,9 @@ func TestClaimEVM(t *testing.T) {
stepWitness, err := goState.Step(true)
require.NoError(t, err)
evmPost := evm.Step(t, stepWitness, curStep)
evmPost := evm.Step(t, stepWitness, curStep, singlethreaded.GetStateHashFn())
goPost, _ := goState.state.EncodeWitness()
goPost, _ := goState.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
}
......
package mipsevm
package tests
import (
"bytes"
......@@ -6,11 +6,17 @@ 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"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
)
const syscallInsn = uint32(0x00_00_00_0c)
......@@ -20,8 +26,8 @@ func FuzzStateSyscallBrk(f *testing.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{
Cpu: CpuScalars{
state := &singlethreaded.State{
Cpu: mipsevm.CpuScalars{
PC: pc,
NextPC: nextPC,
LO: 0,
......@@ -30,8 +36,8 @@ func FuzzStateSyscallBrk(f *testing.F) {
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysBrk},
Memory: memory.NewMemory(),
Registers: [32]uint32{2: exec.SysBrk},
Step: step,
PreimageKey: common.Hash{},
PreimageOffset: preimageOffset,
......@@ -41,7 +47,7 @@ func FuzzStateSyscallBrk(f *testing.F) {
expectedRegisters := state.Registers
expectedRegisters[2] = 0x4000_0000
goState := NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
goState := singlethreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
......@@ -59,9 +65,9 @@ func FuzzStateSyscallBrk(f *testing.F) {
require.Equal(t, common.Hash{}, state.PreimageKey)
require.Equal(t, preimageOffset, state.PreimageOffset)
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step)
goPost, _ := goState.state.EncodeWitness()
evm := testutil.NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step, singlethreaded.GetStateHashFn())
goPost, _ := goState.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
......@@ -72,8 +78,8 @@ func FuzzStateSyscallClone(f *testing.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{
Cpu: CpuScalars{
state := &singlethreaded.State{
Cpu: mipsevm.CpuScalars{
PC: pc,
NextPC: nextPC,
LO: 0,
......@@ -82,8 +88,8 @@ func FuzzStateSyscallClone(f *testing.F) {
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysClone},
Memory: memory.NewMemory(),
Registers: [32]uint32{2: exec.SysClone},
Step: step,
PreimageOffset: preimageOffset,
}
......@@ -92,7 +98,7 @@ func FuzzStateSyscallClone(f *testing.F) {
expectedRegisters := state.Registers
expectedRegisters[2] = 0x1
goState := NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
goState := singlethreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
......@@ -110,9 +116,9 @@ func FuzzStateSyscallClone(f *testing.F) {
require.Equal(t, common.Hash{}, state.PreimageKey)
require.Equal(t, preimageOffset, state.PreimageOffset)
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step)
goPost, _ := goState.state.EncodeWitness()
evm := testutil.NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step, singlethreaded.GetStateHashFn())
goPost, _ := goState.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
......@@ -122,8 +128,8 @@ func FuzzStateSyscallMmap(f *testing.F) {
contracts, addrs := testContractsSetup(f)
step := uint64(0)
f.Fuzz(func(t *testing.T, addr uint32, siz uint32, heap uint32) {
state := &State{
Cpu: CpuScalars{
state := &singlethreaded.State{
Cpu: mipsevm.CpuScalars{
PC: 0,
NextPC: 4,
LO: 0,
......@@ -132,8 +138,8 @@ func FuzzStateSyscallMmap(f *testing.F) {
Heap: heap,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysMmap, 4: addr, 5: siz},
Memory: memory.NewMemory(),
Registers: [32]uint32{2: exec.SysMmap, 4: addr, 5: siz},
Step: step,
PreimageOffset: 0,
}
......@@ -141,7 +147,7 @@ func FuzzStateSyscallMmap(f *testing.F) {
preStateRoot := state.Memory.MerkleRoot()
preStateRegisters := state.Registers
goState := NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
goState := singlethreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
......@@ -161,8 +167,8 @@ func FuzzStateSyscallMmap(f *testing.F) {
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)
if sizAlign&memory.PageAddrMask != 0 { // adjust size to align with page size
sizAlign = siz + memory.PageSize - (siz & memory.PageAddrMask)
}
require.Equal(t, uint32(heap+sizAlign), state.Heap)
} else {
......@@ -172,9 +178,9 @@ func FuzzStateSyscallMmap(f *testing.F) {
require.Equal(t, uint32(heap), state.Heap)
}
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step)
goPost, _ := goState.state.EncodeWitness()
evm := testutil.NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step, singlethreaded.GetStateHashFn())
goPost, _ := goState.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
......@@ -185,8 +191,8 @@ func FuzzStateSyscallExitGroup(f *testing.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{
Cpu: CpuScalars{
state := &singlethreaded.State{
Cpu: mipsevm.CpuScalars{
PC: pc,
NextPC: nextPC,
LO: 0,
......@@ -195,8 +201,8 @@ func FuzzStateSyscallExitGroup(f *testing.F) {
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysExitGroup, 4: uint32(exitCode)},
Memory: memory.NewMemory(),
Registers: [32]uint32{2: exec.SysExitGroup, 4: uint32(exitCode)},
Step: step,
PreimageOffset: 0,
}
......@@ -204,7 +210,7 @@ func FuzzStateSyscallExitGroup(f *testing.F) {
preStateRoot := state.Memory.MerkleRoot()
preStateRegisters := state.Registers
goState := NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
goState := singlethreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
......@@ -222,9 +228,9 @@ func FuzzStateSyscallExitGroup(f *testing.F) {
require.Equal(t, common.Hash{}, state.PreimageKey)
require.Equal(t, uint32(0), state.PreimageOffset)
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step)
goPost, _ := goState.state.EncodeWitness()
evm := testutil.NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step, singlethreaded.GetStateHashFn())
goPost, _ := goState.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
......@@ -234,8 +240,8 @@ func FuzzStateSyscallFcntl(f *testing.F) {
contracts, addrs := testContractsSetup(f)
step := uint64(0)
f.Fuzz(func(t *testing.T, fd uint32, cmd uint32) {
state := &State{
Cpu: CpuScalars{
state := &singlethreaded.State{
Cpu: mipsevm.CpuScalars{
PC: 0,
NextPC: 4,
LO: 0,
......@@ -244,8 +250,8 @@ func FuzzStateSyscallFcntl(f *testing.F) {
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysFcntl, 4: fd, 5: cmd},
Memory: memory.NewMemory(),
Registers: [32]uint32{2: exec.SysFcntl, 4: fd, 5: cmd},
Step: step,
PreimageOffset: 0,
}
......@@ -253,7 +259,7 @@ func FuzzStateSyscallFcntl(f *testing.F) {
preStateRoot := state.Memory.MerkleRoot()
preStateRegisters := state.Registers
goState := NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
goState := singlethreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
......@@ -272,25 +278,25 @@ func FuzzStateSyscallFcntl(f *testing.F) {
if cmd == 3 {
expectedRegisters := preStateRegisters
switch fd {
case fdStdin, fdPreimageRead, fdHintRead:
case exec.FdStdin, exec.FdPreimageRead, exec.FdHintRead:
expectedRegisters[2] = 0
case fdStdout, fdStderr, fdPreimageWrite, fdHintWrite:
case exec.FdStdout, exec.FdStderr, exec.FdPreimageWrite, exec.FdHintWrite:
expectedRegisters[2] = 1
default:
expectedRegisters[2] = 0xFF_FF_FF_FF
expectedRegisters[7] = MipsEBADF
expectedRegisters[7] = exec.MipsEBADF
}
require.Equal(t, expectedRegisters, state.Registers)
} else {
expectedRegisters := preStateRegisters
expectedRegisters[2] = 0xFF_FF_FF_FF
expectedRegisters[7] = MipsEINVAL
expectedRegisters[7] = exec.MipsEINVAL
require.Equal(t, expectedRegisters, state.Registers)
}
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step)
goPost, _ := goState.state.EncodeWitness()
evm := testutil.NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step, singlethreaded.GetStateHashFn())
goPost, _ := goState.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
......@@ -301,8 +307,8 @@ func FuzzStateHintRead(f *testing.F) {
step := uint64(0)
f.Fuzz(func(t *testing.T, addr uint32, count uint32) {
preimageData := []byte("hello world")
state := &State{
Cpu: CpuScalars{
state := &singlethreaded.State{
Cpu: mipsevm.CpuScalars{
PC: 0,
NextPC: 4,
LO: 0,
......@@ -311,8 +317,8 @@ func FuzzStateHintRead(f *testing.F) {
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysRead, 4: fdHintRead, 5: addr, 6: count},
Memory: memory.NewMemory(),
Registers: [32]uint32{2: exec.SysRead, 4: exec.FdHintRead, 5: addr, 6: count},
Step: step,
PreimageKey: preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey(),
PreimageOffset: 0,
......@@ -323,8 +329,8 @@ func FuzzStateHintRead(f *testing.F) {
expectedRegisters := state.Registers
expectedRegisters[2] = count
oracle := staticOracle(t, preimageData) // only used for hinting
goState := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
oracle := testutil.StaticOracle(t, preimageData) // only used for hinting
goState := singlethreaded.NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
......@@ -341,9 +347,9 @@ func FuzzStateHintRead(f *testing.F) {
require.Equal(t, preStatePreimageKey, state.PreimageKey)
require.Equal(t, expectedRegisters, state.Registers)
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step)
goPost, _ := goState.state.EncodeWitness()
evm := testutil.NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step, singlethreaded.GetStateHashFn())
goPost, _ := goState.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
......@@ -357,8 +363,8 @@ func FuzzStatePreimageRead(f *testing.F) {
if preimageOffset >= uint32(len(preimageData)) {
t.SkipNow()
}
state := &State{
Cpu: CpuScalars{
state := &singlethreaded.State{
Cpu: mipsevm.CpuScalars{
PC: 0,
NextPC: 4,
LO: 0,
......@@ -367,8 +373,8 @@ func FuzzStatePreimageRead(f *testing.F) {
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysRead, 4: fdPreimageRead, 5: addr, 6: count},
Memory: memory.NewMemory(),
Registers: [32]uint32{2: exec.SysRead, 4: exec.FdPreimageRead, 5: addr, 6: count},
Step: step,
PreimageKey: preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey(),
PreimageOffset: preimageOffset,
......@@ -383,9 +389,9 @@ func FuzzStatePreimageRead(f *testing.F) {
if preimageOffset+writeLen > uint32(8+len(preimageData)) {
writeLen = uint32(8+len(preimageData)) - preimageOffset
}
oracle := staticOracle(t, preimageData)
oracle := testutil.StaticOracle(t, preimageData)
goState := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
goState := singlethreaded.NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.True(t, stepWitness.HasPreimage())
......@@ -408,9 +414,9 @@ func FuzzStatePreimageRead(f *testing.F) {
require.Equal(t, uint64(1), state.Step)
require.Equal(t, preStatePreimageKey, state.PreimageKey)
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step)
goPost, _ := goState.state.EncodeWitness()
evm := testutil.NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step, singlethreaded.GetStateHashFn())
goPost, _ := goState.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
......@@ -421,8 +427,8 @@ func FuzzStateHintWrite(f *testing.F) {
step := uint64(0)
f.Fuzz(func(t *testing.T, addr uint32, count uint32, randSeed int64) {
preimageData := []byte("hello world")
state := &State{
Cpu: CpuScalars{
state := &singlethreaded.State{
Cpu: mipsevm.CpuScalars{
PC: 0,
NextPC: 4,
LO: 0,
......@@ -431,8 +437,8 @@ func FuzzStateHintWrite(f *testing.F) {
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysWrite, 4: fdHintWrite, 5: addr, 6: count},
Memory: memory.NewMemory(),
Registers: [32]uint32{2: exec.SysWrite, 4: exec.FdHintWrite, 5: addr, 6: count},
Step: step,
PreimageKey: preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey(),
PreimageOffset: 0,
......@@ -451,8 +457,8 @@ func FuzzStateHintWrite(f *testing.F) {
expectedRegisters := state.Registers
expectedRegisters[2] = count
oracle := staticOracle(t, preimageData) // only used for hinting
goState := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
oracle := testutil.StaticOracle(t, preimageData) // only used for hinting
goState := singlethreaded.NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
......@@ -469,9 +475,9 @@ func FuzzStateHintWrite(f *testing.F) {
require.Equal(t, preStatePreimageKey, state.PreimageKey)
require.Equal(t, expectedRegisters, state.Registers)
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step)
goPost, _ := goState.state.EncodeWitness()
evm := testutil.NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step, singlethreaded.GetStateHashFn())
goPost, _ := goState.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
......@@ -482,8 +488,8 @@ func FuzzStatePreimageWrite(f *testing.F) {
step := uint64(0)
f.Fuzz(func(t *testing.T, addr uint32, count uint32) {
preimageData := []byte("hello world")
state := &State{
Cpu: CpuScalars{
state := &singlethreaded.State{
Cpu: mipsevm.CpuScalars{
PC: 0,
NextPC: 4,
LO: 0,
......@@ -492,8 +498,8 @@ func FuzzStatePreimageWrite(f *testing.F) {
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysWrite, 4: fdPreimageWrite, 5: addr, 6: count},
Memory: memory.NewMemory(),
Registers: [32]uint32{2: exec.SysWrite, 4: exec.FdPreimageWrite, 5: addr, 6: count},
Step: 0,
PreimageKey: preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey(),
PreimageOffset: 128,
......@@ -507,8 +513,8 @@ func FuzzStatePreimageWrite(f *testing.F) {
}
expectedRegisters[2] = count
oracle := staticOracle(t, preimageData)
goState := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
oracle := testutil.StaticOracle(t, preimageData)
goState := singlethreaded.NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
......@@ -525,9 +531,9 @@ func FuzzStatePreimageWrite(f *testing.F) {
require.Equal(t, uint32(0), state.PreimageOffset)
require.Equal(t, expectedRegisters, state.Registers)
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step)
goPost, _ := goState.state.EncodeWitness()
evm := testutil.NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness, step, singlethreaded.GetStateHashFn())
goPost, _ := goState.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
......
......@@ -2,7 +2,7 @@
Tests from https://github.com/grantae/OpenMIPS/tree/d606b35e9d5260aef20de2a58660c8303a681e9c/software/test/macro/tests
OpenMIPS is licensed LGPLv3 (as seen in the root of the repository), see [`LICENSE`](./LICENSE) file.
OpenMIPS is licensed LGPLv3 (as seen in the root of the repository), see [`LICENSE`](LICENSE) file.
Note that some build-system files from 2014/2015 in that repository by the same author are marked as BSD licensed,
but the build-system is not used here.
......
package testutil
// 0xbf_c0_00_00 ... BaseAddrEnd is used in tests to write the results to
const BaseAddrEnd = 0xbf_ff_ff_f0
// EndAddr is used as return-address for tests
const EndAddr = 0xa7ef00d0
package testutil
import (
"debug/elf"
"testing"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
)
func LoadELFProgram[T mipsevm.FPVMState](t *testing.T, name string, initState program.CreateFPVMState[T]) T {
elfProgram, err := elf.Open(name)
require.NoError(t, err, "open ELF file")
state, err := program.LoadELF(elfProgram, initState)
require.NoError(t, err, "load ELF into state")
err = program.PatchGo(elfProgram, state)
require.NoError(t, err, "apply Go runtime patches")
require.NoError(t, program.PatchStack(state), "add initial stack")
return state
}
package mipsevm
package testutil
import (
"encoding/binary"
"fmt"
"math/big"
"os"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
......@@ -21,12 +24,12 @@ import (
// LoadArtifacts loads the Cannon contracts, from the contracts package.
func LoadArtifacts() (*Artifacts, error) {
mips, err := foundry.ReadArtifact("../../packages/contracts-bedrock/forge-artifacts/MIPS.sol/MIPS.json")
mips, err := foundry.ReadArtifact("../../../packages/contracts-bedrock/forge-artifacts/MIPS.sol/MIPS.json")
if err != nil {
return nil, fmt.Errorf("failed to load MIPS contract: %w", err)
}
oracle, err := foundry.ReadArtifact("../../packages/contracts-bedrock/forge-artifacts/PreimageOracle.sol/PreimageOracle.json")
oracle, err := foundry.ReadArtifact("../../../packages/contracts-bedrock/forge-artifacts/PreimageOracle.sol/PreimageOracle.json")
if err != nil {
return nil, fmt.Errorf("failed to load Oracle contract: %w", err)
}
......@@ -118,3 +121,7 @@ func (d *testChain) GetHeader(h common.Hash, n uint64) *types.Header {
WithdrawalsHash: &types.EmptyWithdrawalsHash,
}
}
func MarkdownTracer() vm.EVMLogger {
return logger.NewMarkdownLogger(&logger.Config{}, os.Stdout)
}
package testutil
import (
"errors"
"fmt"
"math"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
)
type MIPSEVM struct {
env *vm.EVM
evmState *state.StateDB
addrs *Addresses
localOracle mipsevm.PreimageOracle
artifacts *Artifacts
// Track step execution for logging purposes
lastStep uint64
lastStepInput []byte
}
func NewMIPSEVM(artifacts *Artifacts, addrs *Addresses) *MIPSEVM {
env, evmState := NewEVMEnv(artifacts, addrs)
return &MIPSEVM{env, evmState, addrs, nil, artifacts, math.MaxUint64, nil}
}
func (m *MIPSEVM) SetTracer(tracer vm.EVMLogger) {
m.env.Config.Tracer = tracer
}
func (m *MIPSEVM) SetLocalOracle(oracle mipsevm.PreimageOracle) {
m.localOracle = oracle
}
// 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 *mipsevm.StepWitness, step uint64, stateHashFn mipsevm.HashFn) []byte {
m.lastStep = step
m.lastStepInput = nil
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 := EncodePreimageOracleInput(t, stepWitness, mipsevm.LocalContext{}, m.localOracle, m.artifacts.Oracle)
require.NoError(t, err, "encode preimage oracle input")
_, leftOverGas, err := m.env.Call(vm.AccountRef(sender), m.addrs.Oracle, poInput, startingGas, common.U2560)
require.NoErrorf(t, err, "evm should not fail, took %d gas", startingGas-leftOverGas)
}
input := EncodeStepInput(t, stepWitness, mipsevm.LocalContext{}, m.artifacts.MIPS)
m.lastStepInput = input
ret, leftOverGas, err := m.env.Call(vm.AccountRef(sender), m.addrs.MIPS, input, startingGas, common.U2560)
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
stateHash, err := stateHashFn(evmPost)
require.NoError(t, err, "state hash could not be computed")
require.Equal(t, stateHash, postHash, "logged state must be accurate")
m.env.StateDB.RevertToSnapshot(snap)
t.Logf("EVM step %d took %d gas, and returned stateHash %s", step, startingGas-leftOverGas, postHash)
return evmPost
}
func EncodeStepInput(t *testing.T, wit *mipsevm.StepWitness, localContext mipsevm.LocalContext, mips *foundry.Artifact) []byte {
input, err := mips.ABI.Pack("step", wit.State, wit.ProofData, localContext)
require.NoError(t, err)
return input
}
func EncodePreimageOracleInput(t *testing.T, wit *mipsevm.StepWitness, localContext mipsevm.LocalContext, localOracle mipsevm.PreimageOracle, oracle *foundry.Artifact) ([]byte, error) {
if wit.PreimageKey == ([32]byte{}) {
return nil, errors.New("cannot encode pre-image oracle input, witness has no pre-image to proof")
}
switch preimage.KeyType(wit.PreimageKey[0]) {
case preimage.LocalKeyType:
if len(wit.PreimageValue) > 32+8 {
return nil, fmt.Errorf("local pre-image exceeds maximum size of 32 bytes with key 0x%x", wit.PreimageKey)
}
preimagePart := wit.PreimageValue[8:]
var tmp [32]byte
copy(tmp[:], preimagePart)
input, err := oracle.ABI.Pack("loadLocalData",
new(big.Int).SetBytes(wit.PreimageKey[1:]),
localContext,
tmp,
new(big.Int).SetUint64(uint64(len(preimagePart))),
new(big.Int).SetUint64(uint64(wit.PreimageOffset)),
)
require.NoError(t, err)
return input, nil
case preimage.Keccak256KeyType:
input, err := oracle.ABI.Pack(
"loadKeccak256PreimagePart",
new(big.Int).SetUint64(uint64(wit.PreimageOffset)),
wit.PreimageValue[8:])
require.NoError(t, err)
return input, nil
case preimage.PrecompileKeyType:
if localOracle == nil {
return nil, fmt.Errorf("local oracle is required for precompile preimages")
}
preimage := localOracle.GetPreimage(preimage.Keccak256Key(wit.PreimageKey).PreimageKey())
precompile := common.BytesToAddress(preimage[:20])
callInput := preimage[20:]
input, err := oracle.ABI.Pack(
"loadPrecompilePreimagePart",
new(big.Int).SetUint64(uint64(wit.PreimageOffset)),
precompile,
callInput,
)
require.NoError(t, err)
return input, nil
default:
return nil, fmt.Errorf("unsupported pre-image type %d, cannot prepare preimage with key %x offset %d for oracle",
wit.PreimageKey[0], wit.PreimageKey, wit.PreimageOffset)
}
}
func LogStepFailureAtCleanup(t *testing.T, mipsEvm *MIPSEVM) {
t.Cleanup(func() {
if t.Failed() {
// Note: For easier debugging of a failing step, see MIPS.t.sol#test_step_debug_succeeds()
t.Logf("Failed while executing step %d with input: %x", mipsEvm.lastStep, mipsEvm.lastStepInput)
}
})
}
package mipsevm
package testutil
import (
"bytes"
"debug/elf"
"encoding/binary"
"encoding/hex"
"fmt"
"io"
"os"
"path"
"strings"
"testing"
......@@ -16,151 +11,60 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
)
// 0xbf_c0_00_00 ... baseAddrEnd is used in tests to write the results to
const baseAddrEnd = 0xbf_ff_ff_f0
// endAddr is used as return-address for tests
const endAddr = 0xa7ef00d0
func TestState(t *testing.T) {
testFiles, err := os.ReadDir("open_mips_tests/test/bin")
require.NoError(t, err)
for _, f := range testFiles {
t.Run(f.Name(), func(t *testing.T) {
oracle := selectOracleFixture(t, f.Name())
// Short-circuit early for exit_group.bin
exitGroup := f.Name() == "exit_group.bin"
// TODO: currently tests are compiled as flat binary objects
// We can use more standard tooling to compile them to ELF files and get remove maketests.py
fn := path.Join("open_mips_tests/test/bin", f.Name())
//elfProgram, err := elf.Open()
//require.NoError(t, err, "must load test ELF binary")
//state, err := LoadELF(elfProgram)
//require.NoError(t, err, "must load ELF into state")
programMem, err := os.ReadFile(fn)
require.NoError(t, err)
state := &State{Cpu: CpuScalars{PC: 0, NextPC: 4}, Memory: NewMemory()}
err = state.Memory.SetMemoryRange(0, bytes.NewReader(programMem))
require.NoError(t, err, "load program into state")
// set the return address ($ra) to jump into when test completes
state.Registers[31] = endAddr
type TestOracle struct {
hint func(v []byte)
getPreimage func(k [32]byte) []byte
}
us := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
var _ mipsevm.PreimageOracle = (*TestOracle)(nil)
for i := 0; i < 1000; i++ {
if us.state.Cpu.PC == endAddr {
break
}
if exitGroup && us.state.Exited {
break
}
_, err := us.Step(false)
require.NoError(t, err)
}
func (t *TestOracle) Hint(v []byte) {
t.hint(v)
}
if exitGroup {
require.NotEqual(t, uint32(endAddr), us.state.Cpu.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")
} else {
require.Equal(t, uint32(endAddr), us.state.Cpu.PC, "must reach end")
done, result := state.Memory.GetMemory(baseAddrEnd+4), state.Memory.GetMemory(baseAddrEnd+8)
// inspect test result
require.Equal(t, done, uint32(1), "must be done")
require.Equal(t, result, uint32(1), "must have success result")
}
})
}
func (t *TestOracle) GetPreimage(k [32]byte) []byte {
return t.getPreimage(k)
}
// Run through all permutations of `exited` / `exitCode` and ensure that the
// correct witness, state hash, and VM Status is produced.
func TestStateHash(t *testing.T) {
cases := []struct {
exited bool
exitCode uint8
}{
{exited: false, exitCode: 0},
{exited: false, exitCode: 1},
{exited: false, exitCode: 2},
{exited: false, exitCode: 3},
{exited: true, exitCode: 0},
{exited: true, exitCode: 1},
{exited: true, exitCode: 2},
{exited: true, exitCode: 3},
func StaticOracle(t *testing.T, preimageData []byte) *TestOracle {
return &TestOracle{
hint: func(v []byte) {},
getPreimage: func(k [32]byte) []byte {
if k != preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey() {
t.Fatalf("invalid preimage request for %x", k)
}
exitedOffset := 32*2 + 4*6
for _, c := range cases {
state := &State{
Memory: NewMemory(),
Exited: c.exited,
ExitCode: c.exitCode,
return preimageData
},
}
}
actualWitness, actualStateHash := state.EncodeWitness()
require.Equal(t, len(actualWitness), STATE_WITNESS_SIZE, "Incorrect witness size")
expectedWitness := make(StateWitness, 226)
memRoot := state.Memory.MerkleRoot()
copy(expectedWitness[:32], memRoot[:])
expectedWitness[exitedOffset] = c.exitCode
var exited uint8
if c.exited {
exited = 1
func StaticPrecompileOracle(t *testing.T, precompile common.Address, input []byte, result []byte) *TestOracle {
return &TestOracle{
hint: func(v []byte) {},
getPreimage: func(k [32]byte) []byte {
keyData := append(precompile.Bytes(), input...)
switch k[0] {
case byte(preimage.Keccak256KeyType):
if k != preimage.Keccak256Key(crypto.Keccak256Hash(keyData)).PreimageKey() {
t.Fatalf("invalid preimage request for %x", k)
}
expectedWitness[exitedOffset+1] = uint8(exited)
require.EqualValues(t, expectedWitness[:], actualWitness[:], "Incorrect witness")
expectedStateHash := crypto.Keccak256Hash(actualWitness)
expectedStateHash[0] = vmStatus(c.exited, c.exitCode)
require.Equal(t, expectedStateHash, actualStateHash, "Incorrect state hash")
return keyData
case byte(preimage.PrecompileKeyType):
if k != preimage.PrecompileKey(crypto.Keccak256Hash(keyData)).PreimageKey() {
t.Fatalf("invalid preimage request for %x", k)
}
}
func TestHello(t *testing.T) {
state := loadELFProgram(t, "../example/bin/hello.elf")
var stdOutBuf, stdErrBuf bytes.Buffer
us := NewInstrumentedState(state, nil, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr))
for i := 0; i < 400_000; i++ {
if us.state.Exited {
break
return result
}
_, err := us.Step(false)
require.NoError(t, err)
panic("unreachable")
},
}
require.True(t, state.Exited, "must complete program")
require.Equal(t, uint8(0), state.ExitCode, "exit with 0")
require.Equal(t, "hello world!\n", stdOutBuf.String(), "stdout says hello")
require.Equal(t, "", stdErrBuf.String(), "stderr silent")
}
type testOracle struct {
hint func(v []byte)
getPreimage func(k [32]byte) []byte
}
func (t *testOracle) Hint(v []byte) {
t.hint(v)
}
func (t *testOracle) GetPreimage(k [32]byte) []byte {
return t.getPreimage(k)
}
var _ PreimageOracle = (*testOracle)(nil)
func claimTestOracle(t *testing.T) (po PreimageOracle, stdOut string, stdErr string) {
func ClaimTestOracle(t *testing.T) (po mipsevm.PreimageOracle, stdOut string, stdErr string) {
s := uint64(1000)
a := uint64(3)
b := uint64(4)
......@@ -181,7 +85,7 @@ func claimTestOracle(t *testing.T) (po PreimageOracle, stdOut string, stdErr str
images[preimage.LocalIndexKey(1).PreimageKey()] = diffHash[:]
images[preimage.LocalIndexKey(2).PreimageKey()] = encodeU64(s*a + b)
oracle := &testOracle{
oracle := &TestOracle{
hint: func(v []byte) {
parts := strings.Split(string(v), " ")
require.Len(t, parts, 2)
......@@ -214,103 +118,8 @@ func claimTestOracle(t *testing.T) (po PreimageOracle, stdOut string, stdErr str
return oracle, fmt.Sprintf("computing %d * %d + %d\nclaim %d is good!\n", s, a, b, s*a+b), "started!"
}
func TestClaim(t *testing.T) {
state := loadELFProgram(t, "../example/bin/claim.elf")
oracle, expectedStdOut, expectedStdErr := claimTestOracle(t)
var stdOutBuf, stdErrBuf bytes.Buffer
us := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr))
for i := 0; i < 2000_000; i++ {
if us.state.Exited {
break
}
_, err := us.Step(false)
require.NoError(t, err)
}
require.True(t, state.Exited, "must complete program")
require.Equal(t, uint8(0), state.ExitCode, "exit with 0")
require.Equal(t, expectedStdOut, stdOutBuf.String(), "stdout")
require.Equal(t, expectedStdErr, stdErrBuf.String(), "stderr")
}
func TestAlloc(t *testing.T) {
t.Skip("TODO(client-pod#906): Currently fails on Single threaded Cannon. Re-enable for the MT FPVM")
state := loadELFProgram(t, "../example/bin/alloc.elf")
const numAllocs = 100 // where each alloc is a 32 MiB chunk
oracle := allocOracle(t, numAllocs)
// completes in ~870 M steps
us := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
for i := 0; i < 20_000_000_000; i++ {
if us.state.Exited {
break
}
_, err := us.Step(false)
require.NoError(t, err)
if state.Step%10_000_000 == 0 {
t.Logf("Completed %d steps", state.Step)
}
}
t.Logf("Completed in %d steps", state.Step)
require.True(t, state.Exited, "must complete program")
require.Equal(t, uint8(0), state.ExitCode, "exit with 0")
require.Less(t, state.Memory.PageCount()*PageSize, 1*1024*1024*1024, "must not allocate more than 1 GiB")
}
func loadELFProgram(t *testing.T, name string) *State {
elfProgram, err := elf.Open(name)
require.NoError(t, err, "open ELF file")
state, err := LoadELF(elfProgram, CreateInitialState)
require.NoError(t, err, "load ELF into state")
err = PatchGo(elfProgram, state)
require.NoError(t, err, "apply Go runtime patches")
require.NoError(t, PatchStack(state), "add initial stack")
return state
}
func staticOracle(t *testing.T, preimageData []byte) *testOracle {
return &testOracle{
hint: func(v []byte) {},
getPreimage: func(k [32]byte) []byte {
if k != preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey() {
t.Fatalf("invalid preimage request for %x", k)
}
return preimageData
},
}
}
func staticPrecompileOracle(t *testing.T, precompile common.Address, input []byte, result []byte) *testOracle {
return &testOracle{
hint: func(v []byte) {},
getPreimage: func(k [32]byte) []byte {
keyData := append(precompile.Bytes(), input...)
switch k[0] {
case byte(preimage.Keccak256KeyType):
if k != preimage.Keccak256Key(crypto.Keccak256Hash(keyData)).PreimageKey() {
t.Fatalf("invalid preimage request for %x", k)
}
return keyData
case byte(preimage.PrecompileKeyType):
if k != preimage.PrecompileKey(crypto.Keccak256Hash(keyData)).PreimageKey() {
t.Fatalf("invalid preimage request for %x", k)
}
return result
}
panic("unreachable")
},
}
}
func allocOracle(t *testing.T, numAllocs int) *testOracle {
return &testOracle{
func AllocOracle(t *testing.T, numAllocs int) *TestOracle {
return &TestOracle{
hint: func(v []byte) {},
getPreimage: func(k [32]byte) []byte {
if k != preimage.LocalIndexKey(0).PreimageKey() {
......@@ -321,38 +130,15 @@ func allocOracle(t *testing.T, numAllocs int) *testOracle {
}
}
func selectOracleFixture(t *testing.T, programName string) PreimageOracle {
func SelectOracleFixture(t *testing.T, programName string) mipsevm.PreimageOracle {
if strings.HasPrefix(programName, "oracle_kzg") {
precompile := common.BytesToAddress([]byte{0xa})
input := common.FromHex("01e798154708fe7789429634053cbf9f99b619f9f084048927333fce637f549b564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d3630624d25032e67a7e6a4910df5834b8fe70e6bcfeeac0352434196bdf4b2485d5a18f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7873033e038326e87ed3e1276fd140253fa08e9fc25fb2d9a98527fc22a2c9612fbeafdad446cbc7bcdbdcd780af2c16a")
blobPrecompileReturnValue := common.FromHex("000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001")
return staticPrecompileOracle(t, precompile, input, append([]byte{0x1}, blobPrecompileReturnValue...))
return StaticPrecompileOracle(t, precompile, input, append([]byte{0x1}, blobPrecompileReturnValue...))
} else if strings.HasPrefix(programName, "oracle") {
return staticOracle(t, []byte("hello world"))
return StaticOracle(t, []byte("hello world"))
} else {
return nil
}
}
func TestStateJSONCodec(t *testing.T) {
elfProgram, err := elf.Open("../example/bin/hello.elf")
require.NoError(t, err, "open ELF file")
state, err := LoadELF(elfProgram, CreateInitialState)
require.NoError(t, err, "load ELF into state")
stateJSON, err := state.MarshalJSON()
require.NoError(t, err)
newState := new(State)
require.NoError(t, newState.UnmarshalJSON(stateJSON))
require.Equal(t, state.PreimageKey, newState.PreimageKey)
require.Equal(t, state.PreimageOffset, newState.PreimageOffset)
require.Equal(t, state.Cpu, newState.Cpu)
require.Equal(t, state.Heap, newState.Heap)
require.Equal(t, state.ExitCode, newState.ExitCode)
require.Equal(t, state.Exited, newState.Exited)
require.Equal(t, state.Memory.MerkleRoot(), newState.Memory.MerkleRoot())
require.Equal(t, state.Registers, newState.Registers)
require.Equal(t, state.Step, newState.Step)
}
......@@ -20,6 +20,8 @@ func (wit *StepWitness) HasPreimage() bool {
return wit.PreimageKey != ([32]byte{})
}
type HashFn func(sw []byte) (common.Hash, error)
func AppendBoolToWitness(witnessData []byte, boolVal bool) []byte {
if boolVal {
return append(witnessData, 1)
......
......@@ -4,10 +4,11 @@ import (
"context"
"math/big"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
)
var absolutePrestate = common.FromHex("0000000000000000000000000000000000000000000000000000000000000060")
......
......@@ -6,10 +6,9 @@ import (
"fmt"
"math/big"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
......
......@@ -5,9 +5,10 @@ import (
"fmt"
"io"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum/go-ethereum/common"
)
var asteriscWitnessLen = 362
......
......@@ -6,9 +6,12 @@ import (
"path/filepath"
"testing"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
)
func newCannonPrestateProvider(dataDir string, prestate string) *CannonPrestateProvider {
......@@ -40,8 +43,8 @@ func TestAbsolutePreStateCommitment(t *testing.T) {
provider := newCannonPrestateProvider(dataDir, prestate)
actual, err := provider.AbsolutePreStateCommitment(context.Background())
require.NoError(t, err)
state := mipsevm.State{
Memory: mipsevm.NewMemory(),
state := singlethreaded.State{
Memory: memory.NewMemory(),
PreimageKey: common.HexToHash("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"),
PreimageOffset: 0,
Cpu: mipsevm.CpuScalars{
......
......@@ -10,17 +10,17 @@ import (
"os"
"path/filepath"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-program/host/kvstore"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
)
type CannonTraceProvider struct {
......@@ -162,7 +162,7 @@ func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*utils.P
return &proof, nil
}
func (c *CannonTraceProvider) finalState() (*mipsevm.State, error) {
func (c *CannonTraceProvider) finalState() (*singlethreaded.State, error) {
state, err := parseState(filepath.Join(c.dir, vm.FinalState))
if err != nil {
return nil, fmt.Errorf("cannot read final state: %w", err)
......
......@@ -11,15 +11,17 @@ import (
"path/filepath"
"testing"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
//go:embed test_data
......@@ -49,8 +51,8 @@ func TestGet(t *testing.T) {
t.Run("ProofAfterEndOfTrace", func(t *testing.T) {
provider, generator := setupWithTestData(t, dataDir, prestate)
generator.finalState = &mipsevm.State{
Memory: &mipsevm.Memory{},
generator.finalState = &singlethreaded.State{
Memory: &memory.Memory{},
Step: 10,
Exited: true,
}
......@@ -105,8 +107,8 @@ func TestGetStepData(t *testing.T) {
t.Run("GenerateProof", func(t *testing.T) {
dataDir, prestate := setupTestData(t)
provider, generator := setupWithTestData(t, dataDir, prestate)
generator.finalState = &mipsevm.State{
Memory: &mipsevm.Memory{},
generator.finalState = &singlethreaded.State{
Memory: &memory.Memory{},
Step: 10,
Exited: true,
}
......@@ -131,8 +133,8 @@ func TestGetStepData(t *testing.T) {
t.Run("ProofAfterEndOfTrace", func(t *testing.T) {
dataDir, prestate := setupTestData(t)
provider, generator := setupWithTestData(t, dataDir, prestate)
generator.finalState = &mipsevm.State{
Memory: &mipsevm.Memory{},
generator.finalState = &singlethreaded.State{
Memory: &memory.Memory{},
Step: 10,
Exited: true,
}
......@@ -157,8 +159,8 @@ func TestGetStepData(t *testing.T) {
t.Run("ReadLastStepFromDisk", func(t *testing.T) {
dataDir, prestate := setupTestData(t)
provider, initGenerator := setupWithTestData(t, dataDir, prestate)
initGenerator.finalState = &mipsevm.State{
Memory: &mipsevm.Memory{},
initGenerator.finalState = &singlethreaded.State{
Memory: &memory.Memory{},
Step: 10,
Exited: true,
}
......@@ -175,8 +177,8 @@ func TestGetStepData(t *testing.T) {
require.Contains(t, initGenerator.generated, 7000, "should have tried to generate the proof")
provider, generator := setupWithTestData(t, dataDir, prestate)
generator.finalState = &mipsevm.State{
Memory: &mipsevm.Memory{},
generator.finalState = &singlethreaded.State{
Memory: &memory.Memory{},
Step: 10,
Exited: true,
}
......@@ -247,7 +249,7 @@ func setupWithTestData(t *testing.T, dataDir string, prestate string) (*CannonTr
type stubGenerator struct {
generated []int // Using int makes assertions easier
finalState *mipsevm.State
finalState *singlethreaded.State
proof *utils.ProofData
}
......
......@@ -5,11 +5,11 @@ import (
"fmt"
"io"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
)
func parseState(path string) (*mipsevm.State, error) {
func parseState(path string) (*singlethreaded.State, error) {
file, err := ioutil.OpenDecompressed(path)
if err != nil {
return nil, fmt.Errorf("cannot open state file (%v): %w", path, err)
......@@ -17,9 +17,9 @@ func parseState(path string) (*mipsevm.State, error) {
return parseStateFromReader(file)
}
func parseStateFromReader(in io.ReadCloser) (*mipsevm.State, error) {
func parseStateFromReader(in io.ReadCloser) (*singlethreaded.State, error) {
defer in.Close()
var state mipsevm.State
var state singlethreaded.State
if err := json.NewDecoder(in).Decode(&state); err != nil {
return nil, fmt.Errorf("invalid mipsevm state: %w", err)
}
......
......@@ -8,8 +8,9 @@ import (
"path/filepath"
"testing"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
)
//go:embed test_data/state.json
......@@ -24,7 +25,7 @@ func TestLoadState(t *testing.T) {
state, err := parseState(path)
require.NoError(t, err)
var expected mipsevm.State
var expected singlethreaded.State
require.NoError(t, json.Unmarshal(testState, &expected))
require.Equal(t, &expected, state)
})
......@@ -43,7 +44,7 @@ func TestLoadState(t *testing.T) {
state, err := parseState(path)
require.NoError(t, err)
var expected mipsevm.State
var expected singlethreaded.State
require.NoError(t, json.Unmarshal(testState, &expected))
require.Equal(t, &expected, state)
})
......
......@@ -5,12 +5,13 @@ import (
"fmt"
"testing"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
)
var (
......
......@@ -11,15 +11,6 @@ import (
"testing"
"time"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-e2e/bindings"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum-optimism/optimism/op-service/client"
"github.com/ethereum-optimism/optimism/op-service/predeploys"
"github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
......@@ -30,6 +21,16 @@ import (
"github.com/ethereum/go-ethereum/rpc"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-e2e/bindings"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum-optimism/optimism/op-service/client"
"github.com/ethereum-optimism/optimism/op-service/predeploys"
"github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum-optimism/optimism/op-service/testlog"
)
func TestBenchmarkCannon_FPP(t *testing.T) {
......
......@@ -8,7 +8,13 @@ import (
"path/filepath"
"testing"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm"
......@@ -20,11 +26,6 @@ import (
"github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require"
)
func TestPrecompiles(t *testing.T) {
......@@ -160,13 +161,13 @@ func runCannon(t *testing.T, ctx context.Context, sys *op_e2e.System, inputs uti
t.Logf("Completed in %d steps", state.Step)
}
func parseState(path string) (*mipsevm.State, error) {
func parseState(path string) (*singlethreaded.State, error) {
file, err := ioutil.OpenDecompressed(path)
if err != nil {
return nil, fmt.Errorf("cannot open state file (%v): %w", path, err)
}
defer file.Close()
var state mipsevm.State
var state singlethreaded.State
err = json.NewDecoder(file).Decode(&state)
if err != nil {
return nil, fmt.Errorf("invalid mipsevm state (%v): %w", path, err)
......
......@@ -7,10 +7,6 @@ import (
"os"
"strconv"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/predeploys"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
......@@ -20,6 +16,11 @@ import (
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/triedb"
"github.com/ethereum/go-ethereum/triedb/hashdb"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/predeploys"
)
// ABI types
......@@ -359,7 +360,7 @@ func DiffTestUtils() {
fmt.Print(hexutil.Encode(packed[32:]))
case "cannonMemoryProof":
// <pc, insn, [memAddr, memValue]>
mem := mipsevm.NewMemory()
mem := memory.NewMemory()
if len(args) != 3 && len(args) != 5 {
panic("Error: cannonMemoryProofWithProof requires 2 or 4 arguments")
}
......@@ -392,7 +393,7 @@ func DiffTestUtils() {
fmt.Print(hexutil.Encode(packed[32:]))
case "cannonMemoryProofWrongLeaf":
// <pc, insn, memAddr, memValue>
mem := mipsevm.NewMemory()
mem := memory.NewMemory()
if len(args) != 5 {
panic("Error: cannonMemoryProofWrongLeaf requires 4 arguments")
}
......
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