From 244ff6e96b3cee9d698bdb1f51f2a337da93b66a Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 4 Dec 2019 23:41:35 +0100 Subject: [PATCH] core/state/snapshot: use local bloom filters --- core/state/snapshot/difflayer.go | 79 +++++++++++++++++++------------- core/state/snapshot/disklayer.go | 3 ++ core/state/snapshot/snapshot.go | 48 +++++++++++-------- core/state/statedb.go | 6 ++- go.mod | 2 + go.sum | 4 ++ 6 files changed, 90 insertions(+), 52 deletions(-) diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index f98b45b6cf..92fe352f2f 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -27,7 +27,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" - "github.com/steakknife/bloomfilter" + "github.com/holiman/bloomfilter" ) var ( @@ -110,7 +110,8 @@ type diffLayer struct { storageList map[common.Hash][]common.Hash // List of storage slots for iterated retrievals, one per account. Any existing lists are sorted if non-nil storageData map[common.Hash]map[common.Hash][]byte // Keyed storage slots for direct retrival. one per account (nil means deleted) - diffed *bloomfilter.Filter // Bloom filter tracking all the diffed items up to the disk layer + diffed *bloomfilter.Filter // Bloom filter tracking local diffed items + cumulative *bloomfilter.Filter // cumulumative bloom filter -- by default set to nil lock sync.RWMutex } @@ -169,34 +170,24 @@ func newDiffLayer(parent snapshot, root common.Hash, destructs map[common.Hash]s accountData: accounts, storageData: storage, } - switch parent := parent.(type) { - case *diskLayer: - dl.rebloom(parent, true) - case *diffLayer: - dl.rebloom(parent.origin, true) - default: - panic("unknown parent type") - } + dl.initBloom() return dl } -// rebloom discards the layer's current bloom and rebuilds it from scratch based -// on the parent's and the local diffs. -func (dl *diffLayer) rebloom(origin *diskLayer, creation bool) { +// initBloom builds the layer's bloom and calculates memory consumption +func (dl *diffLayer) initBloom() { dl.lock.Lock() defer dl.lock.Unlock() defer func(start time.Time) { snapshotBloomIndexTimer.Update(time.Since(start)) }(time.Now()) - - // Inject the new origin that triggered the rebloom - dl.origin = origin - // Retrieve the parent bloom or create a fresh empty one + // We need to use the same keys as the parent bloom if we want to be able + // to make union later if parent, ok := dl.parent.(*diffLayer); ok { parent.lock.RLock() - dl.diffed, _ = parent.diffed.Copy() + dl.diffed, _ = parent.diffed.NewCompatible() parent.lock.RUnlock() } else { dl.diffed, _ = bloomfilter.New(uint64(bloomSize), uint64(bloomFuncs)) @@ -218,9 +209,7 @@ func (dl *diffLayer) rebloom(origin *diskLayer, creation bool) { nHashes++ } dl.memory = dataSize + nHashes*uint64(common.HashLength) - if creation { - snapshotDirtyAccountWriteMeter.Mark(int64(dataSize)) - } + snapshotDirtyAccountWriteMeter.Mark(int64(dataSize)) dataSize, nHashes = uint64(0), uint64(0) for accountHash, slots := range dl.storageData { @@ -235,16 +224,39 @@ func (dl *diffLayer) rebloom(origin *diskLayer, creation bool) { } } dl.memory += dataSize + nHashes*uint64(common.HashLength) - if creation { - snapshotDirtyStorageWriteMeter.Mark(int64(dataSize)) + snapshotDirtyStorageWriteMeter.Mark(int64(dataSize)) +} + +// Prepare prepares the difflayer for execution, and creates the cumulative +// bloom +func (dl *diffLayer) Prepare(origin *diskLayer) { + dl.lock.Lock() + dl.cumulative, _ = dl.diffed.Copy() + layer := dl + for { + if parent, ok := layer.parent.(*diffLayer); ok { + parent.lock.RLock() + dl.cumulative.UnionInPlace(parent.diffed) + parent.lock.RUnlock() + layer = parent + } else { + break + } } + dl.origin = origin + dl.lock.Unlock() // Calculate the current false positive rate and update the error rate meter. - // This is a bit cheating because subsequent layers will overwrite it, but it - // should be fine, we're only interested in ballpark figures. - k := float64(dl.diffed.K()) - n := float64(dl.diffed.N()) - m := float64(dl.diffed.M()) + k := float64(dl.cumulative.K()) + n := float64(dl.cumulative.N()) + m := float64(dl.cumulative.M()) snapshotBloomErrorGauge.Update(math.Pow(1.0-math.Exp((-k)*(n+0.5)/(m-1)), k)) + +} + +func (dl *diffLayer) Release() { + dl.lock.Lock() + dl.cumulative = nil + dl.lock.Unlock() } // Root returns the root hash for which this snapshot was made. @@ -286,9 +298,9 @@ func (dl *diffLayer) AccountRLP(hash common.Hash) ([]byte, error) { // Check the bloom filter first whether there's even a point in reaching into // all the maps in all the layers below dl.lock.RLock() - hit := dl.diffed.Contains(accountBloomHasher(hash)) + hit := dl.cumulative.Contains(accountBloomHasher(hash)) if !hit { - hit = dl.diffed.Contains(destructBloomHasher(hash)) + hit = dl.cumulative.Contains(destructBloomHasher(hash)) } dl.lock.RUnlock() @@ -346,9 +358,9 @@ func (dl *diffLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro // Check the bloom filter first whether there's even a point in reaching into // all the maps in all the layers below dl.lock.RLock() - hit := dl.diffed.Contains(storageBloomHasher{accountHash, storageHash}) + hit := dl.cumulative.Contains(storageBloomHasher{accountHash, storageHash}) if !hit { - hit = dl.diffed.Contains(destructBloomHasher(accountHash)) + hit = dl.cumulative.Contains(destructBloomHasher(accountHash)) } dl.lock.RUnlock() @@ -457,6 +469,7 @@ func (dl *diffLayer) flatten() snapshot { parent.storageData[accountHash] = comboData } // Return the combo parent + parent.diffed.UnionInPlace(dl.diffed) return &diffLayer{ parent: parent.parent, origin: parent.origin, @@ -465,7 +478,7 @@ func (dl *diffLayer) flatten() snapshot { accountData: parent.accountData, storageData: parent.storageData, storageList: make(map[common.Hash][]common.Hash), - diffed: dl.diffed, + diffed: parent.diffed, memory: parent.memory + dl.memory, } } diff --git a/core/state/snapshot/disklayer.go b/core/state/snapshot/disklayer.go index e8f2bc853f..fec9513bc7 100644 --- a/core/state/snapshot/disklayer.go +++ b/core/state/snapshot/disklayer.go @@ -164,3 +164,6 @@ func (dl *diskLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro func (dl *diskLayer) Update(blockHash common.Hash, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer { return newDiffLayer(dl, blockHash, destructs, accounts, storage) } + +func (dl *diskLayer) Prepare(*diskLayer) {} +func (dl *diskLayer) Release() {} diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index 2de7ea0978..5456f219e4 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -107,6 +107,7 @@ type Snapshot interface { // Storage directly retrieves the storage data associated with a particular hash, // within a particular account. Storage(accountHash, storageHash common.Hash) ([]byte, error) + Release() } // snapshot is the internal version of the snapshot data layer that supports some @@ -138,6 +139,8 @@ type snapshot interface { // AccountIterator creates an account iterator over an arbitrary layer. AccountIterator(seek common.Hash) AccountIterator + + Prepare(*diskLayer) } // SnapshotTree is an Ethereum state snapshot tree. It consists of one persistent @@ -150,11 +153,12 @@ type snapshot interface { // storage data to avoid expensive multi-level trie lookups; and to allow sorted, // cheap iteration of the account/storage tries for sync aid. type Tree struct { - diskdb ethdb.KeyValueStore // Persistent database to store the snapshot - triedb *trie.Database // In-memory cache to access the trie through - cache int // Megabytes permitted to use for read caches - layers map[common.Hash]snapshot // Collection of all known layers - lock sync.RWMutex + diskdb ethdb.KeyValueStore // Persistent database to store the snapshot + triedb *trie.Database // In-memory cache to access the trie through + cache int // Megabytes permitted to use for read caches + layers map[common.Hash]snapshot // Collection of all known layers + lock sync.RWMutex + diskLayer *diskLayer // The underlying disklayer } // New attempts to load an already existing snapshot from a persistent key-value @@ -186,6 +190,9 @@ func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root comm for head != nil { snap.layers[head.Root()] = head head = head.Parent() + // TODO(@holiman), check if we need to add this back: + //case *diskLayer: + // snap.diskLayer = self } return snap } @@ -211,6 +218,16 @@ func (t *Tree) waitBuild() { } } +func (t *Tree) PrepareSnapshot(blockRoot common.Hash) Snapshot { + t.lock.RLock() + defer t.lock.RUnlock() + if snap := t.layers[blockRoot]; snap != nil { + snap.Prepare(t.diskLayer) + return snap + } + return nil +} + // Snapshot retrieves a snapshot belonging to the given block root, or nil if no // snapshot is maintained for that block. func (t *Tree) Snapshot(blockRoot common.Hash) Snapshot { @@ -278,6 +295,7 @@ func (t *Tree) Cap(root common.Hash, layers int) error { // Replace the entire snapshot tree with the flat base t.layers = map[common.Hash]snapshot{base.root: base} + t.diskLayer = base return nil case 1: @@ -291,6 +309,7 @@ func (t *Tree) Cap(root common.Hash, layers int) error { bottom = diff.flatten().(*diffLayer) if bottom.memory >= aggregatorMemoryLimit { base = diffToDisk(bottom) + t.diskLayer = base } diff.lock.RUnlock() @@ -305,6 +324,9 @@ func (t *Tree) Cap(root common.Hash, layers int) error { default: // Many layers requested to be retained, cap normally persisted = t.cap(diff, layers) + if persisted != nil { + t.diskLayer = persisted + } } // Remove any layer that is stale or links into a stale layer children := make(map[common.Hash][]common.Hash) @@ -328,18 +350,6 @@ func (t *Tree) Cap(root common.Hash, layers int) error { } } // If the disk layer was modified, regenerate all the cummulative blooms - if persisted != nil { - var rebloom func(root common.Hash) - rebloom = func(root common.Hash) { - if diff, ok := t.layers[root].(*diffLayer); ok { - diff.rebloom(persisted, false) - } - for _, child := range children[root] { - rebloom(child) - } - } - rebloom(persisted.root) - } return nil } @@ -591,8 +601,10 @@ func (t *Tree) Rebuild(root common.Hash) { // Start generating a new snapshot from scratch on a backgroung thread. The // generator will run a wiper first if there's not one running right now. log.Info("Rebuilding state snapshot") + diskLayer := generateSnapshot(t.diskdb, t.triedb, t.cache, root, wiper) + t.diskLayer = diskLayer t.layers = map[common.Hash]snapshot{ - root: generateSnapshot(t.diskdb, t.triedb, t.cache, root, wiper), + root: diskLayer, } } diff --git a/core/state/statedb.go b/core/state/statedb.go index 4aaedfe95e..0b58bd1aca 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -133,7 +133,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) journal: newJournal(), } if sdb.snaps != nil { - if sdb.snap = sdb.snaps.Snapshot(root); sdb.snap != nil { + if sdb.snap = sdb.snaps.PrepareSnapshot(root); sdb.snap != nil { sdb.snapDestructs = make(map[common.Hash]struct{}) sdb.snapAccounts = make(map[common.Hash][]byte) sdb.snapStorage = make(map[common.Hash]map[common.Hash][]byte) @@ -862,7 +862,11 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { if err := s.snaps.Update(root, parent, s.snapDestructs, s.snapAccounts, s.snapStorage); err != nil { log.Warn("Failed to update snapshot tree", "from", parent, "to", root, "err", err) } + if err := s.snaps.Cap(root, 127); err != nil { // Persistent layer is 128th, the last available trie + log.Warn("Failed to cap snapshot tree", "root", root, "layers", 127, "err", err) + } } + s.snap.Release() s.snap, s.snapDestructs, s.snapAccounts, s.snapStorage = nil, nil, nil, nil } return root, err diff --git a/go.mod b/go.mod index 6f2511d29d..33ea4f9f9f 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,7 @@ require ( github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad + github.com/holiman/bloomfilter v0.0.0-20191204204232-5fc8e9365fc2 github.com/huin/goupnp v0.0.0-20161224104101-679507af18f3 github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883 github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 @@ -57,6 +58,7 @@ require ( github.com/stretchr/testify v1.4.0 github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef + github.com/umbracle/fastrlp v0.0.0-20191017143648-86584926e68c github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 golang.org/x/net v0.0.0-20200301022130-244492dfa37a // indirect diff --git a/go.sum b/go.sum index 2a823e15cf..5f8402f842 100644 --- a/go.sum +++ b/go.sum @@ -99,6 +99,8 @@ github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 h1:E0whKx github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad h1:eMxs9EL0PvIGS9TTtxg4R+JxuPGav82J8rA+GFnY7po= github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/holiman/bloomfilter v0.0.0-20191204204232-5fc8e9365fc2 h1:W++/MG1xoR0V8YM2H47YC/G/DnZKODb0TmxY/GBcEac= +github.com/holiman/bloomfilter v0.0.0-20191204204232-5fc8e9365fc2/go.mod h1:+E92jn6hSglI3YfM9VLe+w2tHKtSL9l83iBiZS7/4Lw= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huin/goupnp v0.0.0-20161224104101-679507af18f3 h1:DqD8eigqlUm0+znmx7zhL0xvTW3+e1jCekJMfBUADWI= @@ -189,6 +191,8 @@ github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d h1:gZZadD8H+fF+ github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= +github.com/umbracle/fastrlp v0.0.0-20191017143648-86584926e68c h1:KCbyOp0afQzDTGARP+qj9HpZVk79WQaQtAO+4kMy/E4= +github.com/umbracle/fastrlp v0.0.0-20191017143648-86584926e68c/go.mod h1:TBFAIaLMusGh1X7Pd3trJTjLsOmNErpPV9/J7BY9lSA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 h1:1cngl9mPEoITZG8s8cVcUy5CeIBYhEESkOB7m6Gmkrk= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees=