Skip to content

Commit

Permalink
Merge pull request #61 from ethereum-optimism/tip/pcw109550/fix-inver…
Browse files Browse the repository at this point in the history
…ted-hint-ineq

rvgo: Fix inverted hint writing conditional check
  • Loading branch information
pcw109550 authored May 22, 2024
2 parents 0bbe1c1 + 33afd26 commit 73046f9
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 7 deletions.
2 changes: 1 addition & 1 deletion rvgo/fast/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ type VMState struct {
// 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:])
// and should only be read when len(LastHint) > 4 && uint32(LastHint[:4]) <= len(LastHint[4:])
LastHint hexutil.Bytes `json:"lastHint,omitempty"`

// VMState must hold these values because if not, we must ask FPVM again to
Expand Down
2 changes: 1 addition & 1 deletion rvgo/fast/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ func (inst *InstrumentedState) riscvStep() (outErr error) {
s.LastHint = append(inst.state.LastHint, hintData...)
for len(s.LastHint) >= 4 { // process while there is enough data to check if there are any hints
hintLen := binary.BigEndian.Uint32(s.LastHint[:4])
if hintLen >= uint32(len(s.LastHint[4:])) {
if hintLen <= uint32(len(s.LastHint[4:])) {
hint := s.LastHint[4 : 4+hintLen] // without the length prefix
s.LastHint = s.LastHint[4+hintLen:]
inst.preimageOracle.Hint(hint)
Expand Down
208 changes: 203 additions & 5 deletions rvgo/test/syscall_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package test

import (
"bytes"
"encoding/binary"
"fmt"
"math/rand"
"os"
"testing"

Expand All @@ -16,7 +18,7 @@ import (
"github.com/ethereum-optimism/asterisc/rvgo/slow"
)

var syscallInsn = []byte{0x73}
var syscallInsn = []byte{0x73, 0x00, 0x00, 0x00}

func staticOracle(t *testing.T, preimageData []byte) *testOracle {
return &testOracle{
Expand All @@ -30,6 +32,18 @@ func staticOracle(t *testing.T, preimageData []byte) *testOracle {
}
}

type hintTrackingOracle struct {
hints [][]byte
}

func (t *hintTrackingOracle) Hint(v []byte) {
t.hints = append(t.hints, v)
}

func (t *hintTrackingOracle) GetPreimage(k [32]byte) []byte {
return nil
}

func runEVM(t *testing.T, contracts *Contracts, addrs *Addresses, stepWitness *fast.StepWitness, fastPost fast.StateWitness, revertCode []byte) {
env := newEVMEnv(t, contracts, addrs)
evmPost, _, _ := stepEVM(t, env, stepWitness, addrs, 0, revertCode)
Expand Down Expand Up @@ -93,6 +107,176 @@ func TestStateSyscallUnsupported(t *testing.T) {
}
}

func TestEVMSysWriteHint(t *testing.T) {
contracts := testContracts(t)
addrs := testAddrs

cases := []struct {
name string
memOffset int // Where the hint data is stored in memory
hintData []byte // Hint data stored in memory at memOffset
bytesToWrite int // How many bytes of hintData to write
lastHint []byte // The buffer that stores lastHint in the state
expectedHints [][]byte // The hints we expect to be processed
}{
{
name: "write 1 full hint at beginning of page",
memOffset: 4096,
hintData: []byte{
0, 0, 0, 6, // Length prefix
0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB, // Hint data
},
bytesToWrite: 10,
lastHint: nil,
expectedHints: [][]byte{
{0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB},
},
},
{
name: "write 1 full hint across page boundary",
memOffset: 4092,
hintData: []byte{
0, 0, 0, 8, // Length prefix
0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB, 0xBB, 0xBB, // Hint data
},
bytesToWrite: 12,
lastHint: nil,
expectedHints: [][]byte{
{0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB, 0xBB, 0xBB},
},
},
{
name: "write 2 full hints",
memOffset: 5012,
hintData: []byte{
0, 0, 0, 6, // Length prefix
0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB, // Hint data
0, 0, 0, 8, // Length prefix
0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB, 0xBB, 0xBB, // Hint data
},
bytesToWrite: 22,
lastHint: nil,
expectedHints: [][]byte{
{0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB},
{0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB, 0xBB, 0xBB},
},
},
{
name: "write a single partial hint",
memOffset: 4092,
hintData: []byte{
0, 0, 0, 6, // Length prefix
0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB, // Hint data
},
bytesToWrite: 8,
lastHint: nil,
expectedHints: nil,
},
{
name: "write 1 full, 1 partial hint",
memOffset: 5012,
hintData: []byte{
0, 0, 0, 6, // Length prefix
0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB, // Hint data
0, 0, 0, 8, // Length prefix
0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB, 0xBB, 0xBB, // Hint data
},
bytesToWrite: 16,
lastHint: nil,
expectedHints: [][]byte{
{0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB},
},
},
{
name: "write a single partial hint to large capacity lastHint buffer",
memOffset: 4092,
hintData: []byte{
0, 0, 0, 6, // Length prefix
0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB, // Hint data
},
bytesToWrite: 8,
lastHint: make([]byte, 0, 4096),
expectedHints: nil,
},
{
name: "write full hint to large capacity lastHint buffer",
memOffset: 5012,
hintData: []byte{
0, 0, 0, 6, // Length prefix
0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB, // Hint data
},
bytesToWrite: 10,
lastHint: make([]byte, 0, 4096),
expectedHints: [][]byte{
{0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB},
},
},
{
name: "write multiple hints to large capacity lastHint buffer",
memOffset: 4092,
hintData: []byte{
0, 0, 0, 8, // Length prefix
0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB, 0xCC, 0xCC, // Hint data
0, 0, 0, 8, // Length prefix
0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB, 0xBB, 0xBB, // Hint data
},
bytesToWrite: 24,
lastHint: make([]byte, 0, 4096),
expectedHints: [][]byte{
{0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB, 0xCC, 0xCC},
{0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB, 0xBB, 0xBB},
},
},
{
name: "write remaining hint data to non-empty lastHint buffer",
memOffset: 4092,
hintData: []byte{
0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB, 0xCC, 0xCC, // Hint data
},
bytesToWrite: 8,
lastHint: []byte{0, 0, 0, 8},
expectedHints: [][]byte{
{0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB, 0xCC, 0xCC},
},
},
{
name: "write partial hint data to non-empty lastHint buffer",
memOffset: 4092,
hintData: []byte{
0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB, 0xCC, 0xCC, // Hint data
},
bytesToWrite: 4,
lastHint: []byte{0, 0, 0, 8},
expectedHints: nil,
},
}

for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
oracle := hintTrackingOracle{}

state := &fast.VMState{
PC: 0,
Memory: fast.NewMemory(),
Registers: [32]uint64{17: riscv.SysWrite, 10: riscv.FdHintWrite, 11: uint64(tt.memOffset), 12: uint64(tt.bytesToWrite)},
LastHint: tt.lastHint,
}

err := state.Memory.SetMemoryRange(uint64(tt.memOffset), bytes.NewReader(tt.hintData))
require.NoError(t, err)
state.Memory.SetUnaligned(0, syscallInsn)

fastState := fast.NewInstrumentedState(state, &oracle, os.Stdout, os.Stderr)
stepWitness, err := fastState.Step(true)
require.NoError(t, err)
require.Equal(t, tt.expectedHints, oracle.hints)

fastPost := state.EncodeWitness()
runEVM(t, contracts, addrs, stepWitness, fastPost, nil)
})
}
}

func FuzzStateSyscallExit(f *testing.F) {
contracts := testContracts(f)
addrs := testAddrs
Expand Down Expand Up @@ -817,7 +1001,7 @@ func FuzzStateHintWrite(f *testing.F) {
contracts := testContracts(f)
addrs := testAddrs

f.Fuzz(func(t *testing.T, addr uint64, count uint64, preimageOffset uint64, pc uint64, step uint64) {
f.Fuzz(func(t *testing.T, addr uint64, count uint64, preimageOffset uint64, pc uint64, step uint64, randSeed int64) {
pc = pc & 0xFF_FF_FF_FF_FF_FF_FF_FC // align PC
preimageData := []byte("hello world")
if preimageOffset >= uint64(len(preimageData)) {
Expand All @@ -835,10 +1019,15 @@ func FuzzStateHintWrite(f *testing.F) {
PreimageKey: preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey(),
PreimageOffset: preimageOffset,

// This is only used by fast/vm.go. The reads a zeroed page-sized buffer when reading hint data from memory.
// We pre-allocate a buffer for the read hint data to be copied into.
LastHint: make(hexutil.Bytes, fast.PageSize),
LastHint: nil,
}
// Set random data at the target memory range
randBytes, err := randomBytes(randSeed, count)
require.NoError(t, err)
err = state.Memory.SetMemoryRange(addr, bytes.NewReader(randBytes))
require.NoError(t, err)

// Set syscall instruction
state.Memory.SetUnaligned(pc, syscallInsn)
preStatePreimageKey := state.PreimageKey
preStateRoot := state.Memory.MerkleRoot()
Expand Down Expand Up @@ -940,3 +1129,12 @@ func FuzzStatePreimageWrite(f *testing.F) {
runSlow(t, stepWitness, fastPost, oracle, nil)
})
}

func randomBytes(seed int64, length uint64) ([]byte, error) {
r := rand.New(rand.NewSource(seed))
randBytes := make([]byte, length)
if _, err := r.Read(randBytes); err != nil {
return nil, err
}
return randBytes, nil
}

0 comments on commit 73046f9

Please sign in to comment.