-
Notifications
You must be signed in to change notification settings - Fork 129
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cmd): implement offline pruning of state trie (#1564)
- Loading branch information
Showing
18 changed files
with
557 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/dgraph-io/badger/v2" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func iterateDB(db *badger.DB, cb func(*badger.Item)) { | ||
txn := db.NewTransaction(false) | ||
itr := txn.NewIterator(badger.DefaultIteratorOptions) | ||
|
||
for itr.Rewind(); itr.Valid(); itr.Next() { | ||
cb(itr.Item()) | ||
} | ||
} | ||
func runPruneCmd(t *testing.T, configFile, prunedDBPath string) { | ||
ctx, err := newTestContext( | ||
"Test state trie offline pruning --prune-state", | ||
[]string{"config", "pruned-db-path", "bloom-size", "retain-blocks"}, | ||
[]interface{}{configFile, prunedDBPath, "256", "5"}, | ||
) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
command := pruningCommand | ||
err = command.Run(ctx) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
} | ||
|
||
func TestPruneState(t *testing.T) { | ||
var ( | ||
inputDBPath = "../../tests/data/db" | ||
configFile = "../../tests/data/db/config.toml" | ||
prunedDBPath = fmt.Sprintf("%s/%s", t.TempDir(), "pruned") | ||
storagePrefix = "storage" | ||
) | ||
|
||
inputDB, err := badger.Open(badger.DefaultOptions(inputDBPath).WithReadOnly(true)) | ||
require.NoError(t, err) | ||
|
||
nonStorageKeys := make(map[string]interface{}) | ||
var numStorageKeys int | ||
|
||
getKeysInputDB := func(item *badger.Item) { | ||
key := string(item.Key()) | ||
if strings.HasPrefix(key, storagePrefix) { | ||
numStorageKeys++ | ||
return | ||
} | ||
nonStorageKeys[key] = nil | ||
} | ||
iterateDB(inputDB, getKeysInputDB) | ||
|
||
err = inputDB.Close() | ||
require.NoError(t, err) | ||
|
||
t.Log("Total keys in input DB", numStorageKeys+len(nonStorageKeys), "storage keys", numStorageKeys) | ||
|
||
t.Log("pruned DB path", prunedDBPath) | ||
|
||
runPruneCmd(t, configFile, prunedDBPath) | ||
|
||
prunedDB, err := badger.Open(badger.DefaultOptions(prunedDBPath)) | ||
require.NoError(t, err) | ||
|
||
nonStorageKeysPruned := make(map[string]interface{}) | ||
var numStorageKeysPruned int | ||
|
||
getKeysPrunedDB := func(item *badger.Item) { | ||
key := string(item.Key()) | ||
if strings.HasPrefix(key, storagePrefix) { | ||
numStorageKeysPruned++ | ||
return | ||
} | ||
nonStorageKeysPruned[key] = nil | ||
} | ||
iterateDB(prunedDB, getKeysPrunedDB) | ||
|
||
t.Log("Total keys in pruned DB", len(nonStorageKeysPruned)+numStorageKeysPruned, "storage keys", numStorageKeysPruned) | ||
require.Equal(t, len(nonStorageKeysPruned), len(nonStorageKeys)) | ||
|
||
// Check all non storage keys are present. | ||
for k := range nonStorageKeys { | ||
_, ok := nonStorageKeysPruned[k] | ||
require.True(t, ok) | ||
} | ||
|
||
err = prunedDB.Close() | ||
require.NoError(t, err) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package state | ||
|
||
import ( | ||
"encoding/binary" | ||
"errors" | ||
|
||
"github.com/ChainSafe/gossamer/lib/common" | ||
log "github.com/ChainSafe/log15" | ||
bloomfilter "github.com/holiman/bloomfilter/v2" | ||
) | ||
|
||
// ErrKeySize is returned when key size does not fit | ||
var ErrKeySize = errors.New("cannot have nil keystore") | ||
|
||
type bloomStateHasher []byte | ||
|
||
func (f bloomStateHasher) Write(p []byte) (n int, err error) { panic("not implemented") } | ||
func (f bloomStateHasher) Sum(b []byte) []byte { panic("not implemented") } | ||
func (f bloomStateHasher) Reset() { panic("not implemented") } | ||
func (f bloomStateHasher) BlockSize() int { panic("not implemented") } | ||
func (f bloomStateHasher) Size() int { return 8 } | ||
func (f bloomStateHasher) Sum64() uint64 { return binary.BigEndian.Uint64(f) } | ||
|
||
// bloomState is a wrapper for bloom filter. | ||
// The keys of all generated entries will be recorded here so that in the pruning | ||
// stage the entries belong to the specific version can be avoided for deletion. | ||
type bloomState struct { | ||
bloom *bloomfilter.Filter | ||
} | ||
|
||
// newBloomState creates a brand new state bloom for state generation | ||
// The bloom filter will be created by the passing bloom filter size. the parameters | ||
// are picked so that the false-positive rate for mainnet is low enough. | ||
func newBloomState(size uint64) (*bloomState, error) { | ||
bloom, err := bloomfilter.New(size*1024*1024*8, 4) | ||
if err != nil { | ||
return nil, err | ||
} | ||
log.Info("initialised state bloom", "size", float64(bloom.M()/8)) | ||
return &bloomState{bloom: bloom}, nil | ||
} | ||
|
||
// put writes key to bloom filter | ||
func (sb *bloomState) put(key []byte) error { | ||
if len(key) != common.HashLength { | ||
return ErrKeySize | ||
} | ||
|
||
sb.bloom.Add(bloomStateHasher(key)) | ||
return nil | ||
} | ||
|
||
// contain is the wrapper of the underlying contains function which | ||
// reports whether the key is contained. | ||
// - If it says yes, the key may be contained | ||
// - If it says no, the key is definitely not contained. | ||
func (sb *bloomState) contain(key []byte) bool { | ||
return sb.bloom.Contains(bloomStateHasher(key)) | ||
} |
Oops, something went wrong.