Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cannon: Add MTCannon-specific differential tests #11605

Merged
merged 30 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c93a128
cannon: Implement multithreaded clone fuzz test
mbaxter Aug 5, 2024
e1eb2eb
cannon: Add more clone evm tests
mbaxter Aug 5, 2024
c934a37
cannon: Add evm test for GetTID syscall
mbaxter Aug 5, 2024
3a16481
cannon: Add evm test for SysExit
mbaxter Aug 6, 2024
6665098
cannon: Add evm test for popping exited threads from the stack
mbaxter Aug 6, 2024
a13d55d
cannon: Fix futex wait handling, add evm test
mbaxter Aug 6, 2024
091e389
cannon: Add evm test for handling waiting thread
mbaxter Aug 6, 2024
fce148e
cannon: Add test utils for defining / validating MTState expectations
mbaxter Aug 23, 2024
b4abf11
cannon: Add tests for futex wake, wake traversal
mbaxter Aug 23, 2024
eee19db
cannon: Add test for SysYield
mbaxter Aug 23, 2024
59980a5
cannon: Add SysOpen test, todos
mbaxter Aug 23, 2024
c58913f
cannon: Add test for SchedQuantum preemption, fix inconsistency
mbaxter Aug 23, 2024
3635e5b
cannon: Add tests for noop, unsupported syscalls
mbaxter Aug 23, 2024
451fd7d
cannon: Remove duplicate constants
mbaxter Aug 26, 2024
4a165dc
cannon: Add tests for unsupported futex ops
mbaxter Aug 26, 2024
fdbaf74
cannon: Group traversal tests, fix TestEVM_WakeupTraversalStep
mbaxter Aug 26, 2024
058155b
cannon: Add tests for nanosleep
mbaxter Aug 26, 2024
e0669ce
cannon: Add additional testcase for wakeup traversal
mbaxter Aug 26, 2024
4c4083d
cannon: Tweak futex wake tests
mbaxter Aug 26, 2024
930b858
cannon: Update mt fuzz test to use new test utils
mbaxter Aug 26, 2024
e366dbb
cannon: Rename contructor method for consistency
mbaxter Aug 26, 2024
adae10f
cannon: Add some simple tests for ExpectedMTState util
mbaxter Aug 26, 2024
d9b96d3
cannon: Add another validation test
mbaxter Aug 26, 2024
a5908f5
cannon: Move syscall lists to tests where they're used
mbaxter Aug 26, 2024
27cdc39
cannon: Add comment
mbaxter Aug 26, 2024
38e2854
cannon: Extract some evm test helpers
mbaxter Aug 26, 2024
e551011
cannon: Cleanup - use require.Equalf for formatting
mbaxter Aug 27, 2024
bd1c10d
cannon: Rename test util to AssertEVMReverts
mbaxter Aug 27, 2024
f188c6b
cannon: Add GetThreadStacks helper
mbaxter Aug 27, 2024
c154483
cannon: Add a few more traversal tests
mbaxter Aug 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cannon/mipsevm/exec/mips_syscalls.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
// Syscall codes
const (
SysMmap = 4090
SysMunmap = 4091
SysBrk = 4045
SysClone = 4120
SysExitGroup = 4246
Expand All @@ -32,6 +31,7 @@ const (

// Noop Syscall codes
const (
SysMunmap = 4091
SysGetAffinity = 4240
SysMadvise = 4218
SysRtSigprocmask = 4195
Expand Down
8 changes: 4 additions & 4 deletions cannon/mipsevm/multithreaded/mips.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,13 @@ func (m *InstrumentedState) handleSyscall() error {
// args: a0 = addr, a1 = op, a2 = val, a3 = timeout
switch a1 {
case exec.FutexWaitPrivate:
thread.FutexAddr = a0
mbaxter marked this conversation as resolved.
Show resolved Hide resolved
m.memoryTracker.TrackMemAccess(a0)
mem := m.state.Memory.GetMemory(a0)
if mem != a2 {
v0 = exec.SysErrorSignal
v1 = exec.MipsEAGAIN
} else {
thread.FutexAddr = a0
thread.FutexVal = a2
if a3 == 0 {
thread.FutexTimeoutStep = exec.FutexNoTimeout
Expand Down Expand Up @@ -242,11 +242,11 @@ func (m *InstrumentedState) mipsStep() error {

if m.state.StepsSinceLastContextSwitch >= exec.SchedQuantum {
// Force a context switch as this thread has been active too long
if m.state.threadCount() > 1 {
if m.state.ThreadCount() > 1 {
// Log if we're hitting our context switch limit - only matters if we have > 1 thread
if m.log.Enabled(context.Background(), log.LevelTrace) {
msg := fmt.Sprintf("Thread has reached maximum execution steps (%v) - preempting.", exec.SchedQuantum)
m.log.Trace(msg, "threadId", thread.ThreadId, "threadCount", m.state.threadCount(), "pc", thread.Cpu.PC)
m.log.Trace(msg, "threadId", thread.ThreadId, "threadCount", m.state.ThreadCount(), "pc", thread.Cpu.PC)
}
}
m.preemptThread(thread)
Expand Down Expand Up @@ -339,5 +339,5 @@ func (m *InstrumentedState) popThread() {
}

func (m *InstrumentedState) lastThreadRemaining() bool {
return m.state.threadCount() == 1
return m.state.ThreadCount() == 1
}
2 changes: 1 addition & 1 deletion cannon/mipsevm/multithreaded/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ func (s *State) EncodeThreadProof() []byte {
return out
}

func (s *State) threadCount() int {
func (s *State) ThreadCount() int {
return len(s.LeftThreadStack) + len(s.RightThreadStack)
}

Expand Down
203 changes: 203 additions & 0 deletions cannon/mipsevm/multithreaded/testutil/expectations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package testutil

import (
"fmt"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/require"

"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
)

// ExpectedMTState is a test utility that basically stores a copy of a state that can be explicitly mutated
// to define an expected post-state. The post-state is then validated with ExpectedMTState.Validate(t, postState)
type ExpectedMTState struct {
PreimageKey common.Hash
PreimageOffset uint32
Heap uint32
ExitCode uint8
Exited bool
Step uint64
LastHint hexutil.Bytes
MemoryRoot common.Hash
// Threading-related expectations
StepsSinceLastContextSwitch uint64
Wakeup uint32
TraverseRight bool
NextThreadId uint32
ThreadCount int
RightStackSize int
LeftStackSize int
prestateActiveThreadId uint32
prestateActiveThreadOrig ExpectedThreadState // Cached for internal use
ActiveThreadId uint32
threadExpectations map[uint32]*ExpectedThreadState
}

type ExpectedThreadState struct {
ThreadId uint32
ExitCode uint8
Exited bool
FutexAddr uint32
FutexVal uint32
FutexTimeoutStep uint64
PC uint32
NextPC uint32
HI uint32
LO uint32
Registers [32]uint32
Dropped bool
}

func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState {
currentThread := fromState.GetCurrentThread()

expectedThreads := make(map[uint32]*ExpectedThreadState)
for _, t := range GetAllThreads(fromState) {
expectedThreads[t.ThreadId] = newExpectedThreadState(t)
}

return &ExpectedMTState{
// General Fields
PreimageKey: fromState.GetPreimageKey(),
PreimageOffset: fromState.GetPreimageOffset(),
Heap: fromState.GetHeap(),
ExitCode: fromState.GetExitCode(),
Exited: fromState.GetExited(),
Step: fromState.GetStep(),
LastHint: fromState.GetLastHint(),
MemoryRoot: fromState.GetMemory().MerkleRoot(),
// Thread-related global fields
StepsSinceLastContextSwitch: fromState.StepsSinceLastContextSwitch,
Wakeup: fromState.Wakeup,
TraverseRight: fromState.TraverseRight,
NextThreadId: fromState.NextThreadId,
ThreadCount: fromState.ThreadCount(),
RightStackSize: len(fromState.RightThreadStack),
LeftStackSize: len(fromState.LeftThreadStack),
// ThreadState expectations
prestateActiveThreadId: currentThread.ThreadId,
prestateActiveThreadOrig: *newExpectedThreadState(currentThread), // Cache prestate thread for internal use
ActiveThreadId: currentThread.ThreadId,
threadExpectations: expectedThreads,
}
}

func newExpectedThreadState(fromThread *multithreaded.ThreadState) *ExpectedThreadState {
return &ExpectedThreadState{
ThreadId: fromThread.ThreadId,
ExitCode: fromThread.ExitCode,
Exited: fromThread.Exited,
FutexAddr: fromThread.FutexAddr,
FutexVal: fromThread.FutexVal,
FutexTimeoutStep: fromThread.FutexTimeoutStep,
PC: fromThread.Cpu.PC,
NextPC: fromThread.Cpu.NextPC,
HI: fromThread.Cpu.HI,
LO: fromThread.Cpu.LO,
Registers: fromThread.Registers,
Dropped: false,
}
}

func (e *ExpectedMTState) ExpectStep() {
// Set some standard expectations for a normal step
e.Step += 1
e.PrestateActiveThread().PC += 4
e.PrestateActiveThread().NextPC += 4
e.StepsSinceLastContextSwitch += 1
}

func (e *ExpectedMTState) ExpectPreemption(preState *multithreaded.State) {
e.ActiveThreadId = FindNextThread(preState).ThreadId
e.StepsSinceLastContextSwitch = 0
if preState.TraverseRight {
e.TraverseRight = e.RightStackSize > 1
e.RightStackSize -= 1
e.LeftStackSize += 1
} else {
e.TraverseRight = e.LeftStackSize == 1
e.LeftStackSize -= 1
e.RightStackSize += 1
}
}

func (e *ExpectedMTState) ExpectNewThread() *ExpectedThreadState {
newThreadId := e.NextThreadId
e.NextThreadId += 1
e.ThreadCount += 1

// Clone expectations from prestate active thread's original state (bf changing any expectations)
newThread := &ExpectedThreadState{}
*newThread = e.prestateActiveThreadOrig

newThread.ThreadId = newThreadId
e.threadExpectations[newThreadId] = newThread

return newThread
}

func (e *ExpectedMTState) ActiveThread() *ExpectedThreadState {
return e.threadExpectations[e.ActiveThreadId]
}

func (e *ExpectedMTState) PrestateActiveThread() *ExpectedThreadState {
return e.threadExpectations[e.prestateActiveThreadId]
}

func (e *ExpectedMTState) Thread(threadId uint32) *ExpectedThreadState {
return e.threadExpectations[threadId]
}

func (e *ExpectedMTState) Validate(t require.TestingT, actualState *multithreaded.State) {
require.Equalf(t, e.PreimageKey, actualState.GetPreimageKey(), "Expect preimageKey = %v", e.PreimageKey)
require.Equalf(t, e.PreimageOffset, actualState.GetPreimageOffset(), "Expect preimageOffset = %v", e.PreimageOffset)
require.Equalf(t, e.Heap, actualState.GetHeap(), "Expect heap = 0x%x", e.Heap)
require.Equalf(t, e.ExitCode, actualState.GetExitCode(), "Expect exitCode = 0x%x", e.ExitCode)
require.Equalf(t, e.Exited, actualState.GetExited(), "Expect exited = %v", e.Exited)
require.Equalf(t, e.Step, actualState.GetStep(), "Expect step = %d", e.Step)
require.Equalf(t, e.LastHint, actualState.GetLastHint(), "Expect lastHint = %v", e.LastHint)
require.Equalf(t, e.MemoryRoot, common.Hash(actualState.GetMemory().MerkleRoot()), "Expect memory root = %v", e.MemoryRoot)
// Thread-related global fields
require.Equalf(t, e.StepsSinceLastContextSwitch, actualState.StepsSinceLastContextSwitch, "Expect StepsSinceLastContextSwitch = %v", e.StepsSinceLastContextSwitch)
require.Equalf(t, e.Wakeup, actualState.Wakeup, "Expect Wakeup = %v", e.Wakeup)
require.Equalf(t, e.TraverseRight, actualState.TraverseRight, "Expect TraverseRight = %v", e.TraverseRight)
require.Equalf(t, e.NextThreadId, actualState.NextThreadId, "Expect NextThreadId = %v", e.NextThreadId)
require.Equalf(t, e.ThreadCount, actualState.ThreadCount(), "Expect thread count = %v", e.ThreadCount)
require.Equalf(t, e.RightStackSize, len(actualState.RightThreadStack), "Expect right stack size = %v", e.RightStackSize)
require.Equalf(t, e.LeftStackSize, len(actualState.LeftThreadStack), "Expect right stack size = %v", e.LeftStackSize)

// Check active thread
activeThread := actualState.GetCurrentThread()
require.Equal(t, e.ActiveThreadId, activeThread.ThreadId)
// Check all threads
expectedThreadCount := 0
for tid, exp := range e.threadExpectations {
actualThread := FindThread(actualState, tid)
isActive := tid == activeThread.ThreadId
if exp.Dropped {
require.Nil(t, actualThread, "Thread %v should have been dropped", tid)
} else {
require.NotNil(t, actualThread, "Could not find thread matching expected thread with id %v", tid)
e.validateThread(t, exp, actualThread, isActive)
expectedThreadCount++
}
}
require.Equal(t, expectedThreadCount, actualState.ThreadCount(), "Thread expectations do not match thread count")
}

func (e *ExpectedMTState) validateThread(t require.TestingT, et *ExpectedThreadState, actual *multithreaded.ThreadState, isActive bool) {
threadInfo := fmt.Sprintf("tid = %v, active = %v", actual.ThreadId, isActive)
require.Equalf(t, et.ThreadId, actual.ThreadId, "Expect ThreadId = 0x%x (%v)", et.ThreadId, threadInfo)
require.Equalf(t, et.PC, actual.Cpu.PC, "Expect PC = 0x%x (%v)", et.PC, threadInfo)
require.Equalf(t, et.NextPC, actual.Cpu.NextPC, "Expect nextPC = 0x%x (%v)", et.NextPC, threadInfo)
require.Equalf(t, et.HI, actual.Cpu.HI, "Expect HI = 0x%x (%v)", et.HI, threadInfo)
require.Equalf(t, et.LO, actual.Cpu.LO, "Expect LO = 0x%x (%v)", et.LO, threadInfo)
require.Equalf(t, et.Registers, actual.Registers, "Expect registers to match (%v)", threadInfo)
require.Equalf(t, et.ExitCode, actual.ExitCode, "Expect exitCode = %v (%v)", et.ExitCode, threadInfo)
require.Equalf(t, et.Exited, actual.Exited, "Expect exited = %v (%v)", et.Exited, threadInfo)
require.Equalf(t, et.FutexAddr, actual.FutexAddr, "Expect futexAddr = %v (%v)", et.FutexAddr, threadInfo)
require.Equalf(t, et.FutexVal, actual.FutexVal, "Expect futexVal = %v (%v)", et.FutexVal, threadInfo)
require.Equalf(t, et.FutexTimeoutStep, actual.FutexTimeoutStep, "Expect futexTimeoutStep = %v (%v)", et.FutexTimeoutStep, threadInfo)
}
Loading