tests/fuzzers: move fuzzers into native packages (#28467)

This PR moves our fuzzers from tests/fuzzers into whatever their respective 'native' package is.

The historical reason why they were placed in an external location, is that when they were based on go-fuzz, they could not be "hidden" via the _test.go prefix. So in order to shove them away from the go-ethereum "production code", they were put aside.

But now we've rewritten them to be based on golang testing, and thus can be brought back. I've left (in tests/) the ones that are not production (bls128381), require non-standard imports (secp requires btcec, bn256 requires gnark/google/cloudflare deps).

This PR also adds a fuzzer for precompiled contracts, because why not.

This PR utilizes a newly rewritten replacement for go-118-fuzz-build, namely gofuzz-shim, which utilises the inputs from the fuzzing engine better.
This commit is contained in:
Martin Holst Swende 2023-11-14 14:34:29 +01:00 committed by GitHub
parent 24d46224c1
commit 2391fbc676
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 700 additions and 992 deletions

View File

@ -22,19 +22,19 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/ethereum/go-ethereum/accounts/abi"
fuzz "github.com/google/gofuzz" fuzz "github.com/google/gofuzz"
) )
// TestReplicate can be used to replicate crashers from the fuzzing tests. // TestReplicate can be used to replicate crashers from the fuzzing tests.
// Just replace testString with the data in .quoted // Just replace testString with the data in .quoted
func TestReplicate(t *testing.T) { func TestReplicate(t *testing.T) {
testString := "\x20\x20\x20\x20\x20\x20\x20\x20\x80\x00\x00\x00\x20\x20\x20\x20\x00" //t.Skip("Test only useful for reproducing issues")
data := []byte(testString) fuzzAbi([]byte("\x20\x20\x20\x20\x20\x20\x20\x20\x80\x00\x00\x00\x20\x20\x20\x20\x00"))
fuzzAbi(data) //fuzzAbi([]byte("asdfasdfkadsf;lasdf;lasd;lfk"))
} }
func Fuzz(f *testing.F) { // FuzzABI is the main entrypoint for fuzzing
func FuzzABI(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) { f.Fuzz(func(t *testing.T, data []byte) {
fuzzAbi(data) fuzzAbi(data)
}) })
@ -42,10 +42,8 @@ func Fuzz(f *testing.F) {
var ( var (
names = []string{"_name", "name", "NAME", "name_", "__", "_name_", "n"} names = []string{"_name", "name", "NAME", "name_", "__", "_name_", "n"}
stateMut = []string{"", "pure", "view", "payable"} stateMut = []string{"pure", "view", "payable"}
stateMutabilites = []*string{&stateMut[0], &stateMut[1], &stateMut[2], &stateMut[3]} pays = []string{"true", "false"}
pays = []string{"", "true", "false"}
payables = []*string{&pays[0], &pays[1]}
vNames = []string{"a", "b", "c", "d", "e", "f", "g"} vNames = []string{"a", "b", "c", "d", "e", "f", "g"}
varNames = append(vNames, names...) varNames = append(vNames, names...)
varTypes = []string{"bool", "address", "bytes", "string", varTypes = []string{"bool", "address", "bytes", "string",
@ -62,7 +60,7 @@ var (
"bytes32", "bytes"} "bytes32", "bytes"}
) )
func unpackPack(abi abi.ABI, method string, input []byte) ([]interface{}, bool) { func unpackPack(abi ABI, method string, input []byte) ([]interface{}, bool) {
if out, err := abi.Unpack(method, input); err == nil { if out, err := abi.Unpack(method, input); err == nil {
_, err := abi.Pack(method, out...) _, err := abi.Pack(method, out...)
if err != nil { if err != nil {
@ -78,7 +76,7 @@ func unpackPack(abi abi.ABI, method string, input []byte) ([]interface{}, bool)
return nil, false return nil, false
} }
func packUnpack(abi abi.ABI, method string, input *[]interface{}) bool { func packUnpack(abi ABI, method string, input *[]interface{}) bool {
if packed, err := abi.Pack(method, input); err == nil { if packed, err := abi.Pack(method, input); err == nil {
outptr := reflect.New(reflect.TypeOf(input)) outptr := reflect.New(reflect.TypeOf(input))
err := abi.UnpackIntoInterface(outptr.Interface(), method, packed) err := abi.UnpackIntoInterface(outptr.Interface(), method, packed)
@ -94,12 +92,12 @@ func packUnpack(abi abi.ABI, method string, input *[]interface{}) bool {
return false return false
} }
type args struct { type arg struct {
name string name string
typ string typ string
} }
func createABI(name string, stateMutability, payable *string, inputs []args) (abi.ABI, error) { func createABI(name string, stateMutability, payable *string, inputs []arg) (ABI, error) {
sig := fmt.Sprintf(`[{ "type" : "function", "name" : "%v" `, name) sig := fmt.Sprintf(`[{ "type" : "function", "name" : "%v" `, name)
if stateMutability != nil { if stateMutability != nil {
sig += fmt.Sprintf(`, "stateMutability": "%v" `, *stateMutability) sig += fmt.Sprintf(`, "stateMutability": "%v" `, *stateMutability)
@ -126,56 +124,55 @@ func createABI(name string, stateMutability, payable *string, inputs []args) (ab
sig += "} ]" sig += "} ]"
} }
sig += `}]` sig += `}]`
//fmt.Printf("sig: %s\n", sig)
return abi.JSON(strings.NewReader(sig)) return JSON(strings.NewReader(sig))
} }
func fuzzAbi(input []byte) int { func fuzzAbi(input []byte) {
good := false var (
fuzzer := fuzz.NewFromGoFuzz(input) fuzzer = fuzz.NewFromGoFuzz(input)
name = oneOf(fuzzer, names)
name := names[getUInt(fuzzer)%len(names)] stateM = oneOfOrNil(fuzzer, stateMut)
stateM := stateMutabilites[getUInt(fuzzer)%len(stateMutabilites)] payable = oneOfOrNil(fuzzer, pays)
payable := payables[getUInt(fuzzer)%len(payables)] arguments []arg
maxLen := 5 )
for k := 1; k < maxLen; k++ { for i := 0; i < upTo(fuzzer, 10); i++ {
var arg []args argName := oneOf(fuzzer, varNames)
for i := k; i > 0; i-- { argTyp := oneOf(fuzzer, varTypes)
argName := varNames[i] switch upTo(fuzzer, 10) {
argTyp := varTypes[getUInt(fuzzer)%len(varTypes)] case 0: // 10% chance to make it a slice
if getUInt(fuzzer)%10 == 0 {
argTyp += "[]" argTyp += "[]"
} else if getUInt(fuzzer)%10 == 0 { case 1: // 10% chance to make it an array
arrayArgs := getUInt(fuzzer)%30 + 1 argTyp += fmt.Sprintf("[%d]", 1+upTo(fuzzer, 30))
argTyp += fmt.Sprintf("[%d]", arrayArgs) default:
} }
arg = append(arg, args{ arguments = append(arguments, arg{name: argName, typ: argTyp})
name: argName,
typ: argTyp,
})
} }
abi, err := createABI(name, stateM, payable, arg) abi, err := createABI(name, stateM, payable, arguments)
if err != nil { if err != nil {
continue //fmt.Printf("err: %v\n", err)
panic(err)
} }
structs, b := unpackPack(abi, name, input) structs, _ := unpackPack(abi, name, input)
c := packUnpack(abi, name, &structs) _ = packUnpack(abi, name, &structs)
good = good || b || c
}
if good {
return 1
}
return 0
} }
func getUInt(fuzzer *fuzz.Fuzzer) int { func upTo(fuzzer *fuzz.Fuzzer, max int) int {
var i int var i int
fuzzer.Fuzz(&i) fuzzer.Fuzz(&i)
if i < 0 { if i < 0 {
i = -i return (-1 - i) % max
if i < 0 {
return 0
} }
} return i % max
return i }
func oneOf(fuzzer *fuzz.Fuzzer, options []string) string {
return options[upTo(fuzzer, len(options))]
}
func oneOfOrNil(fuzzer *fuzz.Fuzzer, options []string) *string {
if i := upTo(fuzzer, len(options)+1); i < len(options) {
return &options[i]
}
return nil
} }

View File

@ -16,10 +16,19 @@
package keystore package keystore
import "testing" import (
"testing"
)
func Fuzz(f *testing.F) { func FuzzPassword(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) { f.Fuzz(func(t *testing.T, password string) {
fuzz(data) ks := NewKeyStore(t.TempDir(), LightScryptN, LightScryptP)
a, err := ks.NewAccount(password)
if err != nil {
t.Fatal(err)
}
if err := ks.Unlock(a, password); err != nil {
t.Fatal(err)
}
}) })
} }

View File

@ -18,6 +18,7 @@ package bitutil
import ( import (
"bytes" "bytes"
"fmt"
"math/rand" "math/rand"
"testing" "testing"
@ -48,17 +49,21 @@ func TestEncodingCycle(t *testing.T) {
"0xdf7070533534333636313639343638373532313536346c1bc333393438373130707063363430353639343638373532313536346c1bc333393438336336346c65fe", "0xdf7070533534333636313639343638373532313536346c1bc333393438373130707063363430353639343638373532313536346c1bc333393438336336346c65fe",
} }
for i, tt := range tests { for i, tt := range tests {
data := hexutil.MustDecode(tt) if err := testEncodingCycle(hexutil.MustDecode(tt)); err != nil {
t.Errorf("test %d: %v", i, err)
}
}
}
func testEncodingCycle(data []byte) error {
proc, err := bitsetDecodeBytes(bitsetEncodeBytes(data), len(data)) proc, err := bitsetDecodeBytes(bitsetEncodeBytes(data), len(data))
if err != nil { if err != nil {
t.Errorf("test %d: failed to decompress compressed data: %v", i, err) return fmt.Errorf("failed to decompress compressed data: %v", err)
continue
} }
if !bytes.Equal(data, proc) { if !bytes.Equal(data, proc) {
t.Errorf("test %d: compress/decompress mismatch: have %x, want %x", i, proc, data) return fmt.Errorf("compress/decompress mismatch: have %x, want %x", proc, data)
}
} }
return nil
} }
// Tests that data bitset decoding and rencoding works and is bijective. // Tests that data bitset decoding and rencoding works and is bijective.
@ -179,3 +184,40 @@ func benchmarkEncoding(b *testing.B, bytes int, fill float64) {
bitsetDecodeBytes(bitsetEncodeBytes(data), len(data)) bitsetDecodeBytes(bitsetEncodeBytes(data), len(data))
} }
} }
func FuzzEncoder(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
if err := testEncodingCycle(data); err != nil {
t.Fatal(err)
}
})
}
func FuzzDecoder(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
fuzzDecode(data)
})
}
// fuzzDecode implements a go-fuzz fuzzer method to test the bit decoding and
// reencoding algorithm.
func fuzzDecode(data []byte) {
blob, err := DecompressBytes(data, 1024)
if err != nil {
return
}
// re-compress it (it's OK if the re-compressed differs from the
// original - the first input may not have been compressed at all)
comp := CompressBytes(blob)
if len(comp) > len(blob) {
// After compression, it must be smaller or equal
panic("bad compression")
}
// But decompressing it once again should work
decomp, err := DecompressBytes(data, 1024)
if err != nil {
panic(err)
}
if !bytes.Equal(decomp, blob) {
panic("content mismatch")
}
}

View File

@ -0,0 +1,147 @@
// Copyright 2019 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package types
import (
"bytes"
"fmt"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/rlp"
"github.com/holiman/uint256"
)
func decodeEncode(input []byte, val interface{}) error {
if err := rlp.DecodeBytes(input, val); err != nil {
// not valid rlp, nothing to do
return nil
}
// If it _were_ valid rlp, we can encode it again
output, err := rlp.EncodeToBytes(val)
if err != nil {
return err
}
if !bytes.Equal(input, output) {
return fmt.Errorf("encode-decode is not equal, \ninput : %x\noutput: %x", input, output)
}
return nil
}
func FuzzRLP(f *testing.F) {
f.Fuzz(fuzzRlp)
}
func fuzzRlp(t *testing.T, input []byte) {
if len(input) == 0 || len(input) > 500*1024 {
return
}
rlp.Split(input)
if elems, _, err := rlp.SplitList(input); err == nil {
rlp.CountValues(elems)
}
rlp.NewStream(bytes.NewReader(input), 0).Decode(new(interface{}))
if err := decodeEncode(input, new(interface{})); err != nil {
t.Fatal(err)
}
{
var v struct {
Int uint
String string
Bytes []byte
}
if err := decodeEncode(input, &v); err != nil {
t.Fatal(err)
}
}
{
type Types struct {
Bool bool
Raw rlp.RawValue
Slice []*Types
Iface []interface{}
}
var v Types
if err := decodeEncode(input, &v); err != nil {
t.Fatal(err)
}
}
{
type AllTypes struct {
Int uint
String string
Bytes []byte
Bool bool
Raw rlp.RawValue
Slice []*AllTypes
Array [3]*AllTypes
Iface []interface{}
}
var v AllTypes
if err := decodeEncode(input, &v); err != nil {
t.Fatal(err)
}
}
{
if err := decodeEncode(input, [10]byte{}); err != nil {
t.Fatal(err)
}
}
{
var v struct {
Byte [10]byte
Rool [10]bool
}
if err := decodeEncode(input, &v); err != nil {
t.Fatal(err)
}
}
{
var h Header
if err := decodeEncode(input, &h); err != nil {
t.Fatal(err)
}
var b Block
if err := decodeEncode(input, &b); err != nil {
t.Fatal(err)
}
var tx Transaction
if err := decodeEncode(input, &tx); err != nil {
t.Fatal(err)
}
var txs Transactions
if err := decodeEncode(input, &txs); err != nil {
t.Fatal(err)
}
var rs Receipts
if err := decodeEncode(input, &rs); err != nil {
t.Fatal(err)
}
}
{
var v struct {
AnIntPtr *big.Int
AnInt big.Int
AnU256Ptr *uint256.Int
AnU256 uint256.Int
NotAnU256 [4]uint64
}
if err := decodeEncode(input, &v); err != nil {
t.Fatal(err)
}
}
}

View File

@ -14,12 +14,31 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package rlp package vm
import "testing" import (
"testing"
func Fuzz(f *testing.F) { "github.com/ethereum/go-ethereum/common"
f.Fuzz(func(t *testing.T, data []byte) { )
fuzz(data)
func FuzzPrecompiledContracts(f *testing.F) {
// Create list of addresses
var addrs []common.Address
for k := range allPrecompiles {
addrs = append(addrs, k)
}
f.Fuzz(func(t *testing.T, addr uint8, input []byte) {
a := addrs[int(addr)%len(addrs)]
p := allPrecompiles[a]
gas := p.RequiredGas(input)
if gas > 10_000_000 {
return
}
inWant := string(input)
RunPrecompiledContract(p, input, gas)
if inHave := string(input); inWant != inHave {
t.Errorf("Precompiled %v modified input data", a)
}
}) })
} }

View File

@ -18,13 +18,11 @@ package runtime
import ( import (
"testing" "testing"
"github.com/ethereum/go-ethereum/core/vm/runtime"
) )
func Fuzz(f *testing.F) { func FuzzVmRuntime(f *testing.F) {
f.Fuzz(func(t *testing.T, code, input []byte) { f.Fuzz(func(t *testing.T, code, input []byte) {
runtime.Execute(code, input, &runtime.Config{ Execute(code, input, &Config{
GasLimit: 12000000, GasLimit: 12000000,
}) })
}) })

View File

@ -21,6 +21,7 @@ import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"math/big" "math/big"
"testing"
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -28,7 +29,6 @@ import (
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/protocols/snap"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -36,6 +36,56 @@ import (
fuzz "github.com/google/gofuzz" fuzz "github.com/google/gofuzz"
) )
func FuzzARange(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
doFuzz(data, &GetAccountRangePacket{}, GetAccountRangeMsg)
})
}
func FuzzSRange(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
doFuzz(data, &GetStorageRangesPacket{}, GetStorageRangesMsg)
})
}
func FuzzByteCodes(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
doFuzz(data, &GetByteCodesPacket{}, GetByteCodesMsg)
})
}
func FuzzTrieNodes(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
doFuzz(data, &GetTrieNodesPacket{}, GetTrieNodesMsg)
})
}
func doFuzz(input []byte, obj interface{}, code int) {
bc := getChain()
defer bc.Stop()
fuzz.NewFromGoFuzz(input).Fuzz(obj)
var data []byte
switch p := obj.(type) {
case *GetTrieNodesPacket:
p.Root = trieRoot
data, _ = rlp.EncodeToBytes(obj)
default:
data, _ = rlp.EncodeToBytes(obj)
}
cli := &dummyRW{
code: uint64(code),
data: data,
}
peer := NewFakePeer(65, "gazonk01", cli)
err := HandleMessage(&dummyBackend{bc}, peer)
switch {
case err == nil && cli.writeCount != 1:
panic(fmt.Sprintf("Expected 1 response, got %d", cli.writeCount))
case err != nil && cli.writeCount != 0:
panic(fmt.Sprintf("Expected 0 response, got %d", cli.writeCount))
}
}
var trieRoot common.Hash var trieRoot common.Hash
func getChain() *core.BlockChain { func getChain() *core.BlockChain {
@ -87,9 +137,9 @@ type dummyBackend struct {
} }
func (d *dummyBackend) Chain() *core.BlockChain { return d.chain } func (d *dummyBackend) Chain() *core.BlockChain { return d.chain }
func (d *dummyBackend) RunPeer(*snap.Peer, snap.Handler) error { return nil } func (d *dummyBackend) RunPeer(*Peer, Handler) error { return nil }
func (d *dummyBackend) PeerInfo(enode.ID) interface{} { return "Foo" } func (d *dummyBackend) PeerInfo(enode.ID) interface{} { return "Foo" }
func (d *dummyBackend) Handle(*snap.Peer, snap.Packet) error { return nil } func (d *dummyBackend) Handle(*Peer, Packet) error { return nil }
type dummyRW struct { type dummyRW struct {
code uint64 code uint64
@ -110,34 +160,3 @@ func (d *dummyRW) WriteMsg(msg p2p.Msg) error {
d.writeCount++ d.writeCount++
return nil return nil
} }
func doFuzz(input []byte, obj interface{}, code int) int {
if len(input) > 1024*4 {
return -1
}
bc := getChain()
defer bc.Stop()
backend := &dummyBackend{bc}
fuzz.NewFromGoFuzz(input).Fuzz(obj)
var data []byte
switch p := obj.(type) {
case *snap.GetTrieNodesPacket:
p.Root = trieRoot
data, _ = rlp.EncodeToBytes(obj)
default:
data, _ = rlp.EncodeToBytes(obj)
}
cli := &dummyRW{
code: uint64(code),
data: data,
}
peer := snap.NewFakePeer(65, "gazonk01", cli)
err := snap.HandleMessage(backend, peer)
switch {
case err == nil && cli.writeCount != 1:
panic(fmt.Sprintf("Expected 1 response, got %d", cli.writeCount))
case err != nil && cli.writeCount != 0:
panic(fmt.Sprintf("Expected 0 response, got %d", cli.writeCount))
}
return 1
}

2
go.mod
View File

@ -33,7 +33,7 @@ require (
github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang/protobuf v1.5.3 github.com/golang/protobuf v1.5.3
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb
github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa github.com/google/gofuzz v1.2.0
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2
github.com/graph-gophers/graphql-go v1.3.0 github.com/graph-gophers/graphql-go v1.3.0

4
go.sum
View File

@ -305,8 +305,8 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa h1:Q75Upo5UN4JbPFURXZ8nLKYUvF85dyFRop/vQ0Rv+64= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=

View File

@ -48,38 +48,26 @@ DOG
cd - cd -
} }
function build_native_go_fuzzer() {
fuzzer=$1
function=$2
path=$3
tags="-tags gofuzz"
if [[ $SANITIZER == *coverage* ]]; then
coverbuild $path $function $fuzzer $coverpkg
else
go-118-fuzz-build $tags -o $fuzzer.a -func $function $path
$CXX $CXXFLAGS $LIB_FUZZING_ENGINE $fuzzer.a -o $OUT/$fuzzer
fi
}
function compile_fuzzer() { function compile_fuzzer() {
path=$GOPATH/src/github.com/ethereum/go-ethereum/$1 package=$1
function=$2 function=$2
fuzzer=$3 fuzzer=$3
file=$4
path=$GOPATH/src/$package
echo "Building $fuzzer" echo "Building $fuzzer"
cd $path cd $path
# Install build dependencies # Install build dependencies
go install github.com/AdamKorcz/go-118-fuzz-build@latest go mod tidy
go get github.com/AdamKorcz/go-118-fuzz-build/testing go get github.com/holiman/gofuzz-shim/testing
# Test if file contains a line with "func $function(" and "testing.F". if [[ $SANITIZER == *coverage* ]]; then
if [ $(grep -r "func $function(" $path | grep "testing.F" | wc -l) -eq 1 ] coverbuild $path $function $fuzzer $coverpkg
then
build_native_go_fuzzer $fuzzer $function $path
else else
echo "Could not find the function: func ${function}(f *testing.F)" gofuzz-shim --func $function --package $package -f $file -o $fuzzer.a
$CXX $CXXFLAGS $LIB_FUZZING_ENGINE $fuzzer.a -o $OUT/$fuzzer
fi fi
## Check if there exists a seed corpus file ## Check if there exists a seed corpus file
@ -92,42 +80,140 @@ function compile_fuzzer() {
cd - cd -
} }
compile_fuzzer tests/fuzzers/bitutil FuzzEncoder fuzzBitutilEncoder go install github.com/holiman/gofuzz-shim@latest
compile_fuzzer tests/fuzzers/bitutil FuzzDecoder fuzzBitutilDecoder repo=$GOPATH/src/github.com/ethereum/go-ethereum
compile_fuzzer tests/fuzzers/bn256 FuzzAdd fuzzBn256Add compile_fuzzer github.com/ethereum/go-ethereum/accounts/abi \
compile_fuzzer tests/fuzzers/bn256 FuzzMul fuzzBn256Mul FuzzABI fuzzAbi \
compile_fuzzer tests/fuzzers/bn256 FuzzPair fuzzBn256Pair $repo/accounts/abi/abifuzzer_test.go
compile_fuzzer tests/fuzzers/runtime Fuzz fuzzVmRuntime
compile_fuzzer tests/fuzzers/keystore Fuzz fuzzKeystore
compile_fuzzer tests/fuzzers/txfetcher Fuzz fuzzTxfetcher
compile_fuzzer tests/fuzzers/rlp Fuzz fuzzRlp
compile_fuzzer tests/fuzzers/trie Fuzz fuzzTrie
compile_fuzzer tests/fuzzers/stacktrie Fuzz fuzzStackTrie
compile_fuzzer tests/fuzzers/difficulty Fuzz fuzzDifficulty
compile_fuzzer tests/fuzzers/abi Fuzz fuzzAbi
compile_fuzzer tests/fuzzers/les Fuzz fuzzLes
compile_fuzzer tests/fuzzers/secp256k1 Fuzz fuzzSecp256k1
compile_fuzzer tests/fuzzers/vflux FuzzClientPool fuzzClientPool
compile_fuzzer tests/fuzzers/bls12381 FuzzG1Add fuzz_g1_add compile_fuzzer github.com/ethereum/go-ethereum/common/bitutil \
compile_fuzzer tests/fuzzers/bls12381 FuzzG1Mul fuzz_g1_mul FuzzEncoder fuzzBitutilEncoder \
compile_fuzzer tests/fuzzers/bls12381 FuzzG1MultiExp fuzz_g1_multiexp $repo/common/bitutil/compress_test.go
compile_fuzzer tests/fuzzers/bls12381 FuzzG2Add fuzz_g2_add
compile_fuzzer tests/fuzzers/bls12381 FuzzG2Mul fuzz_g2_mul
compile_fuzzer tests/fuzzers/bls12381 FuzzG2MultiExp fuzz_g2_multiexp
compile_fuzzer tests/fuzzers/bls12381 FuzzPairing fuzz_pairing
compile_fuzzer tests/fuzzers/bls12381 FuzzMapG1 fuzz_map_g1
compile_fuzzer tests/fuzzers/bls12381 FuzzMapG2 fuzz_map_g2
compile_fuzzer tests/fuzzers/bls12381 FuzzCrossG1Add fuzz_cross_g1_add compile_fuzzer github.com/ethereum/go-ethereum/common/bitutil \
compile_fuzzer tests/fuzzers/bls12381 FuzzCrossG1MultiExp fuzz_cross_g1_multiexp FuzzDecoder fuzzBitutilDecoder \
compile_fuzzer tests/fuzzers/bls12381 FuzzCrossG2Add fuzz_cross_g2_add $repo/common/bitutil/compress_test.go
compile_fuzzer tests/fuzzers/bls12381 FuzzCrossPairing fuzz_cross_pairing
compile_fuzzer tests/fuzzers/snap FuzzARange fuzz_account_range compile_fuzzer github.com/ethereum/go-ethereum/core/vm/runtime \
compile_fuzzer tests/fuzzers/snap FuzzSRange fuzz_storage_range FuzzVmRuntime fuzzVmRuntime\
compile_fuzzer tests/fuzzers/snap FuzzByteCodes fuzz_byte_codes $repo/core/vm/runtime/runtime_fuzz_test.go
compile_fuzzer tests/fuzzers/snap FuzzTrieNodes fuzz_trie_nodes
compile_fuzzer github.com/ethereum/go-ethereum/core/vm \
FuzzPrecompiledContracts fuzzPrecompiledContracts\
$repo/core/vm/contracts_fuzz_test.go,$repo/core/vm/contracts_test.go
compile_fuzzer github.com/ethereum/go-ethereum/core/types \
FuzzRLP fuzzRlp \
$repo/core/types/rlp_fuzzer_test.go
compile_fuzzer github.com/ethereum/go-ethereum/crypto/blake2b \
Fuzz fuzzBlake2b \
$repo/crypto/blake2b/blake2b_f_fuzz_test.go
compile_fuzzer github.com/ethereum/go-ethereum/accounts/keystore \
FuzzPassword fuzzKeystore \
$repo/accounts/keystore/keystore_fuzzing_test.go
pkg=$repo/trie/
compile_fuzzer github.com/ethereum/go-ethereum/trie \
FuzzTrie fuzzTrie \
$pkg/trie_test.go,$pkg/database_test.go,$pkg/tracer_test.go,$pkg/proof_test.go,$pkg/iterator_test.go,$pkg/sync_test.go
compile_fuzzer github.com/ethereum/go-ethereum/trie \
FuzzStackTrie fuzzStackTrie \
$pkg/stacktrie_fuzzer_test.go,$pkg/iterator_test.go,$pkg/trie_test.go,$pkg/database_test.go,$pkg/tracer_test.go,$pkg/proof_test.go,$pkg/sync_test.go
#compile_fuzzer tests/fuzzers/snap FuzzARange fuzz_account_range
compile_fuzzer github.com/ethereum/go-ethereum/eth/protocols/snap \
FuzzARange fuzz_account_range \
$repo/eth/protocols/snap/handler_fuzzing_test.go
compile_fuzzer github.com/ethereum/go-ethereum/eth/protocols/snap \
FuzzSRange fuzz_storage_range \
$repo/eth/protocols/snap/handler_fuzzing_test.go
compile_fuzzer github.com/ethereum/go-ethereum/eth/protocols/snap \
FuzzByteCodes fuzz_byte_codes \
$repo/eth/protocols/snap/handler_fuzzing_test.go
compile_fuzzer github.com/ethereum/go-ethereum/eth/protocols/snap \
FuzzTrieNodes fuzz_trie_nodes\
$repo/eth/protocols/snap/handler_fuzzing_test.go
compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bn256 \
FuzzAdd fuzzBn256Add\
$repo/tests/fuzzers/bn256/bn256_test.go
compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bn256 \
FuzzMul fuzzBn256Mul \
$repo/tests/fuzzers/bn256/bn256_test.go
compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bn256 \
FuzzPair fuzzBn256Pair \
$repo/tests/fuzzers/bn256/bn256_test.go
compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/txfetcher \
Fuzz fuzzTxfetcher \
$repo/tests/fuzzers/txfetcher/txfetcher_test.go
compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
FuzzG1Add fuzz_g1_add\
$repo/tests/fuzzers/bls12381/bls12381_test.go
compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
FuzzG1Mul fuzz_g1_mul\
$repo/tests/fuzzers/bls12381/bls12381_test.go
compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
FuzzG1MultiExp fuzz_g1_multiexp \
$repo/tests/fuzzers/bls12381/bls12381_test.go
compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
FuzzG2Add fuzz_g2_add \
$repo/tests/fuzzers/bls12381/bls12381_test.go
compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
FuzzG2Mul fuzz_g2_mul\
$repo/tests/fuzzers/bls12381/bls12381_test.go
compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
FuzzG2MultiExp fuzz_g2_multiexp \
$repo/tests/fuzzers/bls12381/bls12381_test.go
compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
FuzzPairing fuzz_pairing \
$repo/tests/fuzzers/bls12381/bls12381_test.go
compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
FuzzMapG1 fuzz_map_g1\
$repo/tests/fuzzers/bls12381/bls12381_test.go
compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
FuzzMapG2 fuzz_map_g2 \
$repo/tests/fuzzers/bls12381/bls12381_test.go
compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
FuzzCrossG1Add fuzz_cross_g1_add \
$repo/tests/fuzzers/bls12381/bls12381_test.go
compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
FuzzCrossG1MultiExp fuzz_cross_g1_multiexp \
$repo/tests/fuzzers/bls12381/bls12381_test.go
compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
FuzzCrossG2Add fuzz_cross_g2_add \
$repo/tests/fuzzers/bls12381/bls12381_test.go
compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
FuzzCrossPairing fuzz_cross_pairing\
$repo/tests/fuzzers/bls12381/bls12381_test.go
compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/secp256k1 \
Fuzz fuzzSecp256k1\
$repo/tests/fuzzers/secp256k1/secp_test.go
#compile_fuzzer tests/fuzzers/vflux FuzzClientPool fuzzClientPool
#compile_fuzzer tests/fuzzers/difficulty Fuzz fuzzDifficulty
#compile_fuzzer tests/fuzzers/les Fuzz fuzzLes
#TODO: move this to tests/fuzzers, if possible
compile_fuzzer crypto/blake2b Fuzz fuzzBlake2b

View File

@ -1,68 +0,0 @@
// Copyright 2023 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package bitutil
import (
"bytes"
"testing"
"github.com/ethereum/go-ethereum/common/bitutil"
)
func FuzzEncoder(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
fuzzEncode(data)
})
}
func FuzzDecoder(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
fuzzDecode(data)
})
}
// fuzzEncode implements a go-fuzz fuzzer method to test the bitset encoding and
// decoding algorithm.
func fuzzEncode(data []byte) {
proc, _ := bitutil.DecompressBytes(bitutil.CompressBytes(data), len(data))
if !bytes.Equal(data, proc) {
panic("content mismatch")
}
}
// fuzzDecode implements a go-fuzz fuzzer method to test the bit decoding and
// reencoding algorithm.
func fuzzDecode(data []byte) {
blob, err := bitutil.DecompressBytes(data, 1024)
if err != nil {
return
}
// re-compress it (it's OK if the re-compressed differs from the
// original - the first input may not have been compressed at all)
comp := bitutil.CompressBytes(blob)
if len(comp) > len(blob) {
// After compression, it must be smaller or equal
panic("bad compression")
}
// But decompressing it once again should work
decomp, err := bitutil.DecompressBytes(data, 1024)
if err != nil {
panic(err)
}
if !bytes.Equal(decomp, blob) {
panic("content mismatch")
}
}

View File

@ -1,37 +0,0 @@
// Copyright 2019 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package keystore
import (
"os"
"github.com/ethereum/go-ethereum/accounts/keystore"
)
func fuzz(input []byte) int {
ks := keystore.NewKeyStore("/tmp/ks", keystore.LightScryptN, keystore.LightScryptP)
a, err := ks.NewAccount(string(input))
if err != nil {
panic(err)
}
if err := ks.Unlock(a, string(input)); err != nil {
panic(err)
}
os.Remove(a.URL.Path)
return 1
}

View File

@ -1 +0,0 @@
Ë€€€À€ÀÃÀÀÀÀ

View File

@ -1,2 +0,0 @@
øNƒ“à€€€‚
 <EFBFBD>aùËåÀP?-'´{ÏЋD<><1E>³fÆj\ÿE÷ ~ì<>•ÒçF?1(íij6<6A>@Év ±LÀÝÚ

View File

@ -1,143 +0,0 @@
// Copyright 2019 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package rlp
import (
"bytes"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/holiman/uint256"
)
func decodeEncode(input []byte, val interface{}, i int) {
if err := rlp.DecodeBytes(input, val); err == nil {
output, err := rlp.EncodeToBytes(val)
if err != nil {
panic(err)
}
if !bytes.Equal(input, output) {
panic(fmt.Sprintf("case %d: encode-decode is not equal, \ninput : %x\noutput: %x", i, input, output))
}
}
}
func fuzz(input []byte) int {
if len(input) == 0 {
return 0
}
if len(input) > 500*1024 {
return 0
}
var i int
{
rlp.Split(input)
}
{
if elems, _, err := rlp.SplitList(input); err == nil {
rlp.CountValues(elems)
}
}
{
rlp.NewStream(bytes.NewReader(input), 0).Decode(new(interface{}))
}
{
decodeEncode(input, new(interface{}), i)
i++
}
{
var v struct {
Int uint
String string
Bytes []byte
}
decodeEncode(input, &v, i)
i++
}
{
type Types struct {
Bool bool
Raw rlp.RawValue
Slice []*Types
Iface []interface{}
}
var v Types
decodeEncode(input, &v, i)
i++
}
{
type AllTypes struct {
Int uint
String string
Bytes []byte
Bool bool
Raw rlp.RawValue
Slice []*AllTypes
Array [3]*AllTypes
Iface []interface{}
}
var v AllTypes
decodeEncode(input, &v, i)
i++
}
{
decodeEncode(input, [10]byte{}, i)
i++
}
{
var v struct {
Byte [10]byte
Rool [10]bool
}
decodeEncode(input, &v, i)
i++
}
{
var h types.Header
decodeEncode(input, &h, i)
i++
var b types.Block
decodeEncode(input, &b, i)
i++
var t types.Transaction
decodeEncode(input, &t, i)
i++
var txs types.Transactions
decodeEncode(input, &txs, i)
i++
var rs types.Receipts
decodeEncode(input, &rs, i)
}
{
i++
var v struct {
AnIntPtr *big.Int
AnInt big.Int
AnU256Ptr *uint256.Int
AnU256 uint256.Int
NotAnU256 [4]uint64
}
decodeEncode(input, &v, i)
}
return 1
}

View File

@ -35,7 +35,7 @@ func Fuzz(f *testing.F) {
}) })
} }
func fuzz(dataP1, dataP2 []byte) int { func fuzz(dataP1, dataP2 []byte) {
var ( var (
curveA = secp256k1.S256() curveA = secp256k1.S256()
curveB = btcec.S256() curveB = btcec.S256()
@ -50,5 +50,4 @@ func fuzz(dataP1, dataP2 []byte) int {
fmt.Printf("%s %s %s %s\n", x1, y1, x2, y2) fmt.Printf("%s %s %s %s\n", x1, y1, x2, y2)
panic(fmt.Sprintf("Addition failed: geth: %s %s btcd: %s %s", resAX, resAY, resBX, resBY)) panic(fmt.Sprintf("Addition failed: geth: %s %s btcd: %s %s", resAX, resAY, resBX, resBY))
} }
return 0
} }

View File

@ -1,47 +0,0 @@
// Copyright 2023 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package snap
import (
"testing"
"github.com/ethereum/go-ethereum/eth/protocols/snap"
)
func FuzzARange(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
doFuzz(data, &snap.GetAccountRangePacket{}, snap.GetAccountRangeMsg)
})
}
func FuzzSRange(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
doFuzz(data, &snap.GetStorageRangesPacket{}, snap.GetStorageRangesMsg)
})
}
func FuzzByteCodes(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
doFuzz(data, &snap.GetByteCodesPacket{}, snap.GetByteCodesMsg)
})
}
func FuzzTrieNodes(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
doFuzz(data, &snap.GetTrieNodesPacket{}, snap.GetTrieNodesMsg)
})
}

View File

@ -1,248 +0,0 @@
// Copyright 2020 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package stacktrie
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"hash"
"io"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode"
"golang.org/x/crypto/sha3"
"golang.org/x/exp/slices"
)
type fuzzer struct {
input io.Reader
exhausted bool
debugging bool
}
func (f *fuzzer) read(size int) []byte {
out := make([]byte, size)
if _, err := f.input.Read(out); err != nil {
f.exhausted = true
}
return out
}
func (f *fuzzer) readSlice(min, max int) []byte {
var a uint16
binary.Read(f.input, binary.LittleEndian, &a)
size := min + int(a)%(max-min)
out := make([]byte, size)
if _, err := f.input.Read(out); err != nil {
f.exhausted = true
}
return out
}
// spongeDb is a dummy db backend which accumulates writes in a sponge
type spongeDb struct {
sponge hash.Hash
debug bool
}
func (s *spongeDb) Has(key []byte) (bool, error) { panic("implement me") }
func (s *spongeDb) Get(key []byte) ([]byte, error) { return nil, errors.New("no such elem") }
func (s *spongeDb) Delete(key []byte) error { panic("implement me") }
func (s *spongeDb) NewBatch() ethdb.Batch { return &spongeBatch{s} }
func (s *spongeDb) NewBatchWithSize(size int) ethdb.Batch { return &spongeBatch{s} }
func (s *spongeDb) NewSnapshot() (ethdb.Snapshot, error) { panic("implement me") }
func (s *spongeDb) Stat(property string) (string, error) { panic("implement me") }
func (s *spongeDb) Compact(start []byte, limit []byte) error { panic("implement me") }
func (s *spongeDb) Close() error { return nil }
func (s *spongeDb) Put(key []byte, value []byte) error {
if s.debug {
fmt.Printf("db.Put %x : %x\n", key, value)
}
s.sponge.Write(key)
s.sponge.Write(value)
return nil
}
func (s *spongeDb) NewIterator(prefix []byte, start []byte) ethdb.Iterator { panic("implement me") }
// spongeBatch is a dummy batch which immediately writes to the underlying spongedb
type spongeBatch struct {
db *spongeDb
}
func (b *spongeBatch) Put(key, value []byte) error {
b.db.Put(key, value)
return nil
}
func (b *spongeBatch) Delete(key []byte) error { panic("implement me") }
func (b *spongeBatch) ValueSize() int { return 100 }
func (b *spongeBatch) Write() error { return nil }
func (b *spongeBatch) Reset() {}
func (b *spongeBatch) Replay(w ethdb.KeyValueWriter) error { return nil }
type kv struct {
k, v []byte
}
// Fuzz is the fuzzing entry-point.
// The function must return
//
// - 1 if the fuzzer should increase priority of the
// given input during subsequent fuzzing (for example, the input is lexically
// correct and was parsed successfully);
// - -1 if the input must not be added to corpus even if gives new coverage; and
// - 0 otherwise
//
// other values are reserved for future use.
func fuzz(data []byte) int {
f := fuzzer{
input: bytes.NewReader(data),
exhausted: false,
}
return f.fuzz()
}
func Debug(data []byte) int {
f := fuzzer{
input: bytes.NewReader(data),
exhausted: false,
debugging: true,
}
return f.fuzz()
}
func (f *fuzzer) fuzz() int {
// This spongeDb is used to check the sequence of disk-db-writes
var (
spongeA = &spongeDb{sponge: sha3.NewLegacyKeccak256()}
dbA = trie.NewDatabase(rawdb.NewDatabase(spongeA), nil)
trieA = trie.NewEmpty(dbA)
spongeB = &spongeDb{sponge: sha3.NewLegacyKeccak256()}
dbB = trie.NewDatabase(rawdb.NewDatabase(spongeB), nil)
options = trie.NewStackTrieOptions().WithWriter(func(path []byte, hash common.Hash, blob []byte) {
rawdb.WriteTrieNode(spongeB, common.Hash{}, path, hash, blob, dbB.Scheme())
})
trieB = trie.NewStackTrie(options)
vals []kv
useful bool
maxElements = 10000
// operate on unique keys only
keys = make(map[string]struct{})
)
// Fill the trie with elements
for i := 0; !f.exhausted && i < maxElements; i++ {
k := f.read(32)
v := f.readSlice(1, 500)
if f.exhausted {
// If it was exhausted while reading, the value may be all zeroes,
// thus 'deletion' which is not supported on stacktrie
break
}
if _, present := keys[string(k)]; present {
// This key is a duplicate, ignore it
continue
}
keys[string(k)] = struct{}{}
vals = append(vals, kv{k: k, v: v})
trieA.MustUpdate(k, v)
useful = true
}
if !useful {
return 0
}
// Flush trie -> database
rootA, nodes, err := trieA.Commit(false)
if err != nil {
panic(err)
}
if nodes != nil {
dbA.Update(rootA, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
}
// Flush memdb -> disk (sponge)
dbA.Commit(rootA, false)
// Stacktrie requires sorted insertion
slices.SortFunc(vals, func(a, b kv) int {
return bytes.Compare(a.k, b.k)
})
for _, kv := range vals {
if f.debugging {
fmt.Printf("{\"%#x\" , \"%#x\"} // stacktrie.Update\n", kv.k, kv.v)
}
trieB.MustUpdate(kv.k, kv.v)
}
rootB := trieB.Hash()
trieB.Commit()
if rootA != rootB {
panic(fmt.Sprintf("roots differ: (trie) %x != %x (stacktrie)", rootA, rootB))
}
sumA := spongeA.sponge.Sum(nil)
sumB := spongeB.sponge.Sum(nil)
if !bytes.Equal(sumA, sumB) {
panic(fmt.Sprintf("sequence differ: (trie) %x != %x (stacktrie)", sumA, sumB))
}
// Ensure all the nodes are persisted correctly
var (
nodeset = make(map[string][]byte) // path -> blob
optionsC = trie.NewStackTrieOptions().WithWriter(func(path []byte, hash common.Hash, blob []byte) {
if crypto.Keccak256Hash(blob) != hash {
panic("invalid node blob")
}
nodeset[string(path)] = common.CopyBytes(blob)
})
trieC = trie.NewStackTrie(optionsC)
checked int
)
for _, kv := range vals {
trieC.MustUpdate(kv.k, kv.v)
}
rootC := trieC.Commit()
if rootA != rootC {
panic(fmt.Sprintf("roots differ: (trie) %x != %x (stacktrie)", rootA, rootC))
}
trieA, _ = trie.New(trie.TrieID(rootA), dbA)
iterA := trieA.MustNodeIterator(nil)
for iterA.Next(true) {
if iterA.Hash() == (common.Hash{}) {
if _, present := nodeset[string(iterA.Path())]; present {
panic("unexpected tiny node")
}
continue
}
nodeBlob, present := nodeset[string(iterA.Path())]
if !present {
panic("missing node")
}
if !bytes.Equal(nodeBlob, iterA.NodeBlob()) {
panic("node blob is not matched")
}
checked += 1
}
if checked != len(nodeset) {
panic("node number is not matched")
}
return 1
}

View File

@ -1,25 +0,0 @@
// Copyright 2023 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package stacktrie
import "testing"
func Fuzz(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
fuzz(data)
})
}

View File

@ -1,201 +0,0 @@
// Copyright 2019 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package trie
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode"
)
// randTest performs random trie operations.
// Instances of this test are created by Generate.
type randTest []randTestStep
type randTestStep struct {
op int
key []byte // for opUpdate, opDelete, opGet
value []byte // for opUpdate
err error // for debugging
}
type proofDb struct{}
func (proofDb) Put(key []byte, value []byte) error {
return nil
}
func (proofDb) Delete(key []byte) error {
return nil
}
const (
opUpdate = iota
opDelete
opGet
opHash
opCommit
opItercheckhash
opProve
opMax // boundary value, not an actual op
)
type dataSource struct {
input []byte
reader *bytes.Reader
}
func newDataSource(input []byte) *dataSource {
return &dataSource{
input, bytes.NewReader(input),
}
}
func (ds *dataSource) readByte() byte {
if b, err := ds.reader.ReadByte(); err != nil {
return 0
} else {
return b
}
}
func (ds *dataSource) Read(buf []byte) (int, error) {
return ds.reader.Read(buf)
}
func (ds *dataSource) Ended() bool {
return ds.reader.Len() == 0
}
func Generate(input []byte) randTest {
var allKeys [][]byte
r := newDataSource(input)
genKey := func() []byte {
if len(allKeys) < 2 || r.readByte() < 0x0f {
// new key
key := make([]byte, r.readByte()%50)
r.Read(key)
allKeys = append(allKeys, key)
return key
}
// use existing key
return allKeys[int(r.readByte())%len(allKeys)]
}
var steps randTest
for i := 0; !r.Ended(); i++ {
step := randTestStep{op: int(r.readByte()) % opMax}
switch step.op {
case opUpdate:
step.key = genKey()
step.value = make([]byte, 8)
binary.BigEndian.PutUint64(step.value, uint64(i))
case opGet, opDelete, opProve:
step.key = genKey()
}
steps = append(steps, step)
if len(steps) > 500 {
break
}
}
return steps
}
// Fuzz is the fuzzing entry-point.
// The function must return
//
// - 1 if the fuzzer should increase priority of the
// given input during subsequent fuzzing (for example, the input is lexically
// correct and was parsed successfully);
// - -1 if the input must not be added to corpus even if gives new coverage; and
// - 0 otherwise
//
// other values are reserved for future use.
func fuzz(input []byte) int {
program := Generate(input)
if len(program) == 0 {
return 0
}
if err := runRandTest(program); err != nil {
panic(err)
}
return 1
}
func runRandTest(rt randTest) error {
var (
triedb = trie.NewDatabase(rawdb.NewMemoryDatabase(), nil)
tr = trie.NewEmpty(triedb)
origin = types.EmptyRootHash
values = make(map[string]string) // tracks content of the trie
)
for i, step := range rt {
switch step.op {
case opUpdate:
tr.MustUpdate(step.key, step.value)
values[string(step.key)] = string(step.value)
case opDelete:
tr.MustDelete(step.key)
delete(values, string(step.key))
case opGet:
v := tr.MustGet(step.key)
want := values[string(step.key)]
if string(v) != want {
rt[i].err = fmt.Errorf("mismatch for key %#x, got %#x want %#x", step.key, v, want)
}
case opHash:
tr.Hash()
case opCommit:
hash, nodes, err := tr.Commit(false)
if err != nil {
return err
}
if nodes != nil {
if err := triedb.Update(hash, origin, 0, trienode.NewWithNodeSet(nodes), nil); err != nil {
return err
}
}
newtr, err := trie.New(trie.TrieID(hash), triedb)
if err != nil {
return err
}
tr = newtr
origin = hash
case opItercheckhash:
checktr := trie.NewEmpty(triedb)
it := trie.NewIterator(tr.MustNodeIterator(nil))
for it.Next() {
checktr.MustUpdate(it.Key, it.Value)
}
if tr.Hash() != checktr.Hash() {
return errors.New("hash mismatch in opItercheckhash")
}
case opProve:
rt[i].err = tr.Prove(step.key, proofDb{})
}
// Abort the test on error.
if rt[i].err != nil {
return rt[i].err
}
}
return nil
}

View File

@ -1,25 +0,0 @@
// Copyright 2023 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package trie
import "testing"
func Fuzz(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
fuzz(data)
})
}

View File

@ -0,0 +1,155 @@
// Copyright 2020 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package trie
import (
"bytes"
"encoding/binary"
"fmt"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/trie/trienode"
"golang.org/x/crypto/sha3"
"golang.org/x/exp/slices"
)
func FuzzStackTrie(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
fuzz(data, false)
})
}
func fuzz(data []byte, debugging bool) {
// This spongeDb is used to check the sequence of disk-db-writes
var (
input = bytes.NewReader(data)
spongeA = &spongeDb{sponge: sha3.NewLegacyKeccak256()}
dbA = NewDatabase(rawdb.NewDatabase(spongeA), nil)
trieA = NewEmpty(dbA)
spongeB = &spongeDb{sponge: sha3.NewLegacyKeccak256()}
dbB = NewDatabase(rawdb.NewDatabase(spongeB), nil)
options = NewStackTrieOptions().WithWriter(func(path []byte, hash common.Hash, blob []byte) {
rawdb.WriteTrieNode(spongeB, common.Hash{}, path, hash, blob, dbB.Scheme())
})
trieB = NewStackTrie(options)
vals []*kv
maxElements = 10000
// operate on unique keys only
keys = make(map[string]struct{})
)
// Fill the trie with elements
for i := 0; input.Len() > 0 && i < maxElements; i++ {
k := make([]byte, 32)
input.Read(k)
var a uint16
binary.Read(input, binary.LittleEndian, &a)
a = 1 + a%100
v := make([]byte, a)
input.Read(v)
if input.Len() == 0 {
// If it was exhausted while reading, the value may be all zeroes,
// thus 'deletion' which is not supported on stacktrie
break
}
if _, present := keys[string(k)]; present {
// This key is a duplicate, ignore it
continue
}
keys[string(k)] = struct{}{}
vals = append(vals, &kv{k: k, v: v})
trieA.MustUpdate(k, v)
}
if len(vals) == 0 {
return
}
// Flush trie -> database
rootA, nodes, err := trieA.Commit(false)
if err != nil {
panic(err)
}
if nodes != nil {
dbA.Update(rootA, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
}
// Flush memdb -> disk (sponge)
dbA.Commit(rootA, false)
// Stacktrie requires sorted insertion
slices.SortFunc(vals, (*kv).cmp)
for _, kv := range vals {
if debugging {
fmt.Printf("{\"%#x\" , \"%#x\"} // stacktrie.Update\n", kv.k, kv.v)
}
trieB.MustUpdate(kv.k, kv.v)
}
rootB := trieB.Hash()
trieB.Commit()
if rootA != rootB {
panic(fmt.Sprintf("roots differ: (trie) %x != %x (stacktrie)", rootA, rootB))
}
sumA := spongeA.sponge.Sum(nil)
sumB := spongeB.sponge.Sum(nil)
if !bytes.Equal(sumA, sumB) {
panic(fmt.Sprintf("sequence differ: (trie) %x != %x (stacktrie)", sumA, sumB))
}
// Ensure all the nodes are persisted correctly
var (
nodeset = make(map[string][]byte) // path -> blob
optionsC = NewStackTrieOptions().WithWriter(func(path []byte, hash common.Hash, blob []byte) {
if crypto.Keccak256Hash(blob) != hash {
panic("invalid node blob")
}
nodeset[string(path)] = common.CopyBytes(blob)
})
trieC = NewStackTrie(optionsC)
checked int
)
for _, kv := range vals {
trieC.MustUpdate(kv.k, kv.v)
}
rootC := trieC.Commit()
if rootA != rootC {
panic(fmt.Sprintf("roots differ: (trie) %x != %x (stacktrie)", rootA, rootC))
}
trieA, _ = New(TrieID(rootA), dbA)
iterA := trieA.MustNodeIterator(nil)
for iterA.Next(true) {
if iterA.Hash() == (common.Hash{}) {
if _, present := nodeset[string(iterA.Path())]; present {
panic("unexpected tiny node")
}
continue
}
nodeBlob, present := nodeset[string(iterA.Path())]
if !present {
panic("missing node")
}
if !bytes.Equal(nodeBlob, iterA.NodeBlob()) {
panic("node blob is not matched")
}
checked += 1
}
if checked != len(nodeset) {
panic("node number is not matched")
}
}

View File

@ -22,6 +22,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"hash" "hash"
"io"
"math/big" "math/big"
"math/rand" "math/rand"
"reflect" "reflect"
@ -362,7 +363,9 @@ func TestRandomCases(t *testing.T) {
{op: 1, key: common.Hex2Bytes("980c393656413a15c8da01978ed9f89feb80b502f58f2d640e3a2f5f7a99a7018f1b573befd92053ac6f78fca4a87268"), value: common.Hex2Bytes("")}, // step 24 {op: 1, key: common.Hex2Bytes("980c393656413a15c8da01978ed9f89feb80b502f58f2d640e3a2f5f7a99a7018f1b573befd92053ac6f78fca4a87268"), value: common.Hex2Bytes("")}, // step 24
{op: 1, key: common.Hex2Bytes("fd"), value: common.Hex2Bytes("")}, // step 25 {op: 1, key: common.Hex2Bytes("fd"), value: common.Hex2Bytes("")}, // step 25
} }
runRandTest(rt) if err := runRandTest(rt); err != nil {
t.Fatal(err)
}
} }
// randTest performs random trie operations. // randTest performs random trie operations.
@ -389,33 +392,45 @@ const (
) )
func (randTest) Generate(r *rand.Rand, size int) reflect.Value { func (randTest) Generate(r *rand.Rand, size int) reflect.Value {
var finishedFn = func() bool {
size--
return size > 0
}
return reflect.ValueOf(generateSteps(finishedFn, r))
}
func generateSteps(finished func() bool, r io.Reader) randTest {
var allKeys [][]byte var allKeys [][]byte
var one = []byte{0}
genKey := func() []byte { genKey := func() []byte {
if len(allKeys) < 2 || r.Intn(100) < 10 { r.Read(one)
if len(allKeys) < 2 || one[0]%100 > 90 {
// new key // new key
key := make([]byte, r.Intn(50)) size := one[0] % 50
key := make([]byte, size)
r.Read(key) r.Read(key)
allKeys = append(allKeys, key) allKeys = append(allKeys, key)
return key return key
} }
// use existing key // use existing key
return allKeys[r.Intn(len(allKeys))] idx := int(one[0]) % len(allKeys)
return allKeys[idx]
} }
var steps randTest var steps randTest
for i := 0; i < size; i++ { for !finished() {
step := randTestStep{op: r.Intn(opMax)} r.Read(one)
step := randTestStep{op: int(one[0]) % opMax}
switch step.op { switch step.op {
case opUpdate: case opUpdate:
step.key = genKey() step.key = genKey()
step.value = make([]byte, 8) step.value = make([]byte, 8)
binary.BigEndian.PutUint64(step.value, uint64(i)) binary.BigEndian.PutUint64(step.value, uint64(len(steps)))
case opGet, opDelete, opProve: case opGet, opDelete, opProve:
step.key = genKey() step.key = genKey()
} }
steps = append(steps, step) steps = append(steps, step)
} }
return reflect.ValueOf(steps) return steps
} }
func verifyAccessList(old *Trie, new *Trie, set *trienode.NodeSet) error { func verifyAccessList(old *Trie, new *Trie, set *trienode.NodeSet) error {
@ -460,7 +475,12 @@ func verifyAccessList(old *Trie, new *Trie, set *trienode.NodeSet) error {
return nil return nil
} }
func runRandTest(rt randTest) bool { // runRandTestBool coerces error to boolean, for use in quick.Check
func runRandTestBool(rt randTest) bool {
return runRandTest(rt) == nil
}
func runRandTest(rt randTest) error {
var scheme = rawdb.HashScheme var scheme = rawdb.HashScheme
if rand.Intn(2) == 0 { if rand.Intn(2) == 0 {
scheme = rawdb.PathScheme scheme = rawdb.PathScheme
@ -513,12 +533,12 @@ func runRandTest(rt randTest) bool {
newtr, err := New(TrieID(root), triedb) newtr, err := New(TrieID(root), triedb)
if err != nil { if err != nil {
rt[i].err = err rt[i].err = err
return false return err
} }
if nodes != nil { if nodes != nil {
if err := verifyAccessList(origTrie, newtr, nodes); err != nil { if err := verifyAccessList(origTrie, newtr, nodes); err != nil {
rt[i].err = err rt[i].err = err
return false return err
} }
} }
tr = newtr tr = newtr
@ -587,14 +607,14 @@ func runRandTest(rt randTest) bool {
} }
// Abort the test on error. // Abort the test on error.
if rt[i].err != nil { if rt[i].err != nil {
return false return rt[i].err
} }
} }
return true return nil
} }
func TestRandom(t *testing.T) { func TestRandom(t *testing.T) {
if err := quick.Check(runRandTest, nil); err != nil { if err := quick.Check(runRandTestBool, nil); err != nil {
if cerr, ok := err.(*quick.CheckError); ok { if cerr, ok := err.(*quick.CheckError); ok {
t.Fatalf("random test iteration %d failed: %s", cerr.Count, spew.Sdump(cerr.In)) t.Fatalf("random test iteration %d failed: %s", cerr.Count, spew.Sdump(cerr.In))
} }
@ -1185,3 +1205,17 @@ func TestDecodeNode(t *testing.T) {
decodeNode(hash, elems) decodeNode(hash, elems)
} }
} }
func FuzzTrie(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
var steps = 500
var input = bytes.NewReader(data)
var finishedFn = func() bool {
steps--
return steps < 0 || input.Len() == 0
}
if err := runRandTest(generateSteps(finishedFn, input)); err != nil {
t.Fatal(err)
}
})
}