Skip to content

Commit

Permalink
core/vm: refactor + speed up eof validation
Browse files Browse the repository at this point in the history
core/vm: some clarifications in the eof code
core/vm: clarifications + minor speedup
core/vm: clarifications + lint + minor speedup
  • Loading branch information
holiman committed Sep 12, 2024
1 parent a3031cd commit 8196b46
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 140 deletions.
1 change: 0 additions & 1 deletion cmd/eofdump/eofparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,6 @@ func parseAndValidate(s string, isInitCode bool) (*vm.Container, error) {
}

func parse(b []byte, isInitCode bool) (*vm.Container, error) {

var c vm.Container
if err := c.UnmarshalBinary(b, isInitCode); err != nil {
return nil, err
Expand Down
5 changes: 2 additions & 3 deletions cmd/eofdump/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ func FuzzEofParsing(f *testing.F) {
if err := c.UnmarshalBinary(data, true); err == nil {
c.ValidateCode(&jt, true)
if have := c.MarshalBinary(); !bytes.Equal(have, data) {
f.Fatal("Unmarshal-> Marshal failure!")
t.Fatal("Unmarshal-> Marshal failure!")
}
}
if err := c.UnmarshalBinary(data, false); err == nil {
c.ValidateCode(&jt, false)
if have := c.MarshalBinary(); !bytes.Equal(have, data) {
f.Fatal("Unmarshal-> Marshal failure!")
t.Fatal("Unmarshal-> Marshal failure!")
}
}
if !bytes.Equal(cpy, data) {
Expand Down Expand Up @@ -131,7 +131,6 @@ func testEofParse(t *testing.T, isInitCode bool, wantFile string) {
}
}
line++

}
corpus.Close()
}
Expand Down
4 changes: 3 additions & 1 deletion core/vm/analysis_eof.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ func eofCodeBitmapInternal(code, bits bitvec) bitvec {
pc++

switch {
case op >= PUSH1 && op <= PUSH32:
case op < PUSH1:
continue
case op <= PUSH32:
numbits = uint16(op - PUSH1 + 1)
case op == RJUMP || op == RJUMPI || op == CALLF || op == JUMPF || op == DATALOADN:
numbits = 2
Expand Down
7 changes: 7 additions & 0 deletions core/vm/eips.go
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,13 @@ func enable4762(jt *JumpTable) {
}

// enableEOF applies the EOF changes.
// OBS! For EOF, there are two changes:
// 1. Two separate jumptables are required. One, EOF-jumptable, is used by
// eof contracts. This one contains things like RJUMP.
// 2. The regular non-eof jumptable also needs to be modified, specifically to
// modify how EXTCODECOPY works under the hood.
//
// This method _only_ deals with case 1.
func enableEOF(jt *JumpTable) {
// Deprecate opcodes
undefined := &operation{
Expand Down
73 changes: 36 additions & 37 deletions core/vm/eof.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,12 @@ func isEOFVersion1(code []byte) bool {

// Container is an EOF container object.
type Container struct {
types []*functionMetadata
code [][]byte
sections []*Container
containerCode [][]byte
data []byte
dataSize int // might be more than len(data)
types []*functionMetadata
codeSections [][]byte
subContainers []*Container
subContainerCodes [][]byte
data []byte
dataSize int // might be more than len(data)
}

// functionMetadata is an EOF function signature.
Expand All @@ -92,15 +92,15 @@ func (c *Container) MarshalBinary() []byte {
b = append(b, kindTypes)
b = binary.BigEndian.AppendUint16(b, uint16(len(c.types)*4))
b = append(b, kindCode)
b = binary.BigEndian.AppendUint16(b, uint16(len(c.code)))
for _, code := range c.code {
b = binary.BigEndian.AppendUint16(b, uint16(len(code)))
b = binary.BigEndian.AppendUint16(b, uint16(len(c.codeSections)))
for _, codeSection := range c.codeSections {
b = binary.BigEndian.AppendUint16(b, uint16(len(codeSection)))
}
var encodedContainer [][]byte
if len(c.sections) != 0 {
if len(c.subContainers) != 0 {
b = append(b, kindContainer)
b = binary.BigEndian.AppendUint16(b, uint16(len(c.sections)))
for _, section := range c.sections {
b = binary.BigEndian.AppendUint16(b, uint16(len(c.subContainers)))
for _, section := range c.subContainers {
encoded := section.MarshalBinary()
b = binary.BigEndian.AppendUint16(b, uint16(len(encoded)))
encodedContainer = append(encodedContainer, encoded)
Expand All @@ -114,7 +114,7 @@ func (c *Container) MarshalBinary() []byte {
for _, ty := range c.types {
b = append(b, []byte{ty.inputs, ty.outputs, byte(ty.maxStackHeight >> 8), byte(ty.maxStackHeight & 0x00ff)}...)
}
for _, code := range c.code {
for _, code := range c.codeSections {
b = append(b, code...)
}
for _, section := range encodedContainer {
Expand Down Expand Up @@ -228,7 +228,7 @@ func (c *Container) unmarshalSubContainer(b []byte, isInitcode bool, topLevel bo

// Parse types section.
idx := offsetTerminator + 1
var types []*functionMetadata
var types = make([]*functionMetadata, 0, typesSize/4)
for i := 0; i < typesSize/4; i++ {
sig := &functionMetadata{
inputs: b[idx+i*4],
Expand All @@ -253,45 +253,44 @@ func (c *Container) unmarshalSubContainer(b []byte, isInitcode bool, topLevel bo

// Parse code sections.
idx += typesSize
code := make([][]byte, len(codeSizes))
codeSections := make([][]byte, len(codeSizes))
for i, size := range codeSizes {
if size == 0 {
return fmt.Errorf("%w for section %d: size must not be 0", ErrInvalidCodeSize, i)
}
code[i] = b[idx : idx+size]
codeSections[i] = b[idx : idx+size]
idx += size
}
c.code = code

c.codeSections = codeSections
// Parse the optional container sizes.
if len(containerSizes) != 0 {
if len(containerSizes) > maxContainerSections {
return fmt.Errorf("%w number of container section exceed: %v: have %v", ErrInvalidContainerSectionSize, maxContainerSections, len(containerSizes))
}
containerCode := make([][]byte, 0, len(containerSizes))
container := make([]*Container, 0, len(containerSizes))
subContainerCodes := make([][]byte, 0, len(containerSizes))
subContainers := make([]*Container, 0, len(containerSizes))
for i, size := range containerSizes {
if size == 0 || idx+size > len(b) {
return fmt.Errorf("%w for section %d: size must not be 0", ErrInvalidContainerSectionSize, i)
}
c := new(Container)
subC := new(Container)
end := min(idx+size, len(b))
if err := c.unmarshalSubContainer(b[idx:end], isInitcode, false); err != nil {
if err := subC.unmarshalSubContainer(b[idx:end], isInitcode, false); err != nil {
if topLevel {
return fmt.Errorf("%w in sub container %d", err, i)
}
return err
}
container = append(container, c)
containerCode = append(containerCode, b[idx:end])
subContainers = append(subContainers, subC)
subContainerCodes = append(subContainerCodes, b[idx:end])

idx += size
}
c.sections = container
c.containerCode = containerCode
c.subContainers = subContainers
c.subContainerCodes = subContainerCodes
}

// Parse data section.
//Parse data section.
end := len(b)
if !isInitcode {
end = min(idx+dataSize, len(b))
Expand Down Expand Up @@ -327,7 +326,7 @@ func (c *Container) validateSubContainer(jt *JumpTable, refBy int) error {
// should not mean 2 and 3 should be visited twice
var (
index = toVisit[0]
code = c.code[index]
code = c.codeSections[index]
)
if _, ok := visited[index]; !ok {
res, err := validateCode(code, index, c, jt, refBy == refByEOFCreate)
Expand Down Expand Up @@ -359,10 +358,10 @@ func (c *Container) validateSubContainer(jt *JumpTable, refBy int) error {
toVisit = toVisit[1:]
}
// Make sure every code section is visited at least once.
if len(visited) != len(c.code) {
if len(visited) != len(c.codeSections) {
return ErrUnreachableCode
}
for idx, container := range c.sections {
for idx, container := range c.subContainers {
reference, ok := subContainerVisited[idx]
if !ok {
return ErrOrphanedSubcontainer
Expand Down Expand Up @@ -444,14 +443,14 @@ func (c *Container) String() string {
result += fmt.Sprintf("KindType: %02x\n", kindTypes)
result += fmt.Sprintf("TypesSize: %04x\n", len(c.types)*4)
result += fmt.Sprintf("KindCode: %02x\n", kindCode)
result += fmt.Sprintf("CodeSize: %04x\n", len(c.code))
for i, code := range c.code {
result += fmt.Sprintf("CodeSize: %04x\n", len(c.codeSections))
for i, code := range c.codeSections {
result += fmt.Sprintf("Code %v length: %04x\n", i, len(code))
}
if len(c.sections) != 0 {
if len(c.subContainers) != 0 {
result += fmt.Sprintf("KindContainer: %02x\n", kindContainer)
result += fmt.Sprintf("ContainerSize: %04x\n", len(c.sections))
for i, section := range c.sections {
result += fmt.Sprintf("ContainerSize: %04x\n", len(c.subContainers))
for i, section := range c.subContainers {
result += fmt.Sprintf("Container %v length: %04x\n", i, len(section.MarshalBinary()))
}
}
Expand All @@ -464,10 +463,10 @@ func (c *Container) String() string {
for i, typ := range c.types {
result += fmt.Sprintf("Type %v: %v\n", i, hex.EncodeToString([]byte{typ.inputs, typ.outputs, byte(typ.maxStackHeight >> 8), byte(typ.maxStackHeight & 0x00ff)}))
}
for i, code := range c.code {
for i, code := range c.codeSections {
result += fmt.Sprintf("Code %v: %v\n", i, hex.EncodeToString(code))
}
for i, section := range c.sections {
for i, section := range c.subContainers {
result += fmt.Sprintf("Section %v: %v\n", i, hex.EncodeToString(section.MarshalBinary()))
}
result += fmt.Sprintf("Data: %v\n", hex.EncodeToString(c.data))
Expand Down
5 changes: 0 additions & 5 deletions core/vm/eof_instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,6 @@

package vm

// opExtCodeCopyEOF implements the EXTCODECOPY opcode for EOF-enabled forks.
func opExtCodeCopyEOF(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}

// opRjump implements the RJUMP opcode.
func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
Expand Down
28 changes: 14 additions & 14 deletions core/vm/eof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,18 @@ func TestEOFMarshaling(t *testing.T) {
}{
{
want: Container{
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
code: [][]byte{common.Hex2Bytes("604200")},
data: []byte{0x01, 0x02, 0x03},
dataSize: 3,
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
codeSections: [][]byte{common.Hex2Bytes("604200")},
data: []byte{0x01, 0x02, 0x03},
dataSize: 3,
},
},
{
want: Container{
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
code: [][]byte{common.Hex2Bytes("604200")},
data: []byte{0x01, 0x02, 0x03},
dataSize: 3,
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
codeSections: [][]byte{common.Hex2Bytes("604200")},
data: []byte{0x01, 0x02, 0x03},
dataSize: 3,
},
},
{
Expand All @@ -53,7 +53,7 @@ func TestEOFMarshaling(t *testing.T) {
{inputs: 2, outputs: 3, maxStackHeight: 4},
{inputs: 1, outputs: 1, maxStackHeight: 1},
},
code: [][]byte{
codeSections: [][]byte{
common.Hex2Bytes("604200"),
common.Hex2Bytes("6042604200"),
common.Hex2Bytes("00"),
Expand Down Expand Up @@ -82,11 +82,11 @@ func TestEOFSubcontainer(t *testing.T) {
t.Fatal(err)
}
container := Container{
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
code: [][]byte{common.Hex2Bytes("604200")},
sections: []*Container{subcontainer},
data: []byte{0x01, 0x02, 0x03},
dataSize: 3,
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
codeSections: [][]byte{common.Hex2Bytes("604200")},
subContainers: []*Container{subcontainer},
data: []byte{0x01, 0x02, 0x03},
dataSize: 3,
}
var (
b = container.MarshalBinary()
Expand Down
26 changes: 19 additions & 7 deletions core/vm/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
count = 0
op OpCode
analysis bitvec
visitedCode = make(map[int]struct{})
visitedSubcontainers = make(map[int]int)
visitedCode map[int]struct{}
visitedSubcontainers map[int]int
hasReturnContract bool
hasStop bool
)
Expand Down Expand Up @@ -129,6 +129,9 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
if container.types[arg].outputs == 0x80 {
return nil, fmt.Errorf("%w: section %v", ErrInvalidCallArgument, arg)
}
if visitedCode == nil {
visitedCode = make(map[int]struct{})
}
visitedCode[arg] = struct{}{}
case JUMPF:
arg, _ := parseUint16(code[i+1:])
Expand All @@ -138,6 +141,9 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
if container.types[arg].outputs != 0x80 && container.types[arg].outputs > container.types[section].outputs {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrInvalidOutputs, arg, len(container.types), i)
}
if visitedCode == nil {
visitedCode = make(map[int]struct{})
}
visitedCode[arg] = struct{}{}
case DATALOADN:
arg, _ := parseUint16(code[i+1:])
Expand All @@ -150,8 +156,11 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
return nil, ErrIncompatibleContainerKind
}
arg := int(code[i+1])
if arg >= len(container.sections) {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrUnreachableCode, arg, len(container.sections), i)
if arg >= len(container.subContainers) {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrUnreachableCode, arg, len(container.subContainers), i)
}
if visitedSubcontainers == nil {
visitedSubcontainers = make(map[int]int)
}
// We need to store per subcontainer how it was referenced
if v, ok := visitedSubcontainers[arg]; ok && v != refByReturnContract {
Expand All @@ -164,12 +173,15 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
visitedSubcontainers[arg] = refByReturnContract
case EOFCREATE:
arg := int(code[i+1])
if arg >= len(container.sections) {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrUnreachableCode, arg, len(container.sections), i)
if arg >= len(container.subContainers) {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrUnreachableCode, arg, len(container.subContainers), i)
}
if ct := container.sections[arg]; len(ct.data) != ct.dataSize {
if ct := container.subContainers[arg]; len(ct.data) != ct.dataSize {
return nil, fmt.Errorf("%w: container %d, have %d, claimed %d, pos %d", ErrEOFCreateWithTruncatedSection, arg, len(ct.data), ct.dataSize, i)
}
if visitedSubcontainers == nil {
visitedSubcontainers = make(map[int]int)
}
if _, ok := visitedSubcontainers[arg]; ok {
return nil, fmt.Errorf("section already referenced, arg :%d", arg)
}
Expand Down
Loading

0 comments on commit 8196b46

Please sign in to comment.