core/state/snapshot: use local bloom filters

This commit is contained in:
Martin Holst Swende 2019-12-04 23:41:35 +01:00
parent 4373220585
commit 244ff6e96b
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
6 changed files with 90 additions and 52 deletions

View file

@ -27,7 +27,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/steakknife/bloomfilter" "github.com/holiman/bloomfilter"
) )
var ( 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 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) 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 lock sync.RWMutex
} }
@ -169,34 +170,24 @@ func newDiffLayer(parent snapshot, root common.Hash, destructs map[common.Hash]s
accountData: accounts, accountData: accounts,
storageData: storage, storageData: storage,
} }
switch parent := parent.(type) { dl.initBloom()
case *diskLayer:
dl.rebloom(parent, true)
case *diffLayer:
dl.rebloom(parent.origin, true)
default:
panic("unknown parent type")
}
return dl return dl
} }
// rebloom discards the layer's current bloom and rebuilds it from scratch based // initBloom builds the layer's bloom and calculates memory consumption
// on the parent's and the local diffs. func (dl *diffLayer) initBloom() {
func (dl *diffLayer) rebloom(origin *diskLayer, creation bool) {
dl.lock.Lock() dl.lock.Lock()
defer dl.lock.Unlock() defer dl.lock.Unlock()
defer func(start time.Time) { defer func(start time.Time) {
snapshotBloomIndexTimer.Update(time.Since(start)) snapshotBloomIndexTimer.Update(time.Since(start))
}(time.Now()) }(time.Now())
// Inject the new origin that triggered the rebloom
dl.origin = origin
// Retrieve the parent bloom or create a fresh empty one // 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 { if parent, ok := dl.parent.(*diffLayer); ok {
parent.lock.RLock() parent.lock.RLock()
dl.diffed, _ = parent.diffed.Copy() dl.diffed, _ = parent.diffed.NewCompatible()
parent.lock.RUnlock() parent.lock.RUnlock()
} else { } else {
dl.diffed, _ = bloomfilter.New(uint64(bloomSize), uint64(bloomFuncs)) dl.diffed, _ = bloomfilter.New(uint64(bloomSize), uint64(bloomFuncs))
@ -218,9 +209,7 @@ func (dl *diffLayer) rebloom(origin *diskLayer, creation bool) {
nHashes++ nHashes++
} }
dl.memory = dataSize + nHashes*uint64(common.HashLength) dl.memory = dataSize + nHashes*uint64(common.HashLength)
if creation { snapshotDirtyAccountWriteMeter.Mark(int64(dataSize))
snapshotDirtyAccountWriteMeter.Mark(int64(dataSize))
}
dataSize, nHashes = uint64(0), uint64(0) dataSize, nHashes = uint64(0), uint64(0)
for accountHash, slots := range dl.storageData { 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) 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. // 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 k := float64(dl.cumulative.K())
// should be fine, we're only interested in ballpark figures. n := float64(dl.cumulative.N())
k := float64(dl.diffed.K()) m := float64(dl.cumulative.M())
n := float64(dl.diffed.N())
m := float64(dl.diffed.M())
snapshotBloomErrorGauge.Update(math.Pow(1.0-math.Exp((-k)*(n+0.5)/(m-1)), k)) 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. // 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 // Check the bloom filter first whether there's even a point in reaching into
// all the maps in all the layers below // all the maps in all the layers below
dl.lock.RLock() dl.lock.RLock()
hit := dl.diffed.Contains(accountBloomHasher(hash)) hit := dl.cumulative.Contains(accountBloomHasher(hash))
if !hit { if !hit {
hit = dl.diffed.Contains(destructBloomHasher(hash)) hit = dl.cumulative.Contains(destructBloomHasher(hash))
} }
dl.lock.RUnlock() 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 // Check the bloom filter first whether there's even a point in reaching into
// all the maps in all the layers below // all the maps in all the layers below
dl.lock.RLock() dl.lock.RLock()
hit := dl.diffed.Contains(storageBloomHasher{accountHash, storageHash}) hit := dl.cumulative.Contains(storageBloomHasher{accountHash, storageHash})
if !hit { if !hit {
hit = dl.diffed.Contains(destructBloomHasher(accountHash)) hit = dl.cumulative.Contains(destructBloomHasher(accountHash))
} }
dl.lock.RUnlock() dl.lock.RUnlock()
@ -457,6 +469,7 @@ func (dl *diffLayer) flatten() snapshot {
parent.storageData[accountHash] = comboData parent.storageData[accountHash] = comboData
} }
// Return the combo parent // Return the combo parent
parent.diffed.UnionInPlace(dl.diffed)
return &diffLayer{ return &diffLayer{
parent: parent.parent, parent: parent.parent,
origin: parent.origin, origin: parent.origin,
@ -465,7 +478,7 @@ func (dl *diffLayer) flatten() snapshot {
accountData: parent.accountData, accountData: parent.accountData,
storageData: parent.storageData, storageData: parent.storageData,
storageList: make(map[common.Hash][]common.Hash), storageList: make(map[common.Hash][]common.Hash),
diffed: dl.diffed, diffed: parent.diffed,
memory: parent.memory + dl.memory, memory: parent.memory + dl.memory,
} }
} }

View file

@ -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 { 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) return newDiffLayer(dl, blockHash, destructs, accounts, storage)
} }
func (dl *diskLayer) Prepare(*diskLayer) {}
func (dl *diskLayer) Release() {}

View file

@ -107,6 +107,7 @@ type Snapshot interface {
// Storage directly retrieves the storage data associated with a particular hash, // Storage directly retrieves the storage data associated with a particular hash,
// within a particular account. // within a particular account.
Storage(accountHash, storageHash common.Hash) ([]byte, error) Storage(accountHash, storageHash common.Hash) ([]byte, error)
Release()
} }
// snapshot is the internal version of the snapshot data layer that supports some // 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 creates an account iterator over an arbitrary layer.
AccountIterator(seek common.Hash) AccountIterator AccountIterator(seek common.Hash) AccountIterator
Prepare(*diskLayer)
} }
// SnapshotTree is an Ethereum state snapshot tree. It consists of one persistent // 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, // storage data to avoid expensive multi-level trie lookups; and to allow sorted,
// cheap iteration of the account/storage tries for sync aid. // cheap iteration of the account/storage tries for sync aid.
type Tree struct { type Tree struct {
diskdb ethdb.KeyValueStore // Persistent database to store the snapshot diskdb ethdb.KeyValueStore // Persistent database to store the snapshot
triedb *trie.Database // In-memory cache to access the trie through triedb *trie.Database // In-memory cache to access the trie through
cache int // Megabytes permitted to use for read caches cache int // Megabytes permitted to use for read caches
layers map[common.Hash]snapshot // Collection of all known layers layers map[common.Hash]snapshot // Collection of all known layers
lock sync.RWMutex lock sync.RWMutex
diskLayer *diskLayer // The underlying disklayer
} }
// New attempts to load an already existing snapshot from a persistent key-value // 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 { for head != nil {
snap.layers[head.Root()] = head snap.layers[head.Root()] = head
head = head.Parent() head = head.Parent()
// TODO(@holiman), check if we need to add this back:
//case *diskLayer:
// snap.diskLayer = self
} }
return snap 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 retrieves a snapshot belonging to the given block root, or nil if no
// snapshot is maintained for that block. // snapshot is maintained for that block.
func (t *Tree) Snapshot(blockRoot common.Hash) Snapshot { 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 // Replace the entire snapshot tree with the flat base
t.layers = map[common.Hash]snapshot{base.root: base} t.layers = map[common.Hash]snapshot{base.root: base}
t.diskLayer = base
return nil return nil
case 1: case 1:
@ -291,6 +309,7 @@ func (t *Tree) Cap(root common.Hash, layers int) error {
bottom = diff.flatten().(*diffLayer) bottom = diff.flatten().(*diffLayer)
if bottom.memory >= aggregatorMemoryLimit { if bottom.memory >= aggregatorMemoryLimit {
base = diffToDisk(bottom) base = diffToDisk(bottom)
t.diskLayer = base
} }
diff.lock.RUnlock() diff.lock.RUnlock()
@ -305,6 +324,9 @@ func (t *Tree) Cap(root common.Hash, layers int) error {
default: default:
// Many layers requested to be retained, cap normally // Many layers requested to be retained, cap normally
persisted = t.cap(diff, layers) persisted = t.cap(diff, layers)
if persisted != nil {
t.diskLayer = persisted
}
} }
// Remove any layer that is stale or links into a stale layer // Remove any layer that is stale or links into a stale layer
children := make(map[common.Hash][]common.Hash) 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 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 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 // 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. // generator will run a wiper first if there's not one running right now.
log.Info("Rebuilding state snapshot") log.Info("Rebuilding state snapshot")
diskLayer := generateSnapshot(t.diskdb, t.triedb, t.cache, root, wiper)
t.diskLayer = diskLayer
t.layers = map[common.Hash]snapshot{ t.layers = map[common.Hash]snapshot{
root: generateSnapshot(t.diskdb, t.triedb, t.cache, root, wiper), root: diskLayer,
} }
} }

View file

@ -133,7 +133,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
journal: newJournal(), journal: newJournal(),
} }
if sdb.snaps != nil { 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.snapDestructs = make(map[common.Hash]struct{})
sdb.snapAccounts = make(map[common.Hash][]byte) sdb.snapAccounts = make(map[common.Hash][]byte)
sdb.snapStorage = make(map[common.Hash]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 { 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) 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 s.snap, s.snapDestructs, s.snapAccounts, s.snapStorage = nil, nil, nil, nil
} }
return root, err return root, err

2
go.mod
View file

@ -33,6 +33,7 @@ require (
github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989
github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277
github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad 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/huin/goupnp v0.0.0-20161224104101-679507af18f3
github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883 github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883
github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 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/stretchr/testify v1.4.0
github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d
github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef 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 github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4
golang.org/x/net v0.0.0-20200301022130-244492dfa37a // indirect golang.org/x/net v0.0.0-20200301022130-244492dfa37a // indirect

4
go.sum
View file

@ -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/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 h1:eMxs9EL0PvIGS9TTtxg4R+JxuPGav82J8rA+GFnY7po=
github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 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 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 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= 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/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 h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4=
github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= 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/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 h1:1cngl9mPEoITZG8s8cVcUy5CeIBYhEESkOB7m6Gmkrk=
github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees=