mirror of https://github.com/ethereum/go-ethereum
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.pull/28523/head
parent
24d46224c1
commit
2391fbc676
@ -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) |
||||
} |
||||
} |
||||
} |
@ -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") |
||||
} |
||||
} |
@ -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 |
||||
} |
Binary file not shown.
@ -1 +0,0 @@ |
||||
Ë€€€À€ÀÃÀÀÀÀ |
@ -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 |
||||
} |
@ -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) |
||||
}) |
||||
} |
@ -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 |
||||
} |
@ -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) |
||||
}) |
||||
} |
@ -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 |
||||
} |
@ -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) |
||||
}) |
||||
} |
@ -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") |
||||
} |
||||
} |
Loading…
Reference in new issue