Commit 7ff5e6ed authored by Inphi's avatar Inphi Committed by GitHub

cannon: Fix GC emulation of Go programs (#11704)

* cannon: Fix GC emulation of Go programs

Improves Linux/MIPS32 emulation for Go programs that utilize the garbage
collector and goroutine scheduling.

This adds support for the following syscalls:

- getpid - used by the go scheduler
- clock_gettime - used by the go scheduler and for GC assists and to properly emulate
  time related operations such as `time.Sleep`.

Note on GC assists:

The Go GC relies on `clock_gettime` for GC "assists", whereby a mutator can perform a little bit
of GC without waiting for the scheduler to do so.
A monotonic clock (runtime.nanotime) is used to compute the current goroutine's compute budget.
By modeling a MIPS32 CPU that runs at some clock speed (ex: 10 MHz), we can provide a consistent
emulation of monotonic time needed by the Go runtime.
All other clock_gettime flags are handled as unimplemented syscalls.

* fix unsupported syscalls test

* fix some review comments

* address review comments

* update snapshots

* fuzz invalid memory proof

* reduce test runtime

* tweak realtime emulation

* reduce test runtime

* set a high timeout for heavy fuzz tests

* simplify fuzz

* fix heavy tests
parent e9337dbb
...@@ -14,7 +14,10 @@ type MemoryTrackerImpl struct { ...@@ -14,7 +14,10 @@ type MemoryTrackerImpl struct {
memory *memory.Memory memory *memory.Memory
lastMemAccess uint32 lastMemAccess uint32
memProofEnabled bool memProofEnabled bool
memProof [memory.MEM_PROOF_SIZE]byte // proof of first unique memory access
memProof [memory.MEM_PROOF_SIZE]byte
// proof of second unique memory access
memProof2 [memory.MEM_PROOF_SIZE]byte
} }
func NewMemoryTracker(memory *memory.Memory) *MemoryTrackerImpl { func NewMemoryTracker(memory *memory.Memory) *MemoryTrackerImpl {
...@@ -31,6 +34,16 @@ func (m *MemoryTrackerImpl) TrackMemAccess(effAddr uint32) { ...@@ -31,6 +34,16 @@ func (m *MemoryTrackerImpl) TrackMemAccess(effAddr uint32) {
} }
} }
// TrackMemAccess2 creates a proof for a memory access following a call to TrackMemAccess
// This is used to generate proofs for contiguous memory accesses within the same step
func (m *MemoryTrackerImpl) TrackMemAccess2(effAddr uint32) {
if m.memProofEnabled && m.lastMemAccess+4 != effAddr {
panic(fmt.Errorf("unexpected disjointed mem access at %08x, last memory access is at %08x buffered", effAddr, m.lastMemAccess))
}
m.lastMemAccess = effAddr
m.memProof2 = m.memory.MerkleProof(effAddr)
}
func (m *MemoryTrackerImpl) Reset(enableProof bool) { func (m *MemoryTrackerImpl) Reset(enableProof bool) {
m.memProofEnabled = enableProof m.memProofEnabled = enableProof
m.lastMemAccess = ^uint32(0) m.lastMemAccess = ^uint32(0)
...@@ -39,3 +52,7 @@ func (m *MemoryTrackerImpl) Reset(enableProof bool) { ...@@ -39,3 +52,7 @@ func (m *MemoryTrackerImpl) Reset(enableProof bool) {
func (m *MemoryTrackerImpl) MemProof() [memory.MEM_PROOF_SIZE]byte { func (m *MemoryTrackerImpl) MemProof() [memory.MEM_PROOF_SIZE]byte {
return m.memProof return m.memProof
} }
func (m *MemoryTrackerImpl) MemProof2() [memory.MEM_PROOF_SIZE]byte {
return m.memProof2
}
...@@ -14,19 +14,21 @@ import ( ...@@ -14,19 +14,21 @@ import (
// Syscall codes // Syscall codes
const ( const (
SysMmap = 4090 SysMmap = 4090
SysBrk = 4045 SysBrk = 4045
SysClone = 4120 SysClone = 4120
SysExitGroup = 4246 SysExitGroup = 4246
SysRead = 4003 SysRead = 4003
SysWrite = 4004 SysWrite = 4004
SysFcntl = 4055 SysFcntl = 4055
SysExit = 4001 SysExit = 4001
SysSchedYield = 4162 SysSchedYield = 4162
SysGetTID = 4222 SysGetTID = 4222
SysFutex = 4238 SysFutex = 4238
SysOpen = 4005 SysOpen = 4005
SysNanosleep = 4166 SysNanosleep = 4166
SysClockGetTime = 4263
SysGetpid = 4020
) )
// Noop Syscall codes // Noop Syscall codes
...@@ -67,7 +69,6 @@ const ( ...@@ -67,7 +69,6 @@ const (
SysTimerCreate = 4257 SysTimerCreate = 4257
SysTimerSetTime = 4258 SysTimerSetTime = 4258
SysTimerDelete = 4261 SysTimerDelete = 4261
SysClockGetTime = 4263
) )
// File descriptors // File descriptors
...@@ -132,7 +133,21 @@ const ( ...@@ -132,7 +133,21 @@ const (
// Other constants // Other constants
const ( const (
// SchedQuantum is the number of steps dedicated for a thread before it's preempted. Effectively used to emulate thread "time slices"
SchedQuantum = 100_000 SchedQuantum = 100_000
// HZ is the assumed clock rate of an emulated MIPS32 CPU.
// The value of HZ is a rough estimate of the Cannon instruction count / second on a typical machine.
// HZ is used to emulate the clock_gettime syscall used by guest programs that have a Go runtime.
// The Go runtime consumes the system time to determine when to initiate gc assists and for goroutine scheduling.
// A HZ value that is too low (i.e. lower than the emulation speed) results in the main goroutine attempting to assist with GC more often.
// Adjust this value accordingly as the emulation speed changes. The HZ value should be within the same order of magnitude as the emulation speed.
HZ = 10_000_000
// ClockGettimeRealtimeFlag is the clock_gettime clock id for Linux's realtime clock: https://github.com/torvalds/linux/blob/ad618736883b8970f66af799e34007475fe33a68/include/uapi/linux/time.h#L49
ClockGettimeRealtimeFlag = 0
// ClockGettimeMonotonicFlag is the clock_gettime clock id for Linux's monotonic clock: https://github.com/torvalds/linux/blob/ad618736883b8970f66af799e34007475fe33a68/include/uapi/linux/time.h#L50
ClockGettimeMonotonicFlag = 1
) )
func GetSyscallArgs(registers *[32]uint32) (syscallNum, a0, a1, a2, a3 uint32) { func GetSyscallArgs(registers *[32]uint32) (syscallNum, a0, a1, a2, a3 uint32) {
......
...@@ -328,6 +328,20 @@ func (m *Memory) Deserialize(in io.Reader) error { ...@@ -328,6 +328,20 @@ func (m *Memory) Deserialize(in io.Reader) error {
return nil return nil
} }
func (m *Memory) Copy() *Memory {
out := NewMemory()
out.nodes = make(map[uint64]*[32]byte)
out.pages = make(map[uint32]*CachedPage)
out.lastPageKeys = [2]uint32{^uint32(0), ^uint32(0)}
out.lastPage = [2]*CachedPage{nil, nil}
for k, page := range m.pages {
data := new(Page)
*data = *page.Data
out.AllocPage(k).Data = data
}
return out
}
type memReader struct { type memReader struct {
m *Memory m *Memory
addr uint32 addr uint32
......
...@@ -187,3 +187,11 @@ func TestMemoryJSON(t *testing.T) { ...@@ -187,3 +187,11 @@ func TestMemoryJSON(t *testing.T) {
require.NoError(t, json.Unmarshal(dat, &res)) require.NoError(t, json.Unmarshal(dat, &res))
require.Equal(t, uint32(123), res.GetMemory(8)) require.Equal(t, uint32(123), res.GetMemory(8))
} }
func TestMemoryCopy(t *testing.T) {
m := NewMemory()
m.SetMemory(0x8000, 123)
mcpy := m.Copy()
require.Equal(t, uint32(123), mcpy.GetMemory(0x8000))
require.Equal(t, m.MerkleRoot(), mcpy.MerkleRoot())
}
...@@ -73,7 +73,9 @@ func (m *InstrumentedState) Step(proof bool) (wit *mipsevm.StepWitness, err erro ...@@ -73,7 +73,9 @@ func (m *InstrumentedState) Step(proof bool) (wit *mipsevm.StepWitness, err erro
if proof { if proof {
memProof := m.memoryTracker.MemProof() memProof := m.memoryTracker.MemProof()
memProof2 := m.memoryTracker.MemProof2()
wit.ProofData = append(wit.ProofData, memProof[:]...) wit.ProofData = append(wit.ProofData, memProof[:]...)
wit.ProofData = append(wit.ProofData, memProof2[:]...)
lastPreimageKey, lastPreimage, lastPreimageOffset := m.preimageOracle.LastPreimage() lastPreimageKey, lastPreimage, lastPreimageOffset := m.preimageOracle.LastPreimage()
if lastPreimageOffset != ^uint32(0) { if lastPreimageOffset != ^uint32(0) {
wit.PreimageOffset = lastPreimageOffset wit.PreimageOffset = lastPreimageOffset
......
...@@ -19,19 +19,23 @@ func vmFactory(state *State, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer ...@@ -19,19 +19,23 @@ func vmFactory(state *State, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer
} }
func TestInstrumentedState_OpenMips(t *testing.T) { func TestInstrumentedState_OpenMips(t *testing.T) {
t.Parallel()
// TODO(cp-903): Add mt-specific tests here // TODO(cp-903): Add mt-specific tests here
testutil.RunVMTests_OpenMips(t, CreateEmptyState, vmFactory, "clone.bin") testutil.RunVMTests_OpenMips(t, CreateEmptyState, vmFactory, "clone.bin")
} }
func TestInstrumentedState_Hello(t *testing.T) { func TestInstrumentedState_Hello(t *testing.T) {
t.Parallel()
testutil.RunVMTest_Hello(t, CreateInitialState, vmFactory, false) testutil.RunVMTest_Hello(t, CreateInitialState, vmFactory, false)
} }
func TestInstrumentedState_Claim(t *testing.T) { func TestInstrumentedState_Claim(t *testing.T) {
t.Parallel()
testutil.RunVMTest_Claim(t, CreateInitialState, vmFactory, false) testutil.RunVMTest_Claim(t, CreateInitialState, vmFactory, false)
} }
func TestInstrumentedState_MultithreadedProgram(t *testing.T) { func TestInstrumentedState_MultithreadedProgram(t *testing.T) {
t.Parallel()
state, _ := testutil.LoadELFProgram(t, "../../testdata/example/bin/multithreaded.elf", CreateInitialState, false) state, _ := testutil.LoadELFProgram(t, "../../testdata/example/bin/multithreaded.elf", CreateInitialState, false)
oracle := testutil.StaticOracle(t, []byte{}) oracle := testutil.StaticOracle(t, []byte{})
...@@ -54,26 +58,43 @@ func TestInstrumentedState_MultithreadedProgram(t *testing.T) { ...@@ -54,26 +58,43 @@ func TestInstrumentedState_MultithreadedProgram(t *testing.T) {
} }
func TestInstrumentedState_Alloc(t *testing.T) { func TestInstrumentedState_Alloc(t *testing.T) {
t.Skip("TODO(client-pod#906): Currently failing - need to debug.") const MiB = 1024 * 1024
state, _ := testutil.LoadELFProgram(t, "../../testdata/example/bin/alloc.elf", CreateInitialState, false) cases := []struct {
const numAllocs = 100 // where each alloc is a 32 MiB chunk name string
oracle := testutil.AllocOracle(t, numAllocs) numAllocs int
allocSize int
maxMemoryUsageCheck int
}{
{name: "10 32MiB allocations", numAllocs: 10, allocSize: 32 * MiB, maxMemoryUsageCheck: 256 * MiB},
{name: "5 64MiB allocations", numAllocs: 5, allocSize: 64 * MiB, maxMemoryUsageCheck: 256 * MiB},
{name: "5 128MiB allocations", numAllocs: 5, allocSize: 128 * MiB, maxMemoryUsageCheck: 128 * 3 * MiB},
}
// completes in ~870 M steps for _, test := range cases {
us := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr, testutil.CreateLogger(), nil) test := test
for i := 0; i < 20_000_000_000; i++ { t.Run(test.name, func(t *testing.T) {
if us.GetState().GetExited() { t.Parallel()
break state, _ := testutil.LoadELFProgram(t, "../../testdata/example/bin/alloc.elf", CreateInitialState, false)
} oracle := testutil.AllocOracle(t, test.numAllocs, test.allocSize)
_, err := us.Step(false)
require.NoError(t, err) us := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr, testutil.CreateLogger(), nil)
if state.Step%10_000_000 == 0 { // emulation shouldn't take more than 20 B steps
t.Logf("Completed %d steps", state.Step) 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)
}
}
memUsage := state.Memory.PageCount() * memory.PageSize
t.Logf("Completed in %d steps. cannon memory usage: %d KiB", state.Step, memUsage/1024/1024.0)
require.True(t, state.Exited, "must complete program")
require.Equal(t, uint8(0), state.ExitCode, "exit with 0")
require.Less(t, memUsage, test.maxMemoryUsageCheck, "memory allocation is too large")
})
} }
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")
} }
...@@ -143,6 +143,30 @@ func (m *InstrumentedState) handleSyscall() error { ...@@ -143,6 +143,30 @@ func (m *InstrumentedState) handleSyscall() error {
case exec.SysOpen: case exec.SysOpen:
v0 = exec.SysErrorSignal v0 = exec.SysErrorSignal
v1 = exec.MipsEBADF v1 = exec.MipsEBADF
case exec.SysClockGetTime:
switch a0 {
case exec.ClockGettimeRealtimeFlag, exec.ClockGettimeMonotonicFlag:
v0, v1 = 0, 0
var secs, nsecs uint32
if a0 == exec.ClockGettimeMonotonicFlag {
// monotonic clock_gettime is used by Go guest programs for goroutine scheduling and to implement
// `time.Sleep` (and other sleep related operations).
secs = uint32(m.state.Step / exec.HZ)
nsecs = uint32((m.state.Step % exec.HZ) * (1_000_000_000 / exec.HZ))
} // else realtime set to Unix Epoch
effAddr := a1 & 0xFFffFFfc
m.memoryTracker.TrackMemAccess(effAddr)
m.state.Memory.SetMemory(effAddr, secs)
m.memoryTracker.TrackMemAccess2(effAddr + 4)
m.state.Memory.SetMemory(effAddr+4, nsecs)
default:
v0 = exec.SysErrorSignal
v1 = exec.MipsEINVAL
}
case exec.SysGetpid:
v0 = 0
v1 = 0
case exec.SysMunmap: case exec.SysMunmap:
case exec.SysGetAffinity: case exec.SysGetAffinity:
case exec.SysMadvise: case exec.SysMadvise:
...@@ -173,7 +197,6 @@ func (m *InstrumentedState) handleSyscall() error { ...@@ -173,7 +197,6 @@ func (m *InstrumentedState) handleSyscall() error {
case exec.SysTimerCreate: case exec.SysTimerCreate:
case exec.SysTimerSetTime: case exec.SysTimerSetTime:
case exec.SysTimerDelete: case exec.SysTimerDelete:
case exec.SysClockGetTime:
default: default:
m.Traceback() m.Traceback()
panic(fmt.Sprintf("unrecognized syscall: %d", syscallNum)) panic(fmt.Sprintf("unrecognized syscall: %d", syscallNum))
......
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded" "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
) )
...@@ -33,6 +34,7 @@ type ExpectedMTState struct { ...@@ -33,6 +34,7 @@ type ExpectedMTState struct {
prestateActiveThreadOrig ExpectedThreadState // Cached for internal use prestateActiveThreadOrig ExpectedThreadState // Cached for internal use
ActiveThreadId uint32 ActiveThreadId uint32
threadExpectations map[uint32]*ExpectedThreadState threadExpectations map[uint32]*ExpectedThreadState
memoryExpectations *memory.Memory
} }
type ExpectedThreadState struct { type ExpectedThreadState struct {
...@@ -81,6 +83,7 @@ func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState { ...@@ -81,6 +83,7 @@ func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState {
prestateActiveThreadOrig: *newExpectedThreadState(currentThread), // Cache prestate thread for internal use prestateActiveThreadOrig: *newExpectedThreadState(currentThread), // Cache prestate thread for internal use
ActiveThreadId: currentThread.ThreadId, ActiveThreadId: currentThread.ThreadId,
threadExpectations: expectedThreads, threadExpectations: expectedThreads,
memoryExpectations: fromState.Memory.Copy(),
} }
} }
...@@ -109,6 +112,17 @@ func (e *ExpectedMTState) ExpectStep() { ...@@ -109,6 +112,17 @@ func (e *ExpectedMTState) ExpectStep() {
e.StepsSinceLastContextSwitch += 1 e.StepsSinceLastContextSwitch += 1
} }
func (e *ExpectedMTState) ExpectMemoryWrite(addr uint32, val uint32) {
e.memoryExpectations.SetMemory(addr, val)
e.MemoryRoot = e.memoryExpectations.MerkleRoot()
}
func (e *ExpectedMTState) ExpectMemoryWriteMultiple(addr uint32, val uint32, addr2 uint32, val2 uint32) {
e.memoryExpectations.SetMemory(addr, val)
e.memoryExpectations.SetMemory(addr2, val)
e.MemoryRoot = e.memoryExpectations.MerkleRoot()
}
func (e *ExpectedMTState) ExpectPreemption(preState *multithreaded.State) { func (e *ExpectedMTState) ExpectPreemption(preState *multithreaded.State) {
e.ActiveThreadId = FindNextThread(preState).ThreadId e.ActiveThreadId = FindNextThread(preState).ThreadId
e.StepsSinceLastContextSwitch = 0 e.StepsSinceLastContextSwitch = 0
......
...@@ -488,8 +488,8 @@ func TestHelloEVM(t *testing.T) { ...@@ -488,8 +488,8 @@ func TestHelloEVM(t *testing.T) {
// verify the post-state matches. // verify the post-state matches.
// TODO: maybe more readable to decode the evmPost state, and do attribute-wise comparison. // TODO: maybe more readable to decode the evmPost state, and do attribute-wise comparison.
goPost, _ := goVm.GetState().EncodeWitness() goPost, _ := goVm.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(), require.Equalf(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM") "mipsevm produced different state than EVM. insn: %x", insn)
} }
end := time.Now() end := time.Now()
delta := end.Sub(start) delta := end.Sub(start)
......
...@@ -558,6 +558,111 @@ func TestEVM_SysOpen(t *testing.T) { ...@@ -558,6 +558,111 @@ func TestEVM_SysOpen(t *testing.T) {
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
} }
func TestEVM_SysGetPID(t *testing.T) {
var tracer *tracing.Hooks
goVm, state, contracts := setup(t, 1929)
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = exec.SysGetpid // Set syscall number
step := state.Step
// Set up post-state expectations
expected := mttestutil.NewExpectedMTState(state)
expected.ExpectStep()
expected.ActiveThread().Registers[2] = 0
expected.ActiveThread().Registers[7] = 0
// State transition
var err error
var stepWitness *mipsevm.StepWitness
stepWitness, err = goVm.Step(true)
require.NoError(t, err)
// Validate post-state
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
}
func TestEVM_SysClockGettimeMonotonic(t *testing.T) {
testEVM_SysClockGettime(t, exec.ClockGettimeMonotonicFlag)
}
func TestEVM_SysClockGettimeRealtime(t *testing.T) {
testEVM_SysClockGettime(t, exec.ClockGettimeRealtimeFlag)
}
func testEVM_SysClockGettime(t *testing.T, clkid uint32) {
var tracer *tracing.Hooks
cases := []struct {
name string
timespecAddr uint32
}{
{"aligned timespec address", 0x1000},
{"unaligned timespec address", 0x1003},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
goVm, state, contracts := setup(t, 2101)
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = exec.SysClockGetTime // Set syscall number
state.GetRegistersRef()[4] = clkid // a0
state.GetRegistersRef()[5] = c.timespecAddr // a1
step := state.Step
expected := mttestutil.NewExpectedMTState(state)
expected.ExpectStep()
expected.ActiveThread().Registers[2] = 0
expected.ActiveThread().Registers[7] = 0
next := state.Step + 1
var secs, nsecs uint32
if clkid == exec.ClockGettimeMonotonicFlag {
secs = uint32(next / exec.HZ)
nsecs = uint32((next % exec.HZ) * (1_000_000_000 / exec.HZ))
}
effAddr := c.timespecAddr & 0xFFffFFfc
expected.ExpectMemoryWrite(effAddr, secs)
expected.ExpectMemoryWrite(effAddr+4, nsecs)
var err error
var stepWitness *mipsevm.StepWitness
stepWitness, err = goVm.Step(true)
require.NoError(t, err)
// Validate post-state
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
})
}
}
func TestEVM_SysClockGettimeNonMonotonic(t *testing.T) {
var tracer *tracing.Hooks
goVm, state, contracts := setup(t, 2101)
timespecAddr := uint32(0x1000)
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = exec.SysClockGetTime // Set syscall number
state.GetRegistersRef()[4] = 0xDEAD // a0 - invalid clockid
state.GetRegistersRef()[5] = timespecAddr // a1
step := state.Step
expected := mttestutil.NewExpectedMTState(state)
expected.ExpectStep()
expected.ActiveThread().Registers[2] = exec.SysErrorSignal
expected.ActiveThread().Registers[7] = exec.MipsEINVAL
var err error
var stepWitness *mipsevm.StepWitness
stepWitness, err = goVm.Step(true)
require.NoError(t, err)
// Validate post-state
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
}
var NoopSyscalls = map[string]uint32{ var NoopSyscalls = map[string]uint32{
"SysGetAffinity": 4240, "SysGetAffinity": 4240,
"SysMadvise": 4218, "SysMadvise": 4218,
...@@ -589,7 +694,6 @@ var NoopSyscalls = map[string]uint32{ ...@@ -589,7 +694,6 @@ var NoopSyscalls = map[string]uint32{
"SysTimerCreate": 4257, "SysTimerCreate": 4257,
"SysTimerSetTime": 4258, "SysTimerSetTime": 4258,
"SysTimerDelete": 4261, "SysTimerDelete": 4261,
"SysClockGetTime": 4263,
} }
func TestEVM_NoopSyscall(t *testing.T) { func TestEVM_NoopSyscall(t *testing.T) {
...@@ -627,7 +731,7 @@ func TestEVM_UnsupportedSyscall(t *testing.T) { ...@@ -627,7 +731,7 @@ func TestEVM_UnsupportedSyscall(t *testing.T) {
var tracer *tracing.Hooks var tracer *tracing.Hooks
var NoopSyscallNums = maps.Values(NoopSyscalls) var NoopSyscallNums = maps.Values(NoopSyscalls)
var SupportedSyscalls = []uint32{exec.SysMmap, exec.SysBrk, exec.SysClone, exec.SysExitGroup, exec.SysRead, exec.SysWrite, exec.SysFcntl, exec.SysExit, exec.SysSchedYield, exec.SysGetTID, exec.SysFutex, exec.SysOpen, exec.SysNanosleep} var SupportedSyscalls = []uint32{exec.SysMmap, exec.SysBrk, exec.SysClone, exec.SysExitGroup, exec.SysRead, exec.SysWrite, exec.SysFcntl, exec.SysExit, exec.SysSchedYield, exec.SysGetTID, exec.SysFutex, exec.SysOpen, exec.SysNanosleep, exec.SysClockGetTime, exec.SysGetpid}
unsupportedSyscalls := make([]uint32, 0, 400) unsupportedSyscalls := make([]uint32, 0, 400)
for i := 4000; i < 4400; i++ { for i := 4000; i < 4400; i++ {
candidate := uint32(i) candidate := uint32(i)
......
...@@ -120,14 +120,19 @@ func ClaimTestOracle(t *testing.T) (po mipsevm.PreimageOracle, stdOut string, st ...@@ -120,14 +120,19 @@ func ClaimTestOracle(t *testing.T) (po mipsevm.PreimageOracle, stdOut string, st
return oracle, fmt.Sprintf("computing %d * %d + %d\nclaim %d is good!\n", s, a, b, s*a+b), "started!" return oracle, fmt.Sprintf("computing %d * %d + %d\nclaim %d is good!\n", s, a, b, s*a+b), "started!"
} }
func AllocOracle(t *testing.T, numAllocs int) *TestOracle { func AllocOracle(t *testing.T, numAllocs int, allocSize int) *TestOracle {
return &TestOracle{ return &TestOracle{
hint: func(v []byte) {}, hint: func(v []byte) {},
getPreimage: func(k [32]byte) []byte { getPreimage: func(k [32]byte) []byte {
if k != preimage.LocalIndexKey(0).PreimageKey() { switch k {
case preimage.LocalIndexKey(0).PreimageKey():
return binary.LittleEndian.AppendUint64(nil, uint64(numAllocs))
case preimage.LocalIndexKey(1).PreimageKey():
return binary.LittleEndian.AppendUint64(nil, uint64(allocSize))
default:
t.Fatalf("invalid preimage request for %x", k) t.Fatalf("invalid preimage request for %x", k)
} }
return binary.LittleEndian.AppendUint64(nil, uint64(numAllocs)) panic("unreachable")
}, },
} }
} }
......
...@@ -12,20 +12,27 @@ func main() { ...@@ -12,20 +12,27 @@ func main() {
var mem []byte var mem []byte
po := preimage.NewOracleClient(preimage.ClientPreimageChannel()) po := preimage.NewOracleClient(preimage.ClientPreimageChannel())
numAllocs := binary.LittleEndian.Uint64(po.Get(preimage.LocalIndexKey(0))) numAllocs := binary.LittleEndian.Uint64(po.Get(preimage.LocalIndexKey(0)))
allocSize := binary.LittleEndian.Uint64(po.Get(preimage.LocalIndexKey(1)))
fmt.Printf("alloc program. numAllocs=%d\n", numAllocs) fmt.Printf("alloc program. numAllocs=%d allocSize=%d\n", numAllocs, allocSize)
var alloc int var alloc int
for i := 0; i < int(numAllocs); i++ { for i := 0; i < int(numAllocs); i++ {
mem = make([]byte, 32*1024*1024) mem = make([]byte, allocSize)
alloc += len(mem) alloc += len(mem)
// touch a couple pages to prevent the runtime from overcommitting memory // touch a couple pages to prevent the runtime from overcommitting memory
for j := 0; j < len(mem); j += 1024 { for j := 0; j < len(mem); j += 1024 {
mem[j] = 0xFF mem[j] = 0xFF
} }
fmt.Printf("allocated %d bytes\n", alloc) printGCStats(alloc)
} }
fmt.Println("alloc program exit")
printGCStats(alloc)
}
func printGCStats(alloc int) {
var m runtime.MemStats var m runtime.MemStats
runtime.ReadMemStats(&m) runtime.ReadMemStats(&m)
fmt.Printf("alloc program exit. memstats: heap_alloc=%d frees=%d mallocs=%d\n", m.HeapAlloc, m.Frees, m.Mallocs) fmt.Printf("allocated %d bytes. memstats: heap_alloc=%d next_gc=%d frees=%d mallocs=%d num_gc=%d\n",
alloc, m.HeapAlloc, m.NextGC, m.Frees, m.Mallocs, m.NumGC)
} }
...@@ -239,8 +239,15 @@ func (s *SourceMapTracer) info(codeAddr common.Address, pc uint64) string { ...@@ -239,8 +239,15 @@ func (s *SourceMapTracer) info(codeAddr common.Address, pc uint64) string {
func (s *SourceMapTracer) OnOpCode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { func (s *SourceMapTracer) OnOpCode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
op := vm.OpCode(opcode) op := vm.OpCode(opcode)
if op.IsPush() { if op.IsPush() {
start := uint64(op) - uint64(vm.PUSH1) + 1 var val []byte
val := scope.StackData()[:start] sc, ok := scope.(*vm.ScopeContext)
if ok {
start := pc + 1
end := uint64(op) - uint64(vm.PUSH1) + 1
val = sc.Contract.Code[start : start+end]
} else {
val = []byte("N/A")
}
fmt.Fprintf(s.out, "%-40s : pc %x opcode %s (%x)\n", s.info(scope.Address(), pc), pc, op.String(), val) fmt.Fprintf(s.out, "%-40s : pc %x opcode %s (%x)\n", s.info(scope.Address(), pc), pc, op.String(), val)
return return
} }
......
...@@ -359,10 +359,11 @@ func DiffTestUtils() { ...@@ -359,10 +359,11 @@ func DiffTestUtils() {
// Print the output // Print the output
fmt.Print(hexutil.Encode(packed[32:])) fmt.Print(hexutil.Encode(packed[32:]))
case "cannonMemoryProof": case "cannonMemoryProof":
// <pc, insn, [memAddr, memValue]> // <pc, insn, [memAddr, memValue], [memAddr2, memValue2]>
// Generates a memory proof of `memAddr` for a trie containing memValue and memValue2
mem := memory.NewMemory() mem := memory.NewMemory()
if len(args) != 3 && len(args) != 5 { if len(args) != 3 && len(args) != 5 && len(args) != 7 {
panic("Error: cannonMemoryProofWithProof requires 2 or 4 arguments") panic("Error: cannonMemoryProofWithProof requires 2, 4, or 6 arguments")
} }
pc, err := strconv.ParseUint(args[1], 10, 32) pc, err := strconv.ParseUint(args[1], 10, 32)
checkErr(err, "Error decoding addr") checkErr(err, "Error decoding addr")
...@@ -371,7 +372,7 @@ func DiffTestUtils() { ...@@ -371,7 +372,7 @@ func DiffTestUtils() {
mem.SetMemory(uint32(pc), uint32(insn)) mem.SetMemory(uint32(pc), uint32(insn))
var insnProof, memProof [896]byte var insnProof, memProof [896]byte
if len(args) == 5 { if len(args) >= 5 {
memAddr, err := strconv.ParseUint(args[3], 10, 32) memAddr, err := strconv.ParseUint(args[3], 10, 32)
checkErr(err, "Error decoding memAddr") checkErr(err, "Error decoding memAddr")
memValue, err := strconv.ParseUint(args[4], 10, 32) memValue, err := strconv.ParseUint(args[4], 10, 32)
...@@ -379,6 +380,14 @@ func DiffTestUtils() { ...@@ -379,6 +380,14 @@ func DiffTestUtils() {
mem.SetMemory(uint32(memAddr), uint32(memValue)) mem.SetMemory(uint32(memAddr), uint32(memValue))
memProof = mem.MerkleProof(uint32(memAddr)) memProof = mem.MerkleProof(uint32(memAddr))
} }
if len(args) == 7 {
memAddr, err := strconv.ParseUint(args[5], 10, 32)
checkErr(err, "Error decoding memAddr")
memValue, err := strconv.ParseUint(args[6], 10, 32)
checkErr(err, "Error decoding memValue")
mem.SetMemory(uint32(memAddr), uint32(memValue))
memProof = mem.MerkleProof(uint32(memAddr))
}
insnProof = mem.MerkleProof(uint32(pc)) insnProof = mem.MerkleProof(uint32(pc))
output := struct { output := struct {
...@@ -391,6 +400,40 @@ func DiffTestUtils() { ...@@ -391,6 +400,40 @@ func DiffTestUtils() {
packed, err := cannonMemoryProofArgs.Pack(&output) packed, err := cannonMemoryProofArgs.Pack(&output)
checkErr(err, "Error encoding output") checkErr(err, "Error encoding output")
fmt.Print(hexutil.Encode(packed[32:])) fmt.Print(hexutil.Encode(packed[32:]))
case "cannonMemoryProof2":
// <pc, insn, [memAddr, memValue], memAddr2>
// Generates a memory proof of memAddr2 for a trie containing memValue
mem := memory.NewMemory()
if len(args) != 6 {
panic("Error: cannonMemoryProofWithProof2 requires 5 arguments")
}
pc, err := strconv.ParseUint(args[1], 10, 32)
checkErr(err, "Error decoding addr")
insn, err := strconv.ParseUint(args[2], 10, 32)
checkErr(err, "Error decoding insn")
mem.SetMemory(uint32(pc), uint32(insn))
var memProof [896]byte
memAddr, err := strconv.ParseUint(args[3], 10, 32)
checkErr(err, "Error decoding memAddr")
memValue, err := strconv.ParseUint(args[4], 10, 32)
checkErr(err, "Error decoding memValue")
mem.SetMemory(uint32(memAddr), uint32(memValue))
memAddr2, err := strconv.ParseUint(args[5], 10, 32)
checkErr(err, "Error decoding memAddr")
memProof = mem.MerkleProof(uint32(memAddr2))
output := struct {
MemRoot common.Hash
Proof []byte
}{
MemRoot: mem.MerkleRoot(),
Proof: memProof[:],
}
packed, err := cannonMemoryProofArgs.Pack(&output)
checkErr(err, "Error encoding output")
fmt.Print(hexutil.Encode(packed[32:]))
case "cannonMemoryProofWrongLeaf": case "cannonMemoryProofWrongLeaf":
// <pc, insn, memAddr, memValue> // <pc, insn, memAddr, memValue>
mem := memory.NewMemory() mem := memory.NewMemory()
......
...@@ -51,8 +51,24 @@ fi ...@@ -51,8 +51,24 @@ fi
# Initialize an array to hold relevant function names. # Initialize an array to hold relevant function names.
NEW_OR_MODIFIED_TEST_NAMES="" NEW_OR_MODIFIED_TEST_NAMES=""
# these tests are too expensive to run heavily
IGNORED_FUZZ_TESTS=("MIPS.t.sol" "MIPS2.t.sol")
# Process each changed file. # Process each changed file.
for FILE in $CHANGED_FILES; do for FILE in $CHANGED_FILES; do
IGNORED=false
for TEST in "${IGNORED_FUZZ_TESTS[@]}"; do
FILENAME=$(basename "$FILE")
if [[ "$TEST" == "$FILENAME" ]]; then
IGNORED=true
break
fi
done
if $IGNORED; then
echo "skipping $FILE"
continue
fi
# Get the diff for the file. # Get the diff for the file.
DIFF=$(git diff origin/develop...HEAD --unified=0 -- "$FILE") DIFF=$(git diff origin/develop...HEAD --unified=0 -- "$FILE")
......
...@@ -144,12 +144,12 @@ ...@@ -144,12 +144,12 @@
"sourceCodeHash": "0x255a3f78036745feb57da2e16a93a83fe3f52944fec42029da9b9eaa147db11c" "sourceCodeHash": "0x255a3f78036745feb57da2e16a93a83fe3f52944fec42029da9b9eaa147db11c"
}, },
"src/cannon/MIPS.sol": { "src/cannon/MIPS.sol": {
"initCodeHash": "0xd151c45f4b4b2b96d111d37c78290bb461878df4481dbc4bfdd14428e177acc5", "initCodeHash": "0x6add59adb849ec02e13b33df7efd439ca80f6a8ceefdf69ebcb0963c0167da23",
"sourceCodeHash": "0x23543a88057256eb8fd3b4c52d82bb97e359134bd760bdf144e5f697307ce176" "sourceCodeHash": "0xee1aef5a502f9491b7b83dab46ea2f0fc286f87ace31edcc1367c840d462bdfe"
}, },
"src/cannon/MIPS2.sol": { "src/cannon/MIPS2.sol": {
"initCodeHash": "0xf5e2bca4ba0c504ffa68f1ce5fbf4349b1fa892034777d77803d9111aed279fa", "initCodeHash": "0x3430ef0ee7697e7b98589deffb3a303cd8ce73bdc69df2edc5c957cee7486a72",
"sourceCodeHash": "0x3c3716ac78180fc71d67b0f849b2ec28891b2e065d33d4d70b4b7279a12d4b66" "sourceCodeHash": "0xb05a2081aa93881871d08bd95610639dfb64e5d3ce0e8fa5fd212e7ae3c3fe60"
}, },
"src/cannon/PreimageOracle.sol": { "src/cannon/PreimageOracle.sol": {
"initCodeHash": "0x801e52f9c8439fcf7089575fa93272dfb874641dbfc7d82f36d979c987271c0b", "initCodeHash": "0x801e52f9c8439fcf7089575fa93272dfb874641dbfc7d82f36d979c987271c0b",
......
...@@ -64,5 +64,10 @@ ...@@ -64,5 +64,10 @@
], ],
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
},
{
"inputs": [],
"name": "InvalidMemoryProof",
"type": "error"
} }
] ]
\ No newline at end of file
...@@ -56,5 +56,15 @@ ...@@ -56,5 +56,15 @@
"inputs": [], "inputs": [],
"name": "InvalidExitedValue", "name": "InvalidExitedValue",
"type": "error" "type": "error"
},
{
"inputs": [],
"name": "InvalidMemoryProof",
"type": "error"
},
{
"inputs": [],
"name": "InvalidSecondMemoryProof",
"type": "error"
} }
] ]
\ No newline at end of file
...@@ -44,8 +44,8 @@ contract MIPS is ISemver { ...@@ -44,8 +44,8 @@ contract MIPS is ISemver {
} }
/// @notice The semantic version of the MIPS contract. /// @notice The semantic version of the MIPS contract.
/// @custom:semver 1.1.1-beta.2 /// @custom:semver 1.1.1-beta.3
string public constant version = "1.1.1-beta.2"; string public constant version = "1.1.1-beta.3";
/// @notice The preimage oracle contract. /// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE; IPreimageOracle internal immutable ORACLE;
......
...@@ -8,6 +8,7 @@ import { MIPSSyscalls as sys } from "src/cannon/libraries/MIPSSyscalls.sol"; ...@@ -8,6 +8,7 @@ import { MIPSSyscalls as sys } from "src/cannon/libraries/MIPSSyscalls.sol";
import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol"; import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol";
import { MIPSInstructions as ins } from "src/cannon/libraries/MIPSInstructions.sol"; import { MIPSInstructions as ins } from "src/cannon/libraries/MIPSInstructions.sol";
import { VMStatuses } from "src/dispute/lib/Types.sol"; import { VMStatuses } from "src/dispute/lib/Types.sol";
import { InvalidMemoryProof, InvalidSecondMemoryProof } from "src/cannon/libraries/CannonErrors.sol";
/// @title MIPS2 /// @title MIPS2
/// @notice The MIPS2 contract emulates a single MIPS instruction. /// @notice The MIPS2 contract emulates a single MIPS instruction.
...@@ -51,8 +52,8 @@ contract MIPS2 is ISemver { ...@@ -51,8 +52,8 @@ contract MIPS2 is ISemver {
} }
/// @notice The semantic version of the MIPS2 contract. /// @notice The semantic version of the MIPS2 contract.
/// @custom:semver 1.0.0-beta.6 /// @custom:semver 1.0.0-beta.7
string public constant version = "1.0.0-beta.6"; string public constant version = "1.0.0-beta.7";
/// @notice The preimage oracle contract. /// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE; IPreimageOracle internal immutable ORACLE;
...@@ -383,6 +384,45 @@ contract MIPS2 is ISemver { ...@@ -383,6 +384,45 @@ contract MIPS2 is ISemver {
} else if (syscall_no == sys.SYS_OPEN) { } else if (syscall_no == sys.SYS_OPEN) {
v0 = sys.SYS_ERROR_SIGNAL; v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EBADF; v1 = sys.EBADF;
} else if (syscall_no == sys.SYS_CLOCKGETTIME) {
if (a0 == sys.CLOCK_GETTIME_REALTIME_FLAG || a0 == sys.CLOCK_GETTIME_MONOTONIC_FLAG) {
v0 = 0;
v1 = 0;
uint32 secs = 0;
uint32 nsecs = 0;
if (a0 == sys.CLOCK_GETTIME_MONOTONIC_FLAG) {
secs = uint32(state.step / sys.HZ);
nsecs = uint32((state.step % sys.HZ) * (1_000_000_000 / sys.HZ));
}
uint32 effAddr = a1 & 0xFFffFFfc;
// First verify the effAddr path
if (
!MIPSMemory.isValidProof(
state.memRoot, effAddr, MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1)
)
) {
revert InvalidMemoryProof();
}
// Recompute the new root after updating effAddr
state.memRoot =
MIPSMemory.writeMem(effAddr, MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1), secs);
// Verify the second memory proof against the newly computed root
if (
!MIPSMemory.isValidProof(
state.memRoot, effAddr + 4, MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 2)
)
) {
revert InvalidSecondMemoryProof();
}
state.memRoot =
MIPSMemory.writeMem(effAddr + 4, MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 2), nsecs);
} else {
v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EINVAL;
}
} else if (syscall_no == sys.SYS_GETPID) {
v0 = 0;
v1 = 0;
} else if (syscall_no == sys.SYS_MUNMAP) { } else if (syscall_no == sys.SYS_MUNMAP) {
// ignored // ignored
} else if (syscall_no == sys.SYS_GETAFFINITY) { } else if (syscall_no == sys.SYS_GETAFFINITY) {
...@@ -443,8 +483,6 @@ contract MIPS2 is ISemver { ...@@ -443,8 +483,6 @@ contract MIPS2 is ISemver {
// ignored // ignored
} else if (syscall_no == sys.SYS_TIMERDELETE) { } else if (syscall_no == sys.SYS_TIMERDELETE) {
// ignored // ignored
} else if (syscall_no == sys.SYS_CLOCKGETTIME) {
// ignored
} else { } else {
revert("MIPS2: unimplemented syscall"); revert("MIPS2: unimplemented syscall");
} }
......
...@@ -7,6 +7,8 @@ import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; ...@@ -7,6 +7,8 @@ import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol";
/// @title IMIPS /// @title IMIPS
/// @notice Interface for the MIPS contract. /// @notice Interface for the MIPS contract.
interface IMIPS is ISemver { interface IMIPS is ISemver {
error InvalidMemoryProof();
function oracle() external view returns (IPreimageOracle oracle_); function oracle() external view returns (IPreimageOracle oracle_);
function step(bytes memory _stateData, bytes memory _proof, bytes32 _localContext) external returns (bytes32); function step(bytes memory _stateData, bytes memory _proof, bytes32 _localContext) external returns (bytes32);
} }
...@@ -7,6 +7,8 @@ import { ISemver } from "src/universal/interfaces/ISemver.sol"; ...@@ -7,6 +7,8 @@ import { ISemver } from "src/universal/interfaces/ISemver.sol";
/// @notice Interface for the MIPS2 contract. /// @notice Interface for the MIPS2 contract.
interface IMIPS2 is ISemver { interface IMIPS2 is ISemver {
error InvalidExitedValue(); error InvalidExitedValue();
error InvalidMemoryProof();
error InvalidSecondMemoryProof();
function step(bytes memory _stateData, bytes memory _proof, bytes32 _localContext) external returns (bytes32); function step(bytes memory _stateData, bytes memory _proof, bytes32 _localContext) external returns (bytes32);
} }
...@@ -54,3 +54,9 @@ error BondTransferFailed(); ...@@ -54,3 +54,9 @@ error BondTransferFailed();
/// @notice Thrown when the value of the exited boolean is not 0 or 1. /// @notice Thrown when the value of the exited boolean is not 0 or 1.
error InvalidExitedValue(); error InvalidExitedValue();
/// @notice Thrown when reading an invalid memory
error InvalidMemoryProof();
/// @notice Thrown when the second memory location is invalid
error InvalidSecondMemoryProof();
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
import "src/cannon/libraries/CannonErrors.sol";
library MIPSMemory { library MIPSMemory {
/// @notice Reads a 32-bit value from memory. /// @notice Reads a 32-bit value from memory.
/// @param _memRoot The current memory root /// @param _memRoot The current memory root
...@@ -8,6 +10,28 @@ library MIPSMemory { ...@@ -8,6 +10,28 @@ library MIPSMemory {
/// @param _proofOffset The offset of the memory proof in calldata. /// @param _proofOffset The offset of the memory proof in calldata.
/// @return out_ The hashed MIPS state. /// @return out_ The hashed MIPS state.
function readMem(bytes32 _memRoot, uint32 _addr, uint256 _proofOffset) internal pure returns (uint32 out_) { function readMem(bytes32 _memRoot, uint32 _addr, uint256 _proofOffset) internal pure returns (uint32 out_) {
bool valid;
(out_, valid) = readMemUnchecked(_memRoot, _addr, _proofOffset);
if (!valid) {
revert InvalidMemoryProof();
}
}
/// @notice Reads a 32-bit value from memory.
/// @param _memRoot The current memory root
/// @param _addr The address to read from.
/// @param _proofOffset The offset of the memory proof in calldata.
/// @return out_ The hashed MIPS state.
/// valid_ Whether the proof is valid.
function readMemUnchecked(
bytes32 _memRoot,
uint32 _addr,
uint256 _proofOffset
)
internal
pure
returns (uint32 out_, bool valid_)
{
unchecked { unchecked {
validateMemoryProofAvailability(_proofOffset); validateMemoryProofAvailability(_proofOffset);
assembly { assembly {
...@@ -38,14 +62,12 @@ library MIPSMemory { ...@@ -38,14 +62,12 @@ library MIPSMemory {
} }
// Verify the root matches. // Verify the root matches.
if iszero(eq(node, _memRoot)) { valid_ := eq(node, _memRoot)
mstore(0, 0x0badf00d) if valid_ {
revert(0, 32) // Bits to shift = (32 - 4 - (addr % 32)) * 8
let shamt := shl(3, sub(sub(32, 4), and(_addr, 31)))
out_ := and(shr(shamt, leaf), 0xFFffFFff)
} }
// Bits to shift = (32 - 4 - (addr % 32)) * 8
let shamt := shl(3, sub(sub(32, 4), and(_addr, 31)))
out_ := and(shr(shamt, leaf), 0xFFffFFff)
} }
} }
} }
...@@ -97,6 +119,15 @@ library MIPSMemory { ...@@ -97,6 +119,15 @@ library MIPSMemory {
} }
} }
/// @notice Verifies a memory proof.
/// @param _memRoot The expected memory root
/// @param _addr The _addr proven.
/// @param _proofOffset The offset of the memory proof in calldata.
/// @return valid_ True iff it is a valid proof.
function isValidProof(bytes32 _memRoot, uint32 _addr, uint256 _proofOffset) internal pure returns (bool valid_) {
(, valid_) = readMemUnchecked(_memRoot, _addr, _proofOffset);
}
/// @notice Computes the offset of a memory proof in the calldata. /// @notice Computes the offset of a memory proof in the calldata.
/// @param _proofDataOffset The offset of the set of all memory proof data within calldata (proof.offset) /// @param _proofDataOffset The offset of the set of all memory proof data within calldata (proof.offset)
/// Equal to the offset of the first memory proof (at _proofIndex 0). /// Equal to the offset of the first memory proof (at _proofIndex 0).
......
...@@ -41,6 +41,8 @@ library MIPSSyscalls { ...@@ -41,6 +41,8 @@ library MIPSSyscalls {
uint32 internal constant SYS_FUTEX = 4238; uint32 internal constant SYS_FUTEX = 4238;
uint32 internal constant SYS_OPEN = 4005; uint32 internal constant SYS_OPEN = 4005;
uint32 internal constant SYS_NANOSLEEP = 4166; uint32 internal constant SYS_NANOSLEEP = 4166;
uint32 internal constant SYS_CLOCKGETTIME = 4263;
uint32 internal constant SYS_GETPID = 4020;
// unused syscalls // unused syscalls
uint32 internal constant SYS_MUNMAP = 4091; uint32 internal constant SYS_MUNMAP = 4091;
uint32 internal constant SYS_GETAFFINITY = 4240; uint32 internal constant SYS_GETAFFINITY = 4240;
...@@ -74,7 +76,6 @@ library MIPSSyscalls { ...@@ -74,7 +76,6 @@ library MIPSSyscalls {
uint32 internal constant SYS_TIMERCREATE = 4257; uint32 internal constant SYS_TIMERCREATE = 4257;
uint32 internal constant SYS_TIMERSETTIME = 4258; uint32 internal constant SYS_TIMERSETTIME = 4258;
uint32 internal constant SYS_TIMERDELETE = 4261; uint32 internal constant SYS_TIMERDELETE = 4261;
uint32 internal constant SYS_CLOCKGETTIME = 4263;
uint32 internal constant FD_STDIN = 0; uint32 internal constant FD_STDIN = 0;
uint32 internal constant FD_STDOUT = 1; uint32 internal constant FD_STDOUT = 1;
...@@ -97,6 +98,9 @@ library MIPSSyscalls { ...@@ -97,6 +98,9 @@ library MIPSSyscalls {
uint32 internal constant FUTEX_EMPTY_ADDR = 0xFF_FF_FF_FF; uint32 internal constant FUTEX_EMPTY_ADDR = 0xFF_FF_FF_FF;
uint32 internal constant SCHED_QUANTUM = 100_000; uint32 internal constant SCHED_QUANTUM = 100_000;
uint32 internal constant HZ = 10_000_000;
uint32 internal constant CLOCK_GETTIME_REALTIME_FLAG = 0;
uint32 internal constant CLOCK_GETTIME_MONOTONIC_FLAG = 1;
/// @notice Start of the data segment. /// @notice Start of the data segment.
uint32 internal constant PROGRAM_BREAK = 0x40000000; uint32 internal constant PROGRAM_BREAK = 0x40000000;
uint32 internal constant HEAP_END = 0x60000000; uint32 internal constant HEAP_END = 0x60000000;
......
...@@ -6,7 +6,7 @@ import { MIPS } from "src/cannon/MIPS.sol"; ...@@ -6,7 +6,7 @@ import { MIPS } from "src/cannon/MIPS.sol";
import { PreimageOracle } from "src/cannon/PreimageOracle.sol"; import { PreimageOracle } from "src/cannon/PreimageOracle.sol";
import { MIPSInstructions } from "src/cannon/libraries/MIPSInstructions.sol"; import { MIPSInstructions } from "src/cannon/libraries/MIPSInstructions.sol";
import { MIPSSyscalls as sys } from "src/cannon/libraries/MIPSSyscalls.sol"; import { MIPSSyscalls as sys } from "src/cannon/libraries/MIPSSyscalls.sol";
import { InvalidExitedValue } from "src/cannon/libraries/CannonErrors.sol"; import { InvalidExitedValue, InvalidMemoryProof } from "src/cannon/libraries/CannonErrors.sol";
import "src/dispute/lib/Types.sol"; import "src/dispute/lib/Types.sol";
contract MIPS_Test is CommonTest { contract MIPS_Test is CommonTest {
...@@ -63,26 +63,28 @@ contract MIPS_Test is CommonTest { ...@@ -63,26 +63,28 @@ contract MIPS_Test is CommonTest {
/// @notice Tests that the mips step function fails when the value of the exited field is /// @notice Tests that the mips step function fails when the value of the exited field is
/// invalid (anything greater than 1). /// invalid (anything greater than 1).
/// @param _exited Value to set the exited field to. function test_step_invalidExitedValue_fails() external {
function testFuzz_step_invalidExitedValue_fails(uint8 _exited) external { // Bound to invalid exited values.
// Make sure the value of _exited is invalid. for (uint8 exited = 2; exited <= type(uint8).max && exited != 0;) {
_exited = uint8(bound(uint256(_exited), 2, type(uint8).max)); // Rest of this stuff doesn't matter very much, just setting up some state to edit.
// Here just using the parameters for the ADD test below.
// Rest of this stuff doesn't matter very much, just setting up some state to edit. uint32 insn = encodespec(17, 18, 8, 0x20);
// Here just using the parameters for the ADD test below. (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0);
uint32 insn = encodespec(17, 18, 8, 0x20);
(MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); // Compute the encoded state and manipulate it.
bytes memory enc = encodeState(state);
// Compute the encoded state and manipulate it. assembly {
bytes memory enc = encodeState(state); // Push offset by an additional 32 bytes (0x20) to account for length prefix
assembly { mstore8(add(add(enc, 0x20), 89), exited)
// Push offset by an additional 32 bytes (0x20) to account for length prefix }
mstore8(add(add(enc, 0x20), 89), _exited)
// Call the step function and expect a revert.
vm.expectRevert(InvalidExitedValue.selector);
mips.step(enc, proof, 0);
unchecked {
exited++;
}
} }
// Call the step function and expect a revert.
vm.expectRevert(InvalidExitedValue.selector);
mips.step(enc, proof, 0);
} }
function test_add_succeeds() external { function test_add_succeeds() external {
...@@ -1658,7 +1660,7 @@ contract MIPS_Test is CommonTest { ...@@ -1658,7 +1660,7 @@ contract MIPS_Test is CommonTest {
for (uint256 i = 0; i < proof.length; i++) { for (uint256 i = 0; i < proof.length; i++) {
proof[i] = 0x0; proof[i] = 0x0;
} }
vm.expectRevert(hex"000000000000000000000000000000000000000000000000000000000badf00d"); vm.expectRevert(InvalidMemoryProof.selector);
mips.step(encodeState(state), proof, 0); mips.step(encodeState(state), proof, 0);
} }
...@@ -1677,7 +1679,7 @@ contract MIPS_Test is CommonTest { ...@@ -1677,7 +1679,7 @@ contract MIPS_Test is CommonTest {
state.registers[2] = 4246; // exit_group syscall state.registers[2] = 4246; // exit_group syscall
state.registers[4] = 0x5; // a0 state.registers[4] = 0x5; // a0
vm.expectRevert(hex"000000000000000000000000000000000000000000000000000000000badf00d"); vm.expectRevert(InvalidMemoryProof.selector);
mips.step(encodeState(state), proof, 0); mips.step(encodeState(state), proof, 0);
} }
......
...@@ -27,11 +27,11 @@ contract DeploymentSummary is DeploymentSummaryCode { ...@@ -27,11 +27,11 @@ contract DeploymentSummary is DeploymentSummaryCode {
address internal constant l1StandardBridgeProxyAddress = 0x20A42a5a785622c6Ba2576B2D6e924aA82BFA11D; address internal constant l1StandardBridgeProxyAddress = 0x20A42a5a785622c6Ba2576B2D6e924aA82BFA11D;
address internal constant l2OutputOracleAddress = 0x60d37db59d0D14f7EA5c7425A2C03244E08B162D; address internal constant l2OutputOracleAddress = 0x60d37db59d0D14f7EA5c7425A2C03244E08B162D;
address internal constant l2OutputOracleProxyAddress = 0x39Af23E00F1e662025aA01b0cEdA19542B78DF99; address internal constant l2OutputOracleProxyAddress = 0x39Af23E00F1e662025aA01b0cEdA19542B78DF99;
address internal constant mipsAddress = 0x46cce16e68c41f7EBdf485EF825AB3b46143F738; address internal constant mipsAddress = 0x180CBe2EBb9F37D3a3C542DDc2546Fd160555a73;
address internal constant optimismMintableERC20FactoryAddress = 0x79c3114E5f89266e2C8842871Bce16D4e5076b1e; address internal constant optimismMintableERC20FactoryAddress = 0x79c3114E5f89266e2C8842871Bce16D4e5076b1e;
address internal constant optimismMintableERC20FactoryProxyAddress = 0xc7B87b2b892EA5C3CfF47168881FE168C00377FB; address internal constant optimismMintableERC20FactoryProxyAddress = 0xc7B87b2b892EA5C3CfF47168881FE168C00377FB;
address internal constant optimismPortalAddress = 0xBE8eE5CEA97De55Ae8b020E778A5C93C2Af81Ed4; address internal constant optimismPortalAddress = 0xb5A42f01EF5068F82C11fa1c4F9bBD4c8D346961;
address internal constant optimismPortal2Address = 0x5506077419b90A12C048500e2eBcafb4fC6Bab61; address internal constant optimismPortal2Address = 0x150581358018524994Fc29800b1783637943b103;
address internal constant optimismPortalProxyAddress = 0x1c23A6d89F95ef3148BCDA8E242cAb145bf9c0E4; address internal constant optimismPortalProxyAddress = 0x1c23A6d89F95ef3148BCDA8E242cAb145bf9c0E4;
address internal constant permissionedDelayedWETHProxyAddress = 0xd6EAF4c146261653EE059077B78ED088Add54309; address internal constant permissionedDelayedWETHProxyAddress = 0xd6EAF4c146261653EE059077B78ED088Add54309;
address internal constant preimageOracleAddress = 0x7A9Eab4CE99d157AeE7A02E95b366E972a2D5b0b; address internal constant preimageOracleAddress = 0x7A9Eab4CE99d157AeE7A02E95b366E972a2D5b0b;
...@@ -430,7 +430,7 @@ contract DeploymentSummary is DeploymentSummaryCode { ...@@ -430,7 +430,7 @@ contract DeploymentSummary is DeploymentSummaryCode {
value = hex"0000000000000000000000000000000000000000000000000000000000000003"; value = hex"0000000000000000000000000000000000000000000000000000000000000003";
vm.store(systemOwnerSafeAddress, slot, value); vm.store(systemOwnerSafeAddress, slot, value);
slot = hex"360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"; slot = hex"360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
value = hex"000000000000000000000000be8ee5cea97de55ae8b020e778a5c93c2af81ed4"; value = hex"000000000000000000000000b5a42f01ef5068f82c11fa1c4f9bbd4c8d346961";
vm.store(optimismPortalProxyAddress, slot, value); vm.store(optimismPortalProxyAddress, slot, value);
slot = hex"0000000000000000000000000000000000000000000000000000000000000000"; slot = hex"0000000000000000000000000000000000000000000000000000000000000000";
value = hex"0000000000000000000000000000000000000000000000000000000000000001"; value = hex"0000000000000000000000000000000000000000000000000000000000000001";
......
...@@ -27,11 +27,11 @@ contract DeploymentSummaryFaultProofs is DeploymentSummaryFaultProofsCode { ...@@ -27,11 +27,11 @@ contract DeploymentSummaryFaultProofs is DeploymentSummaryFaultProofsCode {
address internal constant l1StandardBridgeProxyAddress = 0x20A42a5a785622c6Ba2576B2D6e924aA82BFA11D; address internal constant l1StandardBridgeProxyAddress = 0x20A42a5a785622c6Ba2576B2D6e924aA82BFA11D;
address internal constant l2OutputOracleAddress = 0x60d37db59d0D14f7EA5c7425A2C03244E08B162D; address internal constant l2OutputOracleAddress = 0x60d37db59d0D14f7EA5c7425A2C03244E08B162D;
address internal constant l2OutputOracleProxyAddress = 0x39Af23E00F1e662025aA01b0cEdA19542B78DF99; address internal constant l2OutputOracleProxyAddress = 0x39Af23E00F1e662025aA01b0cEdA19542B78DF99;
address internal constant mipsAddress = 0x46cce16e68c41f7EBdf485EF825AB3b46143F738; address internal constant mipsAddress = 0x180CBe2EBb9F37D3a3C542DDc2546Fd160555a73;
address internal constant optimismMintableERC20FactoryAddress = 0x79c3114E5f89266e2C8842871Bce16D4e5076b1e; address internal constant optimismMintableERC20FactoryAddress = 0x79c3114E5f89266e2C8842871Bce16D4e5076b1e;
address internal constant optimismMintableERC20FactoryProxyAddress = 0xc7B87b2b892EA5C3CfF47168881FE168C00377FB; address internal constant optimismMintableERC20FactoryProxyAddress = 0xc7B87b2b892EA5C3CfF47168881FE168C00377FB;
address internal constant optimismPortalAddress = 0xBE8eE5CEA97De55Ae8b020E778A5C93C2Af81Ed4; address internal constant optimismPortalAddress = 0xb5A42f01EF5068F82C11fa1c4F9bBD4c8D346961;
address internal constant optimismPortal2Address = 0x5506077419b90A12C048500e2eBcafb4fC6Bab61; address internal constant optimismPortal2Address = 0x150581358018524994Fc29800b1783637943b103;
address internal constant optimismPortalProxyAddress = 0x1c23A6d89F95ef3148BCDA8E242cAb145bf9c0E4; address internal constant optimismPortalProxyAddress = 0x1c23A6d89F95ef3148BCDA8E242cAb145bf9c0E4;
address internal constant permissionedDelayedWETHProxyAddress = 0xd6EAF4c146261653EE059077B78ED088Add54309; address internal constant permissionedDelayedWETHProxyAddress = 0xd6EAF4c146261653EE059077B78ED088Add54309;
address internal constant preimageOracleAddress = 0x7A9Eab4CE99d157AeE7A02E95b366E972a2D5b0b; address internal constant preimageOracleAddress = 0x7A9Eab4CE99d157AeE7A02E95b366E972a2D5b0b;
...@@ -430,7 +430,7 @@ contract DeploymentSummaryFaultProofs is DeploymentSummaryFaultProofsCode { ...@@ -430,7 +430,7 @@ contract DeploymentSummaryFaultProofs is DeploymentSummaryFaultProofsCode {
value = hex"0000000000000000000000000000000000000000000000000000000000000003"; value = hex"0000000000000000000000000000000000000000000000000000000000000003";
vm.store(systemOwnerSafeAddress, slot, value); vm.store(systemOwnerSafeAddress, slot, value);
slot = hex"360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"; slot = hex"360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
value = hex"0000000000000000000000005506077419b90a12c048500e2ebcafb4fc6bab61"; value = hex"000000000000000000000000150581358018524994fc29800b1783637943b103";
vm.store(optimismPortalProxyAddress, slot, value); vm.store(optimismPortalProxyAddress, slot, value);
slot = hex"0000000000000000000000000000000000000000000000000000000000000000"; slot = hex"0000000000000000000000000000000000000000000000000000000000000000";
value = hex"0000000000000000000000000000000000000000000000000000000000000001"; value = hex"0000000000000000000000000000000000000000000000000000000000000001";
......
...@@ -245,6 +245,56 @@ contract FFIInterface { ...@@ -245,6 +245,56 @@ contract FFIInterface {
return (memRoot, proof); return (memRoot, proof);
} }
function getCannonMemoryProof(
uint32 pc,
uint32 insn,
uint32 memAddr,
uint32 memVal,
uint32 memAddr2,
uint32 memVal2
)
external
returns (bytes32, bytes memory)
{
string[] memory cmds = new string[](9);
cmds[0] = "scripts/go-ffi/go-ffi";
cmds[1] = "diff";
cmds[2] = "cannonMemoryProof";
cmds[3] = vm.toString(pc);
cmds[4] = vm.toString(insn);
cmds[5] = vm.toString(memAddr);
cmds[6] = vm.toString(memVal);
cmds[7] = vm.toString(memAddr2);
cmds[8] = vm.toString(memVal2);
bytes memory result = Process.run(cmds);
(bytes32 memRoot, bytes memory proof) = abi.decode(result, (bytes32, bytes));
return (memRoot, proof);
}
function getCannonMemoryProof2(
uint32 pc,
uint32 insn,
uint32 memAddr,
uint32 memVal,
uint32 memAddrForProof
)
external
returns (bytes32, bytes memory)
{
string[] memory cmds = new string[](8);
cmds[0] = "scripts/go-ffi/go-ffi";
cmds[1] = "diff";
cmds[2] = "cannonMemoryProof2";
cmds[3] = vm.toString(pc);
cmds[4] = vm.toString(insn);
cmds[5] = vm.toString(memAddr);
cmds[6] = vm.toString(memVal);
cmds[7] = vm.toString(memAddrForProof);
bytes memory result = Process.run(cmds);
(bytes32 memRoot, bytes memory proof) = abi.decode(result, (bytes32, bytes));
return (memRoot, proof);
}
function getCannonMemoryProofWrongLeaf( function getCannonMemoryProofWrongLeaf(
uint32 pc, uint32 pc,
uint32 insn, uint32 insn,
......
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