forked from cerc-io/plugeth
all: activate pbss as experimental feature (#26274)
* all: activate pbss * core/rawdb: fix compilation error * cma, core, eth, les, trie: address comments * cmd, core, eth, trie: polish code * core, cmd, eth: address comments * cmd, core, eth, les, light, tests: address comment * cmd/utils: shorten log message * trie/triedb/pathdb: limit node buffer size to 1gb * cmd/utils: fix opening non-existing db * cmd/utils: rename flag name * cmd, core: group chain history flags and fix tests * core, eth, trie: fix memory leak in snapshot generation * cmd, eth, internal: deprecate flags * all: enable state tests for pathdb, fixes * cmd, core: polish code * trie/triedb/pathdb: limit the node buffer size to 256mb --------- Co-authored-by: Martin Holst Swende <martin@swende.se> Co-authored-by: Péter Szilágyi <peterke@gmail.com>
This commit is contained in:
co-authored by
Martin Holst Swende
Péter Szilágyi
parent
5e89ff4d6b
commit
503f1f7ada
+90
-17
@@ -21,6 +21,7 @@ import (
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/trie/triedb/hashdb"
|
||||
"github.com/ethereum/go-ethereum/trie/triedb/pathdb"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
@@ -29,14 +30,21 @@ import (
|
||||
|
||||
// Config defines all necessary options for database.
|
||||
type Config struct {
|
||||
Cache int // Memory allowance (MB) to use for caching trie nodes in memory
|
||||
Preimages bool // Flag whether the preimage of trie key is recorded
|
||||
PathDB *pathdb.Config // Configs for experimental path-based scheme, not used yet.
|
||||
Preimages bool // Flag whether the preimage of node key is recorded
|
||||
HashDB *hashdb.Config // Configs for hash-based scheme
|
||||
PathDB *pathdb.Config // Configs for experimental path-based scheme
|
||||
|
||||
// Testing hooks
|
||||
OnCommit func(states *triestate.Set) // Hook invoked when commit is performed
|
||||
}
|
||||
|
||||
// HashDefaults represents a config for using hash-based scheme with
|
||||
// default settings.
|
||||
var HashDefaults = &Config{
|
||||
Preimages: false,
|
||||
HashDB: hashdb.Defaults,
|
||||
}
|
||||
|
||||
// backend defines the methods needed to access/update trie nodes in different
|
||||
// state scheme.
|
||||
type backend interface {
|
||||
@@ -91,22 +99,30 @@ func prepare(diskdb ethdb.Database, config *Config) *Database {
|
||||
}
|
||||
}
|
||||
|
||||
// NewDatabase initializes the trie database with default settings, namely
|
||||
// NewDatabase initializes the trie database with default settings, note
|
||||
// the legacy hash-based scheme is used by default.
|
||||
func NewDatabase(diskdb ethdb.Database) *Database {
|
||||
return NewDatabaseWithConfig(diskdb, nil)
|
||||
}
|
||||
|
||||
// NewDatabaseWithConfig initializes the trie database with provided configs.
|
||||
// The path-based scheme is not activated yet, always initialized with legacy
|
||||
// hash-based scheme by default.
|
||||
func NewDatabaseWithConfig(diskdb ethdb.Database, config *Config) *Database {
|
||||
var cleans int
|
||||
if config != nil && config.Cache != 0 {
|
||||
cleans = config.Cache * 1024 * 1024
|
||||
func NewDatabase(diskdb ethdb.Database, config *Config) *Database {
|
||||
// Sanitize the config and use the default one if it's not specified.
|
||||
if config == nil {
|
||||
config = HashDefaults
|
||||
}
|
||||
var preimages *preimageStore
|
||||
if config.Preimages {
|
||||
preimages = newPreimageStore(diskdb)
|
||||
}
|
||||
db := &Database{
|
||||
config: config,
|
||||
diskdb: diskdb,
|
||||
preimages: preimages,
|
||||
}
|
||||
if config.HashDB != nil && config.PathDB != nil {
|
||||
log.Crit("Both 'hash' and 'path' mode are configured")
|
||||
}
|
||||
if config.PathDB != nil {
|
||||
db.backend = pathdb.New(diskdb, config.PathDB)
|
||||
} else {
|
||||
db.backend = hashdb.New(diskdb, config.HashDB, mptResolver{})
|
||||
}
|
||||
db := prepare(diskdb, config)
|
||||
db.backend = hashdb.New(diskdb, cleans, mptResolver{})
|
||||
return db
|
||||
}
|
||||
|
||||
@@ -240,3 +256,60 @@ func (db *Database) Node(hash common.Hash) ([]byte, error) {
|
||||
}
|
||||
return hdb.Node(hash)
|
||||
}
|
||||
|
||||
// Recover rollbacks the database to a specified historical point. The state is
|
||||
// supported as the rollback destination only if it's canonical state and the
|
||||
// corresponding trie histories are existent. It's only supported by path-based
|
||||
// database and will return an error for others.
|
||||
func (db *Database) Recover(target common.Hash) error {
|
||||
pdb, ok := db.backend.(*pathdb.Database)
|
||||
if !ok {
|
||||
return errors.New("not supported")
|
||||
}
|
||||
return pdb.Recover(target, &trieLoader{db: db})
|
||||
}
|
||||
|
||||
// Recoverable returns the indicator if the specified state is enabled to be
|
||||
// recovered. It's only supported by path-based database and will return an
|
||||
// error for others.
|
||||
func (db *Database) Recoverable(root common.Hash) (bool, error) {
|
||||
pdb, ok := db.backend.(*pathdb.Database)
|
||||
if !ok {
|
||||
return false, errors.New("not supported")
|
||||
}
|
||||
return pdb.Recoverable(root), nil
|
||||
}
|
||||
|
||||
// Reset wipes all available journal from the persistent database and discard
|
||||
// all caches and diff layers. Using the given root to create a new disk layer.
|
||||
// It's only supported by path-based database and will return an error for others.
|
||||
func (db *Database) Reset(root common.Hash) error {
|
||||
pdb, ok := db.backend.(*pathdb.Database)
|
||||
if !ok {
|
||||
return errors.New("not supported")
|
||||
}
|
||||
return pdb.Reset(root)
|
||||
}
|
||||
|
||||
// Journal commits an entire diff hierarchy to disk into a single journal entry.
|
||||
// This is meant to be used during shutdown to persist the snapshot without
|
||||
// flattening everything down (bad for reorgs). It's only supported by path-based
|
||||
// database and will return an error for others.
|
||||
func (db *Database) Journal(root common.Hash) error {
|
||||
pdb, ok := db.backend.(*pathdb.Database)
|
||||
if !ok {
|
||||
return errors.New("not supported")
|
||||
}
|
||||
return pdb.Journal(root)
|
||||
}
|
||||
|
||||
// SetBufferSize sets the node buffer size to the provided value(in bytes).
|
||||
// It's only supported by path-based database and will return an error for
|
||||
// others.
|
||||
func (db *Database) SetBufferSize(size int) error {
|
||||
pdb, ok := db.backend.(*pathdb.Database)
|
||||
if !ok {
|
||||
return errors.New("not supported")
|
||||
}
|
||||
return pdb.SetBufferSize(size)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
func newTestDatabase(diskdb ethdb.Database, scheme string) *Database {
|
||||
db := prepare(diskdb, nil)
|
||||
if scheme == rawdb.HashScheme {
|
||||
db.backend = hashdb.New(diskdb, 0, mptResolver{})
|
||||
db.backend = hashdb.New(diskdb, &hashdb.Config{}, mptResolver{})
|
||||
} else {
|
||||
db.backend = pathdb.New(diskdb, &pathdb.Config{}) // disable clean/dirty cache
|
||||
}
|
||||
|
||||
+10
-103
@@ -18,7 +18,6 @@ package trie
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
@@ -27,13 +26,11 @@ import (
|
||||
"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/ethdb/memorydb"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
)
|
||||
|
||||
func TestEmptyIterator(t *testing.T) {
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
iter := trie.MustNodeIterator(nil)
|
||||
|
||||
seen := make(map[string]struct{})
|
||||
@@ -46,7 +43,7 @@ func TestEmptyIterator(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIterator(t *testing.T) {
|
||||
db := NewDatabase(rawdb.NewMemoryDatabase())
|
||||
db := NewDatabase(rawdb.NewMemoryDatabase(), nil)
|
||||
trie := NewEmpty(db)
|
||||
vals := []struct{ k, v string }{
|
||||
{"do", "verb"},
|
||||
@@ -89,7 +86,7 @@ func (k *kv) less(other *kv) bool {
|
||||
}
|
||||
|
||||
func TestIteratorLargeData(t *testing.T) {
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
vals := make(map[string]*kv)
|
||||
|
||||
for i := byte(0); i < 255; i++ {
|
||||
@@ -208,7 +205,7 @@ var testdata2 = []kvs{
|
||||
}
|
||||
|
||||
func TestIteratorSeek(t *testing.T) {
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
for _, val := range testdata1 {
|
||||
trie.MustUpdate([]byte(val.k), []byte(val.v))
|
||||
}
|
||||
@@ -249,7 +246,7 @@ func checkIteratorOrder(want []kvs, it *Iterator) error {
|
||||
}
|
||||
|
||||
func TestDifferenceIterator(t *testing.T) {
|
||||
dba := NewDatabase(rawdb.NewMemoryDatabase())
|
||||
dba := NewDatabase(rawdb.NewMemoryDatabase(), nil)
|
||||
triea := NewEmpty(dba)
|
||||
for _, val := range testdata1 {
|
||||
triea.MustUpdate([]byte(val.k), []byte(val.v))
|
||||
@@ -258,7 +255,7 @@ func TestDifferenceIterator(t *testing.T) {
|
||||
dba.Update(rootA, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodesA), nil)
|
||||
triea, _ = New(TrieID(rootA), dba)
|
||||
|
||||
dbb := NewDatabase(rawdb.NewMemoryDatabase())
|
||||
dbb := NewDatabase(rawdb.NewMemoryDatabase(), nil)
|
||||
trieb := NewEmpty(dbb)
|
||||
for _, val := range testdata2 {
|
||||
trieb.MustUpdate([]byte(val.k), []byte(val.v))
|
||||
@@ -291,7 +288,7 @@ func TestDifferenceIterator(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnionIterator(t *testing.T) {
|
||||
dba := NewDatabase(rawdb.NewMemoryDatabase())
|
||||
dba := NewDatabase(rawdb.NewMemoryDatabase(), nil)
|
||||
triea := NewEmpty(dba)
|
||||
for _, val := range testdata1 {
|
||||
triea.MustUpdate([]byte(val.k), []byte(val.v))
|
||||
@@ -300,7 +297,7 @@ func TestUnionIterator(t *testing.T) {
|
||||
dba.Update(rootA, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodesA), nil)
|
||||
triea, _ = New(TrieID(rootA), dba)
|
||||
|
||||
dbb := NewDatabase(rawdb.NewMemoryDatabase())
|
||||
dbb := NewDatabase(rawdb.NewMemoryDatabase(), nil)
|
||||
trieb := NewEmpty(dbb)
|
||||
for _, val := range testdata2 {
|
||||
trieb.MustUpdate([]byte(val.k), []byte(val.v))
|
||||
@@ -344,7 +341,7 @@ func TestUnionIterator(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIteratorNoDups(t *testing.T) {
|
||||
tr := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
tr := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
for _, val := range testdata1 {
|
||||
tr.MustUpdate([]byte(val.k), []byte(val.v))
|
||||
}
|
||||
@@ -537,96 +534,6 @@ func TestIteratorNodeBlob(t *testing.T) {
|
||||
testIteratorNodeBlob(t, rawdb.PathScheme)
|
||||
}
|
||||
|
||||
type loggingDb struct {
|
||||
getCount uint64
|
||||
backend ethdb.KeyValueStore
|
||||
}
|
||||
|
||||
func (l *loggingDb) Has(key []byte) (bool, error) {
|
||||
return l.backend.Has(key)
|
||||
}
|
||||
|
||||
func (l *loggingDb) Get(key []byte) ([]byte, error) {
|
||||
l.getCount++
|
||||
return l.backend.Get(key)
|
||||
}
|
||||
|
||||
func (l *loggingDb) Put(key []byte, value []byte) error {
|
||||
return l.backend.Put(key, value)
|
||||
}
|
||||
|
||||
func (l *loggingDb) Delete(key []byte) error {
|
||||
return l.backend.Delete(key)
|
||||
}
|
||||
|
||||
func (l *loggingDb) NewBatch() ethdb.Batch {
|
||||
return l.backend.NewBatch()
|
||||
}
|
||||
|
||||
func (l *loggingDb) NewBatchWithSize(size int) ethdb.Batch {
|
||||
return l.backend.NewBatchWithSize(size)
|
||||
}
|
||||
|
||||
func (l *loggingDb) NewIterator(prefix []byte, start []byte) ethdb.Iterator {
|
||||
return l.backend.NewIterator(prefix, start)
|
||||
}
|
||||
|
||||
func (l *loggingDb) NewSnapshot() (ethdb.Snapshot, error) {
|
||||
return l.backend.NewSnapshot()
|
||||
}
|
||||
|
||||
func (l *loggingDb) Stat(property string) (string, error) {
|
||||
return l.backend.Stat(property)
|
||||
}
|
||||
|
||||
func (l *loggingDb) Compact(start []byte, limit []byte) error {
|
||||
return l.backend.Compact(start, limit)
|
||||
}
|
||||
|
||||
func (l *loggingDb) Close() error {
|
||||
return l.backend.Close()
|
||||
}
|
||||
|
||||
// makeLargeTestTrie create a sample test trie
|
||||
func makeLargeTestTrie() (*Database, *StateTrie, *loggingDb) {
|
||||
// Create an empty trie
|
||||
logDb := &loggingDb{0, memorydb.New()}
|
||||
triedb := NewDatabase(rawdb.NewDatabase(logDb))
|
||||
trie, _ := NewStateTrie(TrieID(types.EmptyRootHash), triedb)
|
||||
|
||||
// Fill it with some arbitrary data
|
||||
for i := 0; i < 10000; i++ {
|
||||
key := make([]byte, 32)
|
||||
val := make([]byte, 32)
|
||||
binary.BigEndian.PutUint64(key, uint64(i))
|
||||
binary.BigEndian.PutUint64(val, uint64(i))
|
||||
key = crypto.Keccak256(key)
|
||||
val = crypto.Keccak256(val)
|
||||
trie.MustUpdate(key, val)
|
||||
}
|
||||
root, nodes, _ := trie.Commit(false)
|
||||
triedb.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
|
||||
triedb.Commit(root, false)
|
||||
|
||||
// Return the generated trie
|
||||
trie, _ = NewStateTrie(TrieID(root), triedb)
|
||||
return triedb, trie, logDb
|
||||
}
|
||||
|
||||
// Tests that the node iterator indeed walks over the entire database contents.
|
||||
func TestNodeIteratorLargeTrie(t *testing.T) {
|
||||
// Create some arbitrary test trie to iterate
|
||||
db, trie, logDb := makeLargeTestTrie()
|
||||
db.Cap(0) // flush everything
|
||||
// Do a seek operation
|
||||
trie.NodeIterator(common.FromHex("0x77667766776677766778855885885885"))
|
||||
// master: 24 get operations
|
||||
// this pr: 6 get operations
|
||||
if have, want := logDb.getCount, uint64(6); have != want {
|
||||
t.Fatalf("Too many lookups during seek, have %d want %d", have, want)
|
||||
}
|
||||
}
|
||||
|
||||
func testIteratorNodeBlob(t *testing.T, scheme string) {
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
@@ -700,7 +607,7 @@ func isTrieNode(scheme string, key, val []byte) (bool, []byte, common.Hash) {
|
||||
}
|
||||
hash = common.BytesToHash(key)
|
||||
} else {
|
||||
ok, remain := rawdb.IsAccountTrieNode(key)
|
||||
ok, remain := rawdb.ResolveAccountTrieNodeKey(key)
|
||||
if !ok {
|
||||
return false, nil, common.Hash{}
|
||||
}
|
||||
|
||||
+10
-10
@@ -94,7 +94,7 @@ func TestProof(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestOneElementProof(t *testing.T) {
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
updateString(trie, "k", "v")
|
||||
for i, prover := range makeProvers(trie) {
|
||||
proof := prover([]byte("k"))
|
||||
@@ -145,7 +145,7 @@ func TestBadProof(t *testing.T) {
|
||||
// Tests that missing keys can also be proven. The test explicitly uses a single
|
||||
// entry trie and checks for missing keys both before and after the single entry.
|
||||
func TestMissingKeyProof(t *testing.T) {
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
updateString(trie, "k", "v")
|
||||
|
||||
for i, key := range []string{"a", "j", "l", "z"} {
|
||||
@@ -395,7 +395,7 @@ func TestOneElementRangeProof(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test the mini trie with only a single element.
|
||||
tinyTrie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
tinyTrie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
entry := &kv{randBytes(32), randBytes(20), false}
|
||||
tinyTrie.MustUpdate(entry.k, entry.v)
|
||||
|
||||
@@ -467,7 +467,7 @@ func TestAllElementsProof(t *testing.T) {
|
||||
// TestSingleSideRangeProof tests the range starts from zero.
|
||||
func TestSingleSideRangeProof(t *testing.T) {
|
||||
for i := 0; i < 64; i++ {
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
var entries []*kv
|
||||
for i := 0; i < 4096; i++ {
|
||||
value := &kv{randBytes(32), randBytes(20), false}
|
||||
@@ -502,7 +502,7 @@ func TestSingleSideRangeProof(t *testing.T) {
|
||||
// TestReverseSingleSideRangeProof tests the range ends with 0xffff...fff.
|
||||
func TestReverseSingleSideRangeProof(t *testing.T) {
|
||||
for i := 0; i < 64; i++ {
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
var entries []*kv
|
||||
for i := 0; i < 4096; i++ {
|
||||
value := &kv{randBytes(32), randBytes(20), false}
|
||||
@@ -609,7 +609,7 @@ func TestBadRangeProof(t *testing.T) {
|
||||
// TestGappedRangeProof focuses on the small trie with embedded nodes.
|
||||
// If the gapped node is embedded in the trie, it should be detected too.
|
||||
func TestGappedRangeProof(t *testing.T) {
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
var entries []*kv // Sorted entries
|
||||
for i := byte(0); i < 10; i++ {
|
||||
value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false}
|
||||
@@ -683,7 +683,7 @@ func TestSameSideProofs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHasRightElement(t *testing.T) {
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
var entries []*kv
|
||||
for i := 0; i < 4096; i++ {
|
||||
value := &kv{randBytes(32), randBytes(20), false}
|
||||
@@ -1036,7 +1036,7 @@ func benchmarkVerifyRangeNoProof(b *testing.B, size int) {
|
||||
}
|
||||
|
||||
func randomTrie(n int) (*Trie, map[string]*kv) {
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
vals := make(map[string]*kv)
|
||||
for i := byte(0); i < 100; i++ {
|
||||
value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false}
|
||||
@@ -1055,7 +1055,7 @@ func randomTrie(n int) (*Trie, map[string]*kv) {
|
||||
}
|
||||
|
||||
func nonRandomTrie(n int) (*Trie, map[string]*kv) {
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
vals := make(map[string]*kv)
|
||||
max := uint64(0xffffffffffffffff)
|
||||
for i := uint64(0); i < uint64(n); i++ {
|
||||
@@ -1080,7 +1080,7 @@ func TestRangeProofKeysWithSharedPrefix(t *testing.T) {
|
||||
common.Hex2Bytes("02"),
|
||||
common.Hex2Bytes("03"),
|
||||
}
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
for i, key := range keys {
|
||||
trie.MustUpdate(key, vals[i])
|
||||
}
|
||||
|
||||
@@ -31,14 +31,14 @@ import (
|
||||
)
|
||||
|
||||
func newEmptySecure() *StateTrie {
|
||||
trie, _ := NewStateTrie(TrieID(types.EmptyRootHash), NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie, _ := NewStateTrie(TrieID(types.EmptyRootHash), NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
return trie
|
||||
}
|
||||
|
||||
// makeTestStateTrie creates a large enough secure trie for testing.
|
||||
func makeTestStateTrie() (*Database, *StateTrie, map[string][]byte) {
|
||||
// Create an empty trie
|
||||
triedb := NewDatabase(rawdb.NewMemoryDatabase())
|
||||
triedb := NewDatabase(rawdb.NewMemoryDatabase(), nil)
|
||||
trie, _ := NewStateTrie(TrieID(types.EmptyRootHash), triedb)
|
||||
|
||||
// Fill it with some arbitrary data
|
||||
|
||||
@@ -188,7 +188,7 @@ func TestStackTrieInsertAndHash(t *testing.T) {
|
||||
|
||||
func TestSizeBug(t *testing.T) {
|
||||
st := NewStackTrie(nil)
|
||||
nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
|
||||
leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563")
|
||||
value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3")
|
||||
@@ -203,7 +203,7 @@ func TestSizeBug(t *testing.T) {
|
||||
|
||||
func TestEmptyBug(t *testing.T) {
|
||||
st := NewStackTrie(nil)
|
||||
nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
|
||||
//leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563")
|
||||
//value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3")
|
||||
@@ -229,7 +229,7 @@ func TestEmptyBug(t *testing.T) {
|
||||
|
||||
func TestValLength56(t *testing.T) {
|
||||
st := NewStackTrie(nil)
|
||||
nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
|
||||
//leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563")
|
||||
//value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3")
|
||||
@@ -254,7 +254,7 @@ func TestValLength56(t *testing.T) {
|
||||
// which causes a lot of node-within-node. This case was found via fuzzing.
|
||||
func TestUpdateSmallNodes(t *testing.T) {
|
||||
st := NewStackTrie(nil)
|
||||
nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
kvs := []struct {
|
||||
K string
|
||||
V string
|
||||
@@ -282,7 +282,7 @@ func TestUpdateSmallNodes(t *testing.T) {
|
||||
func TestUpdateVariableKeys(t *testing.T) {
|
||||
t.SkipNow()
|
||||
st := NewStackTrie(nil)
|
||||
nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
kvs := []struct {
|
||||
K string
|
||||
V string
|
||||
@@ -351,7 +351,7 @@ func TestStacktrieNotModifyValues(t *testing.T) {
|
||||
func TestStacktrieSerialization(t *testing.T) {
|
||||
var (
|
||||
st = NewStackTrie(nil)
|
||||
nt = NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
nt = NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
keyB = big.NewInt(1)
|
||||
keyDelta = big.NewInt(1)
|
||||
vals [][]byte
|
||||
|
||||
+2
-2
@@ -109,8 +109,8 @@ type trieElement struct {
|
||||
|
||||
// Tests that an empty trie is not scheduled for syncing.
|
||||
func TestEmptySync(t *testing.T) {
|
||||
dbA := NewDatabase(rawdb.NewMemoryDatabase())
|
||||
dbB := NewDatabase(rawdb.NewMemoryDatabase())
|
||||
dbA := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)
|
||||
dbB := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)
|
||||
dbC := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme)
|
||||
dbD := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme)
|
||||
|
||||
|
||||
+5
-5
@@ -61,7 +61,7 @@ func TestTrieTracer(t *testing.T) {
|
||||
// Tests if the trie diffs are tracked correctly. Tracer should capture
|
||||
// all non-leaf dirty nodes, no matter the node is embedded or not.
|
||||
func testTrieTracer(t *testing.T, vals []struct{ k, v string }) {
|
||||
db := NewDatabase(rawdb.NewMemoryDatabase())
|
||||
db := NewDatabase(rawdb.NewMemoryDatabase(), nil)
|
||||
trie := NewEmpty(db)
|
||||
|
||||
// Determine all new nodes are tracked
|
||||
@@ -104,7 +104,7 @@ func TestTrieTracerNoop(t *testing.T) {
|
||||
}
|
||||
|
||||
func testTrieTracerNoop(t *testing.T, vals []struct{ k, v string }) {
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
for _, val := range vals {
|
||||
trie.MustUpdate([]byte(val.k), []byte(val.v))
|
||||
}
|
||||
@@ -128,7 +128,7 @@ func TestAccessList(t *testing.T) {
|
||||
|
||||
func testAccessList(t *testing.T, vals []struct{ k, v string }) {
|
||||
var (
|
||||
db = NewDatabase(rawdb.NewMemoryDatabase())
|
||||
db = NewDatabase(rawdb.NewMemoryDatabase(), nil)
|
||||
trie = NewEmpty(db)
|
||||
orig = trie.Copy()
|
||||
)
|
||||
@@ -211,7 +211,7 @@ func testAccessList(t *testing.T, vals []struct{ k, v string }) {
|
||||
// Tests origin values won't be tracked in Iterator or Prover
|
||||
func TestAccessListLeak(t *testing.T) {
|
||||
var (
|
||||
db = NewDatabase(rawdb.NewMemoryDatabase())
|
||||
db = NewDatabase(rawdb.NewMemoryDatabase(), nil)
|
||||
trie = NewEmpty(db)
|
||||
)
|
||||
// Create trie from scratch
|
||||
@@ -262,7 +262,7 @@ func TestAccessListLeak(t *testing.T) {
|
||||
// in its parent due to the smaller size of the original tree node.
|
||||
func TestTinyTree(t *testing.T) {
|
||||
var (
|
||||
db = NewDatabase(rawdb.NewMemoryDatabase())
|
||||
db = NewDatabase(rawdb.NewMemoryDatabase(), nil)
|
||||
trie = NewEmpty(db)
|
||||
)
|
||||
for _, val := range tiny {
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/trie/triestate"
|
||||
)
|
||||
|
||||
// Reader wraps the Node method of a backing trie store.
|
||||
@@ -83,3 +84,18 @@ func (r *trieReader) node(path []byte, hash common.Hash) ([]byte, error) {
|
||||
}
|
||||
return blob, nil
|
||||
}
|
||||
|
||||
// trieLoader implements triestate.TrieLoader for constructing tries.
|
||||
type trieLoader struct {
|
||||
db *Database
|
||||
}
|
||||
|
||||
// OpenTrie opens the main account trie.
|
||||
func (l *trieLoader) OpenTrie(root common.Hash) (triestate.Trie, error) {
|
||||
return New(TrieID(root), l.db)
|
||||
}
|
||||
|
||||
// OpenStorageTrie opens the storage trie of an account.
|
||||
func (l *trieLoader) OpenStorageTrie(stateRoot common.Hash, addrHash, root common.Hash) (triestate.Trie, error) {
|
||||
return New(StorageTrieID(stateRoot, addrHash, root), l.db)
|
||||
}
|
||||
|
||||
+37
-26
@@ -45,7 +45,7 @@ func init() {
|
||||
}
|
||||
|
||||
func TestEmptyTrie(t *testing.T) {
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
res := trie.Hash()
|
||||
exp := types.EmptyRootHash
|
||||
if res != exp {
|
||||
@@ -54,7 +54,7 @@ func TestEmptyTrie(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNull(t *testing.T) {
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
key := make([]byte, 32)
|
||||
value := []byte("test")
|
||||
trie.MustUpdate(key, value)
|
||||
@@ -64,8 +64,13 @@ func TestNull(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMissingRoot(t *testing.T) {
|
||||
testMissingRoot(t, rawdb.HashScheme)
|
||||
testMissingRoot(t, rawdb.PathScheme)
|
||||
}
|
||||
|
||||
func testMissingRoot(t *testing.T, scheme string) {
|
||||
root := common.HexToHash("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33")
|
||||
trie, err := New(TrieID(root), NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie, err := New(TrieID(root), newTestDatabase(rawdb.NewMemoryDatabase(), scheme))
|
||||
if trie != nil {
|
||||
t.Error("New returned non-nil trie for invalid root")
|
||||
}
|
||||
@@ -161,7 +166,7 @@ func testMissingNode(t *testing.T, memonly bool, scheme string) {
|
||||
}
|
||||
|
||||
func TestInsert(t *testing.T) {
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
|
||||
updateString(trie, "doe", "reindeer")
|
||||
updateString(trie, "dog", "puppy")
|
||||
@@ -173,7 +178,7 @@ func TestInsert(t *testing.T) {
|
||||
t.Errorf("case 1: exp %x got %x", exp, root)
|
||||
}
|
||||
|
||||
trie = NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie = NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
updateString(trie, "A", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
||||
|
||||
exp = common.HexToHash("d23786fb4a010da3ce639d66d5e904a11dbc02746d1ce25029e53290cabf28ab")
|
||||
@@ -184,7 +189,7 @@ func TestInsert(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
db := NewDatabase(rawdb.NewMemoryDatabase())
|
||||
db := NewDatabase(rawdb.NewMemoryDatabase(), nil)
|
||||
trie := NewEmpty(db)
|
||||
updateString(trie, "doe", "reindeer")
|
||||
updateString(trie, "dog", "puppy")
|
||||
@@ -209,7 +214,7 @@ func TestGet(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
vals := []struct{ k, v string }{
|
||||
{"do", "verb"},
|
||||
{"ether", "wookiedoo"},
|
||||
@@ -236,7 +241,7 @@ func TestDelete(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEmptyValues(t *testing.T) {
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
|
||||
vals := []struct{ k, v string }{
|
||||
{"do", "verb"},
|
||||
@@ -260,7 +265,7 @@ func TestEmptyValues(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReplication(t *testing.T) {
|
||||
db := NewDatabase(rawdb.NewMemoryDatabase())
|
||||
db := NewDatabase(rawdb.NewMemoryDatabase(), nil)
|
||||
trie := NewEmpty(db)
|
||||
vals := []struct{ k, v string }{
|
||||
{"do", "verb"},
|
||||
@@ -321,7 +326,7 @@ func TestReplication(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLargeValue(t *testing.T) {
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
trie.MustUpdate([]byte("key1"), []byte{99, 99, 99, 99})
|
||||
trie.MustUpdate([]byte("key2"), bytes.Repeat([]byte{1}, 32))
|
||||
trie.Hash()
|
||||
@@ -604,7 +609,7 @@ func BenchmarkUpdateLE(b *testing.B) { benchUpdate(b, binary.LittleEndian) }
|
||||
const benchElemCount = 20000
|
||||
|
||||
func benchGet(b *testing.B) {
|
||||
triedb := NewDatabase(rawdb.NewMemoryDatabase())
|
||||
triedb := NewDatabase(rawdb.NewMemoryDatabase(), nil)
|
||||
trie := NewEmpty(triedb)
|
||||
k := make([]byte, 32)
|
||||
for i := 0; i < benchElemCount; i++ {
|
||||
@@ -621,7 +626,7 @@ func benchGet(b *testing.B) {
|
||||
}
|
||||
|
||||
func benchUpdate(b *testing.B, e binary.ByteOrder) *Trie {
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
k := make([]byte, 32)
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -651,7 +656,7 @@ func BenchmarkHash(b *testing.B) {
|
||||
// entries, then adding N more.
|
||||
addresses, accounts := makeAccounts(2 * b.N)
|
||||
// Insert the accounts into the trie and hash it
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
i := 0
|
||||
for ; i < len(addresses)/2; i++ {
|
||||
trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i])
|
||||
@@ -682,7 +687,7 @@ func BenchmarkCommitAfterHash(b *testing.B) {
|
||||
func benchmarkCommitAfterHash(b *testing.B, collectLeaf bool) {
|
||||
// Make the random benchmark deterministic
|
||||
addresses, accounts := makeAccounts(b.N)
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
for i := 0; i < len(addresses); i++ {
|
||||
trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i])
|
||||
}
|
||||
@@ -696,7 +701,7 @@ func benchmarkCommitAfterHash(b *testing.B, collectLeaf bool) {
|
||||
func TestTinyTrie(t *testing.T) {
|
||||
// Create a realistic account trie to hash
|
||||
_, accounts := makeAccounts(5)
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
trie.MustUpdate(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001337"), accounts[3])
|
||||
if exp, root := common.HexToHash("8c6a85a4d9fda98feff88450299e574e5378e32391f75a055d470ac0653f1005"), trie.Hash(); exp != root {
|
||||
t.Errorf("1: got %x, exp %x", root, exp)
|
||||
@@ -709,7 +714,7 @@ func TestTinyTrie(t *testing.T) {
|
||||
if exp, root := common.HexToHash("0608c1d1dc3905fa22204c7a0e43644831c3b6d3def0f274be623a948197e64a"), trie.Hash(); exp != root {
|
||||
t.Errorf("3: got %x, exp %x", root, exp)
|
||||
}
|
||||
checktr := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
checktr := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
it := NewIterator(trie.MustNodeIterator(nil))
|
||||
for it.Next() {
|
||||
checktr.MustUpdate(it.Key, it.Value)
|
||||
@@ -722,7 +727,7 @@ func TestTinyTrie(t *testing.T) {
|
||||
func TestCommitAfterHash(t *testing.T) {
|
||||
// Create a realistic account trie to hash
|
||||
addresses, accounts := makeAccounts(1000)
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
for i := 0; i < len(addresses); i++ {
|
||||
trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i])
|
||||
}
|
||||
@@ -788,11 +793,17 @@ func (s *spongeDb) Stat(property string) (string, error) { panic("implement
|
||||
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 {
|
||||
valbrief := value
|
||||
var (
|
||||
keybrief = key
|
||||
valbrief = value
|
||||
)
|
||||
if len(keybrief) > 8 {
|
||||
keybrief = keybrief[:8]
|
||||
}
|
||||
if len(valbrief) > 8 {
|
||||
valbrief = valbrief[:8]
|
||||
}
|
||||
s.journal = append(s.journal, fmt.Sprintf("%v: PUT([%x...], [%d bytes] %x...)\n", s.id, key[:8], len(value), valbrief))
|
||||
s.journal = append(s.journal, fmt.Sprintf("%v: PUT([%x...], [%d bytes] %x...)\n", s.id, keybrief, len(value), valbrief))
|
||||
s.sponge.Write(key)
|
||||
s.sponge.Write(value)
|
||||
return nil
|
||||
@@ -830,7 +841,7 @@ func TestCommitSequence(t *testing.T) {
|
||||
addresses, accounts := makeAccounts(tc.count)
|
||||
// This spongeDb is used to check the sequence of disk-db-writes
|
||||
s := &spongeDb{sponge: sha3.NewLegacyKeccak256()}
|
||||
db := NewDatabase(rawdb.NewDatabase(s))
|
||||
db := NewDatabase(rawdb.NewDatabase(s), nil)
|
||||
trie := NewEmpty(db)
|
||||
// Fill the trie with elements
|
||||
for i := 0; i < tc.count; i++ {
|
||||
@@ -861,7 +872,7 @@ func TestCommitSequenceRandomBlobs(t *testing.T) {
|
||||
prng := rand.New(rand.NewSource(int64(i)))
|
||||
// This spongeDb is used to check the sequence of disk-db-writes
|
||||
s := &spongeDb{sponge: sha3.NewLegacyKeccak256()}
|
||||
db := NewDatabase(rawdb.NewDatabase(s))
|
||||
db := NewDatabase(rawdb.NewDatabase(s), nil)
|
||||
trie := NewEmpty(db)
|
||||
// Fill the trie with elements
|
||||
for i := 0; i < tc.count; i++ {
|
||||
@@ -893,7 +904,7 @@ func TestCommitSequenceStackTrie(t *testing.T) {
|
||||
prng := rand.New(rand.NewSource(int64(count)))
|
||||
// This spongeDb is used to check the sequence of disk-db-writes
|
||||
s := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "a"}
|
||||
db := NewDatabase(rawdb.NewDatabase(s))
|
||||
db := NewDatabase(rawdb.NewDatabase(s), nil)
|
||||
trie := NewEmpty(db)
|
||||
// Another sponge is used for the stacktrie commits
|
||||
stackTrieSponge := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "b"}
|
||||
@@ -952,7 +963,7 @@ func TestCommitSequenceStackTrie(t *testing.T) {
|
||||
// not fit into 32 bytes, rlp-encoded. However, it's still the correct thing to do.
|
||||
func TestCommitSequenceSmallRoot(t *testing.T) {
|
||||
s := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "a"}
|
||||
db := NewDatabase(rawdb.NewDatabase(s))
|
||||
db := NewDatabase(rawdb.NewDatabase(s), nil)
|
||||
trie := NewEmpty(db)
|
||||
// Another sponge is used for the stacktrie commits
|
||||
stackTrieSponge := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "b"}
|
||||
@@ -1029,7 +1040,7 @@ func BenchmarkHashFixedSize(b *testing.B) {
|
||||
|
||||
func benchmarkHashFixedSize(b *testing.B, addresses [][20]byte, accounts [][]byte) {
|
||||
b.ReportAllocs()
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
for i := 0; i < len(addresses); i++ {
|
||||
trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i])
|
||||
}
|
||||
@@ -1080,7 +1091,7 @@ func BenchmarkCommitAfterHashFixedSize(b *testing.B) {
|
||||
|
||||
func benchmarkCommitAfterHashFixedSize(b *testing.B, addresses [][20]byte, accounts [][]byte) {
|
||||
b.ReportAllocs()
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
|
||||
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
|
||||
for i := 0; i < len(addresses); i++ {
|
||||
trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i])
|
||||
}
|
||||
@@ -1132,7 +1143,7 @@ func BenchmarkDerefRootFixedSize(b *testing.B) {
|
||||
|
||||
func benchmarkDerefRootFixedSize(b *testing.B, addresses [][20]byte, accounts [][]byte) {
|
||||
b.ReportAllocs()
|
||||
triedb := NewDatabase(rawdb.NewMemoryDatabase())
|
||||
triedb := NewDatabase(rawdb.NewMemoryDatabase(), nil)
|
||||
trie := NewEmpty(triedb)
|
||||
for i := 0; i < len(addresses); i++ {
|
||||
trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i])
|
||||
|
||||
@@ -65,6 +65,20 @@ type ChildResolver interface {
|
||||
ForEach(node []byte, onChild func(common.Hash))
|
||||
}
|
||||
|
||||
// Config contains the settings for database.
|
||||
type Config struct {
|
||||
CleanCacheSize int // Maximum memory allowance (in bytes) for caching clean nodes
|
||||
}
|
||||
|
||||
// Defaults is the default setting for database if it's not specified.
|
||||
// Notably, clean cache is disabled explicitly,
|
||||
var Defaults = &Config{
|
||||
// Explicitly set clean cache size to 0 to avoid creating fastcache,
|
||||
// otherwise database must be closed when it's no longer needed to
|
||||
// prevent memory leak.
|
||||
CleanCacheSize: 0,
|
||||
}
|
||||
|
||||
// Database is an intermediate write layer between the trie data structures and
|
||||
// the disk database. The aim is to accumulate trie writes in-memory and only
|
||||
// periodically flush a couple tries to disk, garbage collecting the remainder.
|
||||
@@ -122,12 +136,13 @@ func (n *cachedNode) forChildren(resolver ChildResolver, onChild func(hash commo
|
||||
}
|
||||
|
||||
// New initializes the hash-based node database.
|
||||
func New(diskdb ethdb.Database, size int, resolver ChildResolver) *Database {
|
||||
// Initialize the clean cache if the specified cache allowance
|
||||
// is non-zero. Note, the size is in bytes.
|
||||
func New(diskdb ethdb.Database, config *Config, resolver ChildResolver) *Database {
|
||||
if config == nil {
|
||||
config = Defaults
|
||||
}
|
||||
var cleans *fastcache.Cache
|
||||
if size > 0 {
|
||||
cleans = fastcache.New(size)
|
||||
if config.CleanCacheSize > 0 {
|
||||
cleans = fastcache.New(config.CleanCacheSize)
|
||||
}
|
||||
return &Database{
|
||||
diskdb: diskdb,
|
||||
@@ -621,7 +636,13 @@ func (db *Database) Size() common.StorageSize {
|
||||
}
|
||||
|
||||
// Close closes the trie database and releases all held resources.
|
||||
func (db *Database) Close() error { return nil }
|
||||
func (db *Database) Close() error {
|
||||
if db.cleans != nil {
|
||||
db.cleans.Reset()
|
||||
db.cleans = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Scheme returns the node scheme used in the database.
|
||||
func (db *Database) Scheme() string {
|
||||
|
||||
@@ -33,8 +33,26 @@ import (
|
||||
"github.com/ethereum/go-ethereum/trie/triestate"
|
||||
)
|
||||
|
||||
// maxDiffLayers is the maximum diff layers allowed in the layer tree.
|
||||
const maxDiffLayers = 128
|
||||
const (
|
||||
// maxDiffLayers is the maximum diff layers allowed in the layer tree.
|
||||
maxDiffLayers = 128
|
||||
|
||||
// defaultCleanSize is the default memory allowance of clean cache.
|
||||
defaultCleanSize = 16 * 1024 * 1024
|
||||
|
||||
// maxBufferSize is the maximum memory allowance of node buffer.
|
||||
// Too large nodebuffer will cause the system to pause for a long
|
||||
// time when write happens. Also, the largest batch that pebble can
|
||||
// support is 4GB, node will panic if batch size exceeds this limit.
|
||||
maxBufferSize = 256 * 1024 * 1024
|
||||
|
||||
// DefaultBufferSize is the default memory allowance of node buffer
|
||||
// that aggregates the writes from above until it's flushed into the
|
||||
// disk. It's meant to be used once the initial sync is finished.
|
||||
// Do not increase the buffer size arbitrarily, otherwise the system
|
||||
// pause time will increase when the database writes happen.
|
||||
DefaultBufferSize = 64 * 1024 * 1024
|
||||
)
|
||||
|
||||
// layer is the interface implemented by all state layers which includes some
|
||||
// public methods and some additional methods for internal usage.
|
||||
@@ -68,30 +86,33 @@ type layer interface {
|
||||
|
||||
// Config contains the settings for database.
|
||||
type Config struct {
|
||||
StateLimit uint64 // Number of recent blocks to maintain state history for
|
||||
CleanSize int // Maximum memory allowance (in bytes) for caching clean nodes
|
||||
DirtySize int // Maximum memory allowance (in bytes) for caching dirty nodes
|
||||
ReadOnly bool // Flag whether the database is opened in read only mode.
|
||||
StateHistory uint64 // Number of recent blocks to maintain state history for
|
||||
CleanCacheSize int // Maximum memory allowance (in bytes) for caching clean nodes
|
||||
DirtyCacheSize int // Maximum memory allowance (in bytes) for caching dirty nodes
|
||||
ReadOnly bool // Flag whether the database is opened in read only mode.
|
||||
}
|
||||
|
||||
var (
|
||||
// defaultCleanSize is the default memory allowance of clean cache.
|
||||
defaultCleanSize = 16 * 1024 * 1024
|
||||
|
||||
// defaultBufferSize is the default memory allowance of node buffer
|
||||
// that aggregates the writes from above until it's flushed into the
|
||||
// disk. Do not increase the buffer size arbitrarily, otherwise the
|
||||
// system pause time will increase when the database writes happen.
|
||||
defaultBufferSize = 128 * 1024 * 1024
|
||||
)
|
||||
// sanitize checks the provided user configurations and changes anything that's
|
||||
// unreasonable or unworkable.
|
||||
func (c *Config) sanitize() *Config {
|
||||
conf := *c
|
||||
if conf.DirtyCacheSize > maxBufferSize {
|
||||
log.Warn("Sanitizing invalid node buffer size", "provided", common.StorageSize(conf.DirtyCacheSize), "updated", common.StorageSize(maxBufferSize))
|
||||
conf.DirtyCacheSize = maxBufferSize
|
||||
}
|
||||
return &conf
|
||||
}
|
||||
|
||||
// Defaults contains default settings for Ethereum mainnet.
|
||||
var Defaults = &Config{
|
||||
StateLimit: params.FullImmutabilityThreshold,
|
||||
CleanSize: defaultCleanSize,
|
||||
DirtySize: defaultBufferSize,
|
||||
StateHistory: params.FullImmutabilityThreshold,
|
||||
CleanCacheSize: defaultCleanSize,
|
||||
DirtyCacheSize: DefaultBufferSize,
|
||||
}
|
||||
|
||||
// ReadOnly is the config in order to open database in read only mode.
|
||||
var ReadOnly = &Config{ReadOnly: true}
|
||||
|
||||
// Database is a multiple-layered structure for maintaining in-memory trie nodes.
|
||||
// It consists of one persistent base layer backed by a key-value store, on top
|
||||
// of which arbitrarily many in-memory diff layers are stacked. The memory diffs
|
||||
@@ -123,9 +144,11 @@ func New(diskdb ethdb.Database, config *Config) *Database {
|
||||
if config == nil {
|
||||
config = Defaults
|
||||
}
|
||||
config = config.sanitize()
|
||||
|
||||
db := &Database{
|
||||
readOnly: config.ReadOnly,
|
||||
bufferSize: config.DirtySize,
|
||||
bufferSize: config.DirtyCacheSize,
|
||||
config: config,
|
||||
diskdb: diskdb,
|
||||
}
|
||||
@@ -140,7 +163,7 @@ func New(diskdb ethdb.Database, config *Config) *Database {
|
||||
// mechanism also ensures that at most one **non-readOnly** database
|
||||
// is opened at the same time to prevent accidental mutation.
|
||||
if ancient, err := diskdb.AncientDatadir(); err == nil && ancient != "" && !db.readOnly {
|
||||
freezer, err := rawdb.NewStateHistoryFreezer(ancient, false)
|
||||
freezer, err := rawdb.NewStateFreezer(ancient, false)
|
||||
if err != nil {
|
||||
log.Crit("Failed to open state history freezer", "err", err)
|
||||
}
|
||||
@@ -344,7 +367,14 @@ func (db *Database) Close() error {
|
||||
db.lock.Lock()
|
||||
defer db.lock.Unlock()
|
||||
|
||||
// Set the database to read-only mode to prevent all
|
||||
// following mutations.
|
||||
db.readOnly = true
|
||||
|
||||
// Release the memory held by clean cache.
|
||||
db.tree.bottom().resetCache()
|
||||
|
||||
// Close the attached state history freezer.
|
||||
if db.freezer == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -382,6 +412,10 @@ func (db *Database) SetBufferSize(size int) error {
|
||||
db.lock.Lock()
|
||||
defer db.lock.Unlock()
|
||||
|
||||
if size > maxBufferSize {
|
||||
log.Info("Capped node buffer size", "provided", common.StorageSize(size), "adjusted", common.StorageSize(maxBufferSize))
|
||||
size = maxBufferSize
|
||||
}
|
||||
db.bufferSize = size
|
||||
return db.tree.bottom().setBufferSize(db.bufferSize)
|
||||
}
|
||||
|
||||
@@ -46,7 +46,8 @@ func updateTrie(addrHash common.Hash, root common.Hash, dirties, cleans map[comm
|
||||
h.Update(key.Bytes(), val)
|
||||
}
|
||||
}
|
||||
return h.Commit(false)
|
||||
root, nodes, _ := h.Commit(false)
|
||||
return root, nodes
|
||||
}
|
||||
|
||||
func generateAccount(storageRoot common.Hash) types.StateAccount {
|
||||
@@ -98,7 +99,7 @@ type tester struct {
|
||||
func newTester(t *testing.T) *tester {
|
||||
var (
|
||||
disk, _ = rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false)
|
||||
db = New(disk, &Config{CleanSize: 256 * 1024, DirtySize: 256 * 1024})
|
||||
db = New(disk, &Config{CleanCacheSize: 256 * 1024, DirtyCacheSize: 256 * 1024})
|
||||
obj = &tester{
|
||||
db: db,
|
||||
preimages: make(map[common.Hash]common.Address),
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
func emptyLayer() *diskLayer {
|
||||
return &diskLayer{
|
||||
db: New(rawdb.NewMemoryDatabase(), nil),
|
||||
buffer: newNodeBuffer(defaultBufferSize, nil, 0),
|
||||
buffer: newNodeBuffer(DefaultBufferSize, nil, 0),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,8 +47,8 @@ func newDiskLayer(root common.Hash, id uint64, db *Database, cleans *fastcache.C
|
||||
// Initialize a clean cache if the memory allowance is not zero
|
||||
// or reuse the provided cache if it is not nil (inherited from
|
||||
// the original disk layer).
|
||||
if cleans == nil && db.config.CleanSize != 0 {
|
||||
cleans = fastcache.New(db.config.CleanSize)
|
||||
if cleans == nil && db.config.CleanCacheSize != 0 {
|
||||
cleans = fastcache.New(db.config.CleanCacheSize)
|
||||
}
|
||||
return &diskLayer{
|
||||
root: root,
|
||||
@@ -177,7 +177,7 @@ func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) {
|
||||
// corresponding states(journal), the stored state history will
|
||||
// be truncated in the next restart.
|
||||
if dl.db.freezer != nil {
|
||||
err := writeHistory(dl.db.diskdb, dl.db.freezer, bottom, dl.db.config.StateLimit)
|
||||
err := writeHistory(dl.db.diskdb, dl.db.freezer, bottom, dl.db.config.StateHistory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -276,6 +276,20 @@ func (dl *diskLayer) size() common.StorageSize {
|
||||
return common.StorageSize(dl.buffer.size)
|
||||
}
|
||||
|
||||
// resetCache releases the memory held by clean cache to prevent memory leak.
|
||||
func (dl *diskLayer) resetCache() {
|
||||
dl.lock.RLock()
|
||||
defer dl.lock.RUnlock()
|
||||
|
||||
// Stale disk layer loses the ownership of clean cache.
|
||||
if dl.stale {
|
||||
return
|
||||
}
|
||||
if dl.cleans != nil {
|
||||
dl.cleans.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
// hasher is used to compute the sha256 hash of the provided data.
|
||||
type hasher struct{ sha crypto.KeccakState }
|
||||
|
||||
|
||||
@@ -226,7 +226,7 @@ func TestTruncateTailHistories(t *testing.T) {
|
||||
|
||||
// openFreezer initializes the freezer instance for storing state histories.
|
||||
func openFreezer(datadir string, readOnly bool) (*rawdb.ResettableFreezer, error) {
|
||||
return rawdb.NewStateHistoryFreezer(datadir, readOnly)
|
||||
return rawdb.NewStateFreezer(datadir, readOnly)
|
||||
}
|
||||
|
||||
func compareSet[k comparable](a, b map[k][]byte) bool {
|
||||
|
||||
@@ -80,7 +80,7 @@ func (h *testHasher) Delete(key []byte) error {
|
||||
|
||||
// Commit computes the new hash of the states and returns the set with all
|
||||
// state changes.
|
||||
func (h *testHasher) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) {
|
||||
func (h *testHasher) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) {
|
||||
var (
|
||||
nodes = make(map[common.Hash][]byte)
|
||||
set = trienode.NewNodeSet(h.owner)
|
||||
@@ -108,7 +108,7 @@ func (h *testHasher) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) {
|
||||
if root == types.EmptyRootHash && h.root != types.EmptyRootHash {
|
||||
set.AddNode(nil, trienode.NewDeleted())
|
||||
}
|
||||
return root, set
|
||||
return root, set, nil
|
||||
}
|
||||
|
||||
// hash performs the hash computation upon the provided states.
|
||||
|
||||
+13
-4
@@ -43,7 +43,7 @@ type Trie interface {
|
||||
|
||||
// Commit the trie and returns a set of dirty nodes generated along with
|
||||
// the new root hash.
|
||||
Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet)
|
||||
Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error)
|
||||
}
|
||||
|
||||
// TrieLoader wraps functions to load tries.
|
||||
@@ -129,7 +129,10 @@ func Apply(prevRoot common.Hash, postRoot common.Hash, accounts map[common.Addre
|
||||
return nil, fmt.Errorf("failed to revert state, err: %w", err)
|
||||
}
|
||||
}
|
||||
root, result := tr.Commit(false)
|
||||
root, result, err := tr.Commit(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if root != prevRoot {
|
||||
return nil, fmt.Errorf("failed to revert state, want %#x, got %#x", prevRoot, root)
|
||||
}
|
||||
@@ -181,7 +184,10 @@ func updateAccount(ctx *context, loader TrieLoader, addr common.Address) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
root, result := st.Commit(false)
|
||||
root, result, err := st.Commit(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if root != prev.Root {
|
||||
return errors.New("failed to reset storage trie")
|
||||
}
|
||||
@@ -232,7 +238,10 @@ func deleteAccount(ctx *context, loader TrieLoader, addr common.Address) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
root, result := st.Commit(false)
|
||||
root, result, err := st.Commit(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if root != types.EmptyRootHash {
|
||||
return errors.New("failed to clear storage trie")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user