From 3eb9399e7fd37ef165671fcd4c3a25a35903f9fb Mon Sep 17 00:00:00 2001 From: Arijit Das Date: Thu, 17 Jun 2021 21:08:16 +0530 Subject: [PATCH] feat(dot/state): implement online pruning of historical state tries (#1596) --- chain/dev/defaults.go | 5 + chain/gssmr/defaults.go | 5 + chain/kusama/defaults.go | 5 + chain/polkadot/defaults.go | 5 + cmd/gossamer/config.go | 15 + cmd/gossamer/config_test.go | 21 +- cmd/gossamer/export.go | 12 +- cmd/gossamer/export_test.go | 9 +- cmd/gossamer/flags.go | 16 +- cmd/gossamer/flags_test.go | 25 +- cmd/gossamer/main.go | 5 +- cmd/gossamer/prune_test.go | 2 +- cmd/gossamer/utils.go | 2 + cmd/gossamer/utils_test.go | 7 + dot/build_spec.go | 6 +- dot/config.go | 49 +-- dot/config/toml/config.go | 12 +- dot/core/digest_test.go | 7 +- dot/core/test_helpers.go | 6 +- dot/import.go | 6 +- dot/node.go | 15 +- dot/node_test.go | 6 +- dot/rpc/modules/chain_test.go | 7 +- dot/rpc/modules/state_test.go | 2 +- dot/rpc/modules/system_test.go | 2 +- dot/services.go | 8 +- dot/state/base.go | 27 +- dot/state/initialize.go | 10 +- dot/state/{prune.go => offline_pruner.go} | 19 +- dot/state/pruner/pruner.go | 375 ++++++++++++++++++++++ dot/state/service.go | 37 ++- dot/state/service_test.go | 103 +++++- dot/state/storage.go | 50 ++- dot/state/storage_notify_test.go | 6 +- dot/state/storage_test.go | 15 +- dot/state/test_helpers.go | 7 +- dot/sync/interface.go | 2 +- dot/sync/syncer.go | 2 +- dot/sync/test_helpers.go | 7 +- go.mod | 4 +- go.sum | 42 +-- lib/babe/babe.go | 5 +- lib/babe/babe_test.go | 7 +- lib/babe/state.go | 2 +- lib/babe/verify_test.go | 6 +- lib/common/db_keys.go | 2 + lib/runtime/storage/trie.go | 14 + lib/trie/database.go | 50 +++ lib/trie/node.go | 14 +- lib/trie/trie.go | 140 ++++---- lib/trie/trie_test.go | 69 ++++ tests/utils/gossamer_utils.go | 10 +- 52 files changed, 1030 insertions(+), 255 deletions(-) rename dot/state/{prune.go => offline_pruner.go} (89%) create mode 100644 dot/state/pruner/pruner.go diff --git a/chain/dev/defaults.go b/chain/dev/defaults.go index 1636fc0d1b..68a9bc4fc2 100644 --- a/chain/dev/defaults.go +++ b/chain/dev/defaults.go @@ -39,6 +39,11 @@ var ( // DefaultLvl is the default log level DefaultLvl = log.LvlInfo + // DefaultPruningMode is the default pruning mode + DefaultPruningMode = "archive" + // DefaultRetainBlocks is the default retained blocks + DefaultRetainBlocks = int64(512) + // InitConfig // DefaultGenesis is the default genesis configuration path diff --git a/chain/gssmr/defaults.go b/chain/gssmr/defaults.go index a1d16aade5..5d7ebfc7a0 100644 --- a/chain/gssmr/defaults.go +++ b/chain/gssmr/defaults.go @@ -39,6 +39,11 @@ var ( // DefaultLvl is the default log level DefaultLvl = log.LvlInfo + // DefaultPruningMode is the default pruning mode + DefaultPruningMode = "archive" + // DefaultRetainBlocks is the default retained blocks + DefaultRetainBlocks = int64(512) + // InitConfig // DefaultGenesis is the default genesis configuration path diff --git a/chain/kusama/defaults.go b/chain/kusama/defaults.go index 948568acb4..e4c2e2657a 100644 --- a/chain/kusama/defaults.go +++ b/chain/kusama/defaults.go @@ -39,6 +39,11 @@ var ( // DefaultLvl is the default log level DefaultLvl = log.LvlInfo + // DefaultPruningMode is the default pruning mode + DefaultPruningMode = "archive" + // DefaultRetainBlocks is the default retained blocks + DefaultRetainBlocks = int64(512) + // InitConfig // DefaultGenesis is the default genesis configuration path diff --git a/chain/polkadot/defaults.go b/chain/polkadot/defaults.go index 46a5dc7989..4af96caf0e 100644 --- a/chain/polkadot/defaults.go +++ b/chain/polkadot/defaults.go @@ -36,6 +36,11 @@ var ( // DefaultLvl is the default log level DefaultLvl = log.LvlInfo + // DefaultPruningMode is the default pruning mode + DefaultPruningMode = "archive" + // DefaultRetainBlocks is the default pruning mode + DefaultRetainBlocks = int64(512) + // InitConfig // DefaultGenesis is the default genesis configuration path diff --git a/cmd/gossamer/config.go b/cmd/gossamer/config.go index f055a1db44..8ddd67902d 100644 --- a/cmd/gossamer/config.go +++ b/cmd/gossamer/config.go @@ -21,10 +21,12 @@ import ( "strconv" "strings" + "github.com/ChainSafe/gossamer/chain/dev" "github.com/ChainSafe/gossamer/chain/gssmr" "github.com/ChainSafe/gossamer/dot" ctoml "github.com/ChainSafe/gossamer/dot/config/toml" "github.com/ChainSafe/gossamer/dot/state" + "github.com/ChainSafe/gossamer/dot/state/pruner" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/genesis" @@ -174,6 +176,14 @@ func createInitConfig(ctx *cli.Context) (*dot.Config, error) { return nil, err } + if !cfg.Global.Pruning.IsValid() { + return nil, fmt.Errorf("--%s must be either %s or %s", PruningFlag.Name, pruner.Full, pruner.Archive) + } + + if cfg.Global.RetainBlocks < dev.DefaultRetainBlocks { + return nil, fmt.Errorf("--%s cannot be less than %d", RetainBlockNumberFlag.Name, dev.DefaultRetainBlocks) + } + // set log config err = setLogConfig(ctx, tomlCfg, &cfg.Global, &cfg.Log) if err != nil { @@ -443,6 +453,9 @@ func setDotGlobalConfigFromToml(tomlCfg *ctoml.Config, cfg *dot.GlobalConfig) { } cfg.MetricsPort = tomlCfg.Global.MetricsPort + + cfg.RetainBlocks = tomlCfg.Global.RetainBlocks + cfg.Pruning = pruner.Mode(tomlCfg.Global.Pruning) } } @@ -472,6 +485,8 @@ func setDotGlobalConfigFromFlags(ctx *cli.Context, cfg *dot.GlobalConfig) { cfg.MetricsPort = uint32(metricsPort) } + cfg.RetainBlocks = ctx.Int64(RetainBlockNumberFlag.Name) + cfg.Pruning = pruner.Mode(ctx.String(PruningFlag.Name)) cfg.NoTelemetry = ctx.Bool("no-telemetry") } diff --git a/cmd/gossamer/config_test.go b/cmd/gossamer/config_test.go index 2f2a3368de..36430ec0df 100644 --- a/cmd/gossamer/config_test.go +++ b/cmd/gossamer/config_test.go @@ -20,6 +20,7 @@ import ( "io/ioutil" "testing" + "github.com/ChainSafe/gossamer/chain/dev" "github.com/ChainSafe/gossamer/chain/gssmr" "github.com/ChainSafe/gossamer/dot" "github.com/ChainSafe/gossamer/dot/state" @@ -45,26 +46,26 @@ func TestConfigFromChainFlag(t *testing.T) { }{ { "Test gossamer --chain gssmr", - []string{"chain", "name"}, - []interface{}{"gssmr", dot.GssmrConfig().Global.Name}, + []string{"chain", "name", "pruning", "retain-blocks"}, + []interface{}{"gssmr", dot.GssmrConfig().Global.Name, gssmr.DefaultPruningMode, gssmr.DefaultRetainBlocks}, dot.GssmrConfig(), }, { "Test gossamer --chain kusama", - []string{"chain", "name"}, - []interface{}{"kusama", dot.KusamaConfig().Global.Name}, + []string{"chain", "name", "pruning", "retain-blocks"}, + []interface{}{"kusama", dot.KusamaConfig().Global.Name, gssmr.DefaultPruningMode, gssmr.DefaultRetainBlocks}, dot.KusamaConfig(), }, { "Test gossamer --chain polkadot", - []string{"chain", "name"}, - []interface{}{"polkadot", dot.PolkadotConfig().Global.Name}, + []string{"chain", "name", "pruning", "retain-blocks"}, + []interface{}{"polkadot", dot.PolkadotConfig().Global.Name, gssmr.DefaultPruningMode, gssmr.DefaultRetainBlocks}, dot.PolkadotConfig(), }, { "Test gossamer --chain dev", - []string{"chain", "name"}, - []interface{}{"dev", dot.DevConfig().Global.Name}, + []string{"chain", "name", "pruning", "retain-blocks"}, + []interface{}{"dev", dot.DevConfig().Global.Name, dev.DefaultPruningMode, dev.DefaultRetainBlocks}, dot.DevConfig(), }, } @@ -100,8 +101,8 @@ func TestInitConfigFromFlags(t *testing.T) { }{ { "Test gossamer --genesis", - []string{"config", "genesis"}, - []interface{}{testCfgFile.Name(), "test_genesis"}, + []string{"config", "genesis", "pruning", "retain-blocks"}, + []interface{}{testCfgFile.Name(), "test_genesis", dev.DefaultPruningMode, dev.DefaultRetainBlocks}, dot.InitConfig{ Genesis: "test_genesis", }, diff --git a/cmd/gossamer/export.go b/cmd/gossamer/export.go index fc6b5b06f1..b088137d2d 100644 --- a/cmd/gossamer/export.go +++ b/cmd/gossamer/export.go @@ -79,11 +79,13 @@ func dotConfigToToml(dcfg *dot.Config) *ctoml.Config { cfg := &ctoml.Config{} cfg.Global = ctoml.GlobalConfig{ - Name: dcfg.Global.Name, - ID: dcfg.Global.ID, - BasePath: dcfg.Global.BasePath, - LogLvl: dcfg.Global.LogLvl.String(), - MetricsPort: dcfg.Global.MetricsPort, + Name: dcfg.Global.Name, + ID: dcfg.Global.ID, + BasePath: dcfg.Global.BasePath, + LogLvl: dcfg.Global.LogLvl.String(), + MetricsPort: dcfg.Global.MetricsPort, + RetainBlocks: dcfg.Global.RetainBlocks, + Pruning: string(dcfg.Global.Pruning), } cfg.Log = ctoml.LogConfig{ diff --git a/cmd/gossamer/export_test.go b/cmd/gossamer/export_test.go index b89e664471..ae247aabb4 100644 --- a/cmd/gossamer/export_test.go +++ b/cmd/gossamer/export_test.go @@ -20,6 +20,7 @@ import ( "io/ioutil" "testing" + "github.com/ChainSafe/gossamer/chain/gssmr" "github.com/ChainSafe/gossamer/dot" "github.com/ChainSafe/gossamer/lib/utils" @@ -91,8 +92,8 @@ func TestExportCommand(t *testing.T) { }, { "Test gossamer export --config --genesis --bootnodes --log --force", - []string{"config", "genesis", "bootnodes", "name", "force"}, - []interface{}{testConfig, genFile.Name(), testBootnode, "Gossamer", "true"}, + []string{"config", "genesis", "bootnodes", "name", "force", "pruning", "retain-blocks"}, + []interface{}{testConfig, genFile.Name(), testBootnode, "Gossamer", "true", gssmr.DefaultPruningMode, gssmr.DefaultRetainBlocks}, &dot.Config{ Global: testCfg.Global, Init: dot.InitConfig{ @@ -122,8 +123,8 @@ func TestExportCommand(t *testing.T) { }, { "Test gossamer export --config --genesis --protocol --log --force", - []string{"config", "genesis", "protocol", "force", "name"}, - []interface{}{testConfig, genFile.Name(), testProtocol, "true", "Gossamer"}, + []string{"config", "genesis", "protocol", "force", "name", "pruning", "retain-blocks"}, + []interface{}{testConfig, genFile.Name(), testProtocol, "true", "Gossamer", gssmr.DefaultPruningMode, gssmr.DefaultRetainBlocks}, &dot.Config{ Global: testCfg.Global, Init: dot.InitConfig{ diff --git a/cmd/gossamer/flags.go b/cmd/gossamer/flags.go index ba3559234b..284a8a3e25 100644 --- a/cmd/gossamer/flags.go +++ b/cmd/gossamer/flags.go @@ -17,6 +17,7 @@ package main import ( + "github.com/ChainSafe/gossamer/chain/dev" log "github.com/ChainSafe/log15" "github.com/urfave/cli" ) @@ -280,10 +281,18 @@ var ( } // RetainBlockNumberFlag retain number of block from latest block while pruning, valid for the use with prune-state subcommand - RetainBlockNumberFlag = cli.IntFlag{ + RetainBlockNumberFlag = cli.Int64Flag{ Name: "retain-blocks", Usage: "Retain number of block from latest block while pruning", - Value: 256, + Value: dev.DefaultRetainBlocks, + } + + // PruningFlag triggers the online pruning of historical state tries. It's either full or archive. To enable pruning the value + // should be set to `full`. + PruningFlag = cli.StringFlag{ + Name: "pruning", + Usage: `State trie online pruning ("full", "archive")`, + Value: dev.DefaultPruningMode, } ) @@ -301,7 +310,6 @@ var ( RewindFlag, DBPathFlag, BloomFilterSizeFlag, - RetainBlockNumberFlag, } // StartupFlags are flags that are valid for use with the root command and the export subcommand @@ -346,6 +354,8 @@ var ( InitFlags = append([]cli.Flag{ ForceFlag, GenesisFlag, + PruningFlag, + RetainBlockNumberFlag, }, GlobalFlags...) BuildSpecFlags = append([]cli.Flag{ diff --git a/cmd/gossamer/flags_test.go b/cmd/gossamer/flags_test.go index 6b459f338d..c095f4be9a 100644 --- a/cmd/gossamer/flags_test.go +++ b/cmd/gossamer/flags_test.go @@ -20,6 +20,7 @@ import ( "io/ioutil" "testing" + "github.com/ChainSafe/gossamer/chain/dev" "github.com/ChainSafe/gossamer/dot" "github.com/ChainSafe/gossamer/lib/utils" @@ -43,24 +44,24 @@ func TestFixFlagOrder(t *testing.T) { values []interface{} }{ { - "Test gossamer --config --genesis --log --force", - []string{"config", "genesis", "log", "force"}, - []interface{}{testConfig.Name(), genFile.Name(), "trace", true}, + "Test gossamer --config --genesis --log --force --pruning --retain-blocks", + []string{"config", "genesis", "log", "force", "pruning", "retain-blocks"}, + []interface{}{testConfig.Name(), genFile.Name(), "trace", true, dev.DefaultPruningMode, dev.DefaultRetainBlocks}, }, { - "Test gossamer --config --genesis --force --log", - []string{"config", "genesis", "force", "log"}, - []interface{}{testConfig.Name(), genFile.Name(), true, "trace"}, + "Test gossamer --config --genesis --force --log --pruning --retain-blocks", + []string{"config", "genesis", "force", "log", "pruning", "retain-blocks"}, + []interface{}{testConfig.Name(), genFile.Name(), true, "trace", dev.DefaultPruningMode, dev.DefaultRetainBlocks}, }, { - "Test gossamer --config --force --genesis --log", - []string{"config", "force", "genesis", "log"}, - []interface{}{testConfig.Name(), true, genFile.Name(), "trace"}, + "Test gossamer --config --force --genesis --log ---pruning --retain-blocks", + []string{"config", "force", "genesis", "log", "pruning", "retain-blocks"}, + []interface{}{testConfig.Name(), true, genFile.Name(), "trace", dev.DefaultPruningMode, dev.DefaultRetainBlocks}, }, { - "Test gossamer --force --config --genesis --log", - []string{"force", "config", "genesis", "log"}, - []interface{}{true, testConfig.Name(), genFile.Name(), "trace"}, + "Test gossamer --force --config --genesis --log --pruning --retain-blocks", + []string{"force", "config", "genesis", "log", "pruning", "retain-blocks"}, + []interface{}{true, testConfig.Name(), genFile.Name(), "trace", dev.DefaultPruningMode, dev.DefaultRetainBlocks}, }, } diff --git a/cmd/gossamer/main.go b/cmd/gossamer/main.go index 46fe0f7409..98faf82db3 100644 --- a/cmd/gossamer/main.go +++ b/cmd/gossamer/main.go @@ -333,7 +333,6 @@ func initAction(ctx *cli.Context) error { // expand data directory and update node configuration (performed separately // from createDotConfig because dot config should not include expanded path) cfg.Global.BasePath = utils.ExpandDir(cfg.Global.BasePath) - // check if node has been initialised (expected false - no warning log) if dot.NodeInitialized(cfg.Global.BasePath, false) { @@ -443,12 +442,12 @@ func pruneState(ctx *cli.Context) error { bloomSize := ctx.GlobalUint64(BloomFilterSizeFlag.Name) retainBlocks := ctx.GlobalInt64(RetainBlockNumberFlag.Name) - pruner, err := state.NewPruner(inputDBPath, prunedDBPath, bloomSize, retainBlocks) + pruner, err := state.NewOfflinePruner(inputDBPath, prunedDBPath, bloomSize, retainBlocks) if err != nil { return err } - logger.Info("Pruner initialised") + logger.Info("Offline pruner initialised") err = pruner.SetBloomFilter() if err != nil { diff --git a/cmd/gossamer/prune_test.go b/cmd/gossamer/prune_test.go index e84e6c7c9e..14c8fba6a9 100644 --- a/cmd/gossamer/prune_test.go +++ b/cmd/gossamer/prune_test.go @@ -22,7 +22,7 @@ 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"}, + []interface{}{configFile, prunedDBPath, "256", int64(5)}, ) if err != nil { t.Fatal(err) diff --git a/cmd/gossamer/utils.go b/cmd/gossamer/utils.go index edd4f36a5b..d83a4045d2 100644 --- a/cmd/gossamer/utils.go +++ b/cmd/gossamer/utils.go @@ -95,6 +95,8 @@ func newTestConfig(t *testing.T) *dot.Config { LogLvl: log.LvlInfo, PublishMetrics: dot.GssmrConfig().Global.PublishMetrics, MetricsPort: dot.GssmrConfig().Global.MetricsPort, + RetainBlocks: dot.GssmrConfig().Global.RetainBlocks, + Pruning: dot.GssmrConfig().Global.Pruning, }, Log: dot.LogConfig{ CoreLvl: log.LvlInfo, diff --git a/cmd/gossamer/utils_test.go b/cmd/gossamer/utils_test.go index 699f55e1c1..dd825f394d 100644 --- a/cmd/gossamer/utils_test.go +++ b/cmd/gossamer/utils_test.go @@ -38,6 +38,8 @@ func newTestContext(description string, flags []string, values []interface{}) (* set.String(flags[i], v, "") case uint: set.Uint(flags[i], v, "") + case int64: + set.Int64(flags[i], v, "") default: return nil, fmt.Errorf("unexpected cli value type: %T", values[i]) } @@ -69,6 +71,11 @@ func newTestContext(description string, flags []string, values []interface{}) (* if err != nil { return nil, fmt.Errorf("failed to set cli flag: %T", flags[i]) } + case int64: + err := ctx.Set(flags[i], strconv.Itoa(int(values[i].(int64)))) + if err != nil { + return nil, fmt.Errorf("failed to set cli flag: %T", flags[i]) + } default: return nil, fmt.Errorf("unexpected cli value type: %T", values[i]) } diff --git a/dot/build_spec.go b/dot/build_spec.go index 53e150f1e6..7abe4a2727 100644 --- a/dot/build_spec.go +++ b/dot/build_spec.go @@ -107,7 +107,11 @@ func BuildFromDB(path string) (*BuildSpec, error) { tmpGen.Genesis.Raw = make(map[string]map[string]string) tmpGen.Genesis.Runtime = make(map[string]map[string]interface{}) - stateSrvc := state.NewService(path, log.LvlCrit) + config := state.Config{ + Path: path, + LogLevel: log.LvlInfo, + } + stateSrvc := state.NewService(config) // start state service (initialise state database) err := stateSrvc.Start() diff --git a/dot/config.go b/dot/config.go index 8741d943ae..e5572e10ab 100644 --- a/dot/config.go +++ b/dot/config.go @@ -23,6 +23,7 @@ import ( "github.com/ChainSafe/gossamer/chain/gssmr" "github.com/ChainSafe/gossamer/chain/kusama" "github.com/ChainSafe/gossamer/chain/polkadot" + "github.com/ChainSafe/gossamer/dot/state/pruner" "github.com/ChainSafe/gossamer/dot/types" log "github.com/ChainSafe/log15" ) @@ -52,6 +53,8 @@ type GlobalConfig struct { PublishMetrics bool MetricsPort uint32 NoTelemetry bool + RetainBlocks int64 + Pruning pruner.Mode } // LogConfig represents the log levels for individual packages @@ -131,11 +134,13 @@ func networkServiceEnabled(cfg *Config) bool { func GssmrConfig() *Config { return &Config{ Global: GlobalConfig{ - Name: gssmr.DefaultName, - ID: gssmr.DefaultID, - BasePath: gssmr.DefaultBasePath, - LogLvl: gssmr.DefaultLvl, - MetricsPort: gssmr.DefaultMetricsPort, + Name: gssmr.DefaultName, + ID: gssmr.DefaultID, + BasePath: gssmr.DefaultBasePath, + LogLvl: gssmr.DefaultLvl, + MetricsPort: gssmr.DefaultMetricsPort, + RetainBlocks: gssmr.DefaultRetainBlocks, + Pruning: pruner.Mode(gssmr.DefaultPruningMode), }, Log: LogConfig{ CoreLvl: gssmr.DefaultLvl, @@ -179,11 +184,13 @@ func GssmrConfig() *Config { func KusamaConfig() *Config { return &Config{ Global: GlobalConfig{ - Name: kusama.DefaultName, - ID: kusama.DefaultID, - BasePath: kusama.DefaultBasePath, - LogLvl: kusama.DefaultLvl, - MetricsPort: kusama.DefaultMetricsPort, + Name: kusama.DefaultName, + ID: kusama.DefaultID, + BasePath: kusama.DefaultBasePath, + LogLvl: kusama.DefaultLvl, + MetricsPort: kusama.DefaultMetricsPort, + RetainBlocks: gssmr.DefaultRetainBlocks, + Pruning: pruner.Mode(gssmr.DefaultPruningMode), }, Log: LogConfig{ CoreLvl: kusama.DefaultLvl, @@ -225,10 +232,12 @@ func KusamaConfig() *Config { func PolkadotConfig() *Config { return &Config{ Global: GlobalConfig{ - Name: polkadot.DefaultName, - ID: polkadot.DefaultID, - BasePath: polkadot.DefaultBasePath, - LogLvl: polkadot.DefaultLvl, + Name: polkadot.DefaultName, + ID: polkadot.DefaultID, + BasePath: polkadot.DefaultBasePath, + LogLvl: polkadot.DefaultLvl, + RetainBlocks: gssmr.DefaultRetainBlocks, + Pruning: pruner.Mode(gssmr.DefaultPruningMode), }, Log: LogConfig{ CoreLvl: polkadot.DefaultLvl, @@ -270,11 +279,13 @@ func PolkadotConfig() *Config { func DevConfig() *Config { return &Config{ Global: GlobalConfig{ - Name: dev.DefaultName, - ID: dev.DefaultID, - BasePath: dev.DefaultBasePath, - LogLvl: dev.DefaultLvl, - MetricsPort: dev.DefaultMetricsPort, + Name: dev.DefaultName, + ID: dev.DefaultID, + BasePath: dev.DefaultBasePath, + LogLvl: dev.DefaultLvl, + MetricsPort: dev.DefaultMetricsPort, + RetainBlocks: dev.DefaultRetainBlocks, + Pruning: pruner.Mode(dev.DefaultPruningMode), }, Log: LogConfig{ CoreLvl: dev.DefaultLvl, diff --git a/dot/config/toml/config.go b/dot/config/toml/config.go index 2097b65801..cf6555f3b5 100644 --- a/dot/config/toml/config.go +++ b/dot/config/toml/config.go @@ -29,11 +29,13 @@ type Config struct { // GlobalConfig is to marshal/unmarshal toml global config vars type GlobalConfig struct { - Name string `toml:"name,omitempty"` - ID string `toml:"id,omitempty"` - BasePath string `toml:"basepath,omitempty"` - LogLvl string `toml:"log,omitempty"` - MetricsPort uint32 `toml:"metrics-port,omitempty"` + Name string `toml:"name,omitempty"` + ID string `toml:"id,omitempty"` + BasePath string `toml:"basepath,omitempty"` + LogLvl string `toml:"log,omitempty"` + MetricsPort uint32 `toml:"metrics-port,omitempty"` + RetainBlocks int64 `toml:"retain-blocks,omitempty"` + Pruning string `toml:"pruning,omitempty"` } // LogConfig represents the log levels for individual packages diff --git a/dot/core/digest_test.go b/dot/core/digest_test.go index b076e32db9..308b06497e 100644 --- a/dot/core/digest_test.go +++ b/dot/core/digest_test.go @@ -38,7 +38,12 @@ import ( func newTestDigestHandler(t *testing.T, withBABE, withGrandpa bool) *DigestHandler { //nolint testDatadirPath, err := ioutil.TempDir("/tmp", "test-datadir-*") require.NoError(t, err) - stateSrvc := state.NewService(testDatadirPath, log.LvlInfo) + + config := state.Config{ + Path: testDatadirPath, + LogLevel: log.LvlInfo, + } + stateSrvc := state.NewService(config) stateSrvc.UseMemDB() gen, genTrie, genHeader := newTestGenesisWithTrieAndHeader(t) diff --git a/dot/core/test_helpers.go b/dot/core/test_helpers.go index 67221798de..a7a4a226a9 100644 --- a/dot/core/test_helpers.go +++ b/dot/core/test_helpers.go @@ -90,7 +90,11 @@ func NewTestService(t *testing.T, cfg *Config) *Service { gen, genTrie, genHeader := newTestGenesisWithTrieAndHeader(t) if cfg.BlockState == nil || cfg.StorageState == nil || cfg.TransactionState == nil || cfg.EpochState == nil { - stateSrvc = state.NewService(testDatadirPath, log.LvlInfo) + config := state.Config{ + Path: testDatadirPath, + LogLevel: log.LvlInfo, + } + stateSrvc = state.NewService(config) stateSrvc.UseMemDB() err = stateSrvc.Initialise(gen, genHeader, genTrie) diff --git a/dot/import.go b/dot/import.go index 0a78503f82..6546871b42 100644 --- a/dot/import.go +++ b/dot/import.go @@ -45,7 +45,11 @@ func ImportState(basepath, stateFP, headerFP string, firstSlot uint64) error { log.Info("ImportState", "header", header) - srv := state.NewService(basepath, log.LvlInfo) + config := state.Config{ + Path: basepath, + LogLevel: log.LvlInfo, + } + srv := state.NewService(config) return srv.Import(header, tr, firstSlot) } diff --git a/dot/node.go b/dot/node.go index bbce2985f4..4da6ec9348 100644 --- a/dot/node.go +++ b/dot/node.go @@ -31,6 +31,7 @@ import ( gssmrmetrics "github.com/ChainSafe/gossamer/dot/metrics" "github.com/ChainSafe/gossamer/dot/network" "github.com/ChainSafe/gossamer/dot/state" + "github.com/ChainSafe/gossamer/dot/state/pruner" "github.com/ChainSafe/gossamer/dot/telemetry" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/genesis" @@ -90,8 +91,20 @@ func InitNode(cfg *Config) error { return fmt.Errorf("failed to create genesis block from trie: %w", err) } + config := state.Config{ + Path: cfg.Global.BasePath, + LogLevel: cfg.Global.LogLvl, + PrunerCfg: struct { + Mode pruner.Mode + RetainedBlocks int64 + }{ + Mode: cfg.Global.Pruning, + RetainedBlocks: cfg.Global.RetainBlocks, + }, + } + // create new state service - stateSrvc := state.NewService(cfg.Global.BasePath, cfg.Global.LogLvl) + stateSrvc := state.NewService(config) // initialise state service with genesis data, block, and trie err = stateSrvc.Initialise(gen, header, t) diff --git a/dot/node_test.go b/dot/node_test.go index 29d331470b..455bebb1d4 100644 --- a/dot/node_test.go +++ b/dot/node_test.go @@ -212,7 +212,11 @@ func TestInitNode_LoadGenesisData(t *testing.T) { err := InitNode(cfg) require.NoError(t, err) - stateSrvc := state.NewService(cfg.Global.BasePath, log.LvlTrace) + config := state.Config{ + Path: cfg.Global.BasePath, + LogLevel: log.LvlInfo, + } + stateSrvc := state.NewService(config) gen, err := genesis.NewGenesisFromJSONRaw(genPath) require.NoError(t, err) diff --git a/dot/rpc/modules/chain_test.go b/dot/rpc/modules/chain_test.go index cb00fcf714..595f723e47 100644 --- a/dot/rpc/modules/chain_test.go +++ b/dot/rpc/modules/chain_test.go @@ -280,7 +280,12 @@ var gen, genTrie, genesisHeader = newTestGenesisWithTrieAndHeader() func newTestStateService(t *testing.T) *state.Service { testDatadirPath, err := ioutil.TempDir("/tmp", "test-datadir-*") require.NoError(t, err) - stateSrvc := state.NewService(testDatadirPath, log.LvlInfo) + + config := state.Config{ + Path: testDatadirPath, + LogLevel: log.LvlInfo, + } + stateSrvc := state.NewService(config) stateSrvc.UseMemDB() err = stateSrvc.Initialise(gen, genesisHeader, genTrie) diff --git a/dot/rpc/modules/state_test.go b/dot/rpc/modules/state_test.go index f08dfc56a4..b5bed60398 100644 --- a/dot/rpc/modules/state_test.go +++ b/dot/rpc/modules/state_test.go @@ -429,7 +429,7 @@ func setupStateModule(t *testing.T) (*StateModule, *common.Hash, *common.Hash) { sr1, err := ts.Root() require.NoError(t, err) - err = chain.Storage.StoreTrie(ts) + err = chain.Storage.StoreTrie(ts, nil) require.NoError(t, err) err = chain.Block.AddBlock(&types.Block{ diff --git a/dot/rpc/modules/system_test.go b/dot/rpc/modules/system_test.go index 34ee87317f..a06ac0f6bb 100644 --- a/dot/rpc/modules/system_test.go +++ b/dot/rpc/modules/system_test.go @@ -306,7 +306,7 @@ func setupSystemModule(t *testing.T) *SystemModule { require.NoError(t, err) ts.Set(aliceAcctStoKey, aliceAcctEncoded) - err = chain.Storage.StoreTrie(ts) + err = chain.Storage.StoreTrie(ts, nil) require.NoError(t, err) err = chain.Block.AddBlock(&types.Block{ Header: &types.Header{ diff --git a/dot/services.go b/dot/services.go index 7379a40dd4..6b7c5697b1 100644 --- a/dot/services.go +++ b/dot/services.go @@ -53,7 +53,13 @@ func newInMemoryDB(path string) (chaindb.Database, error) { // createStateService creates the state service and initialise state database func createStateService(cfg *Config) (*state.Service, error) { logger.Debug("creating state service...") - stateSrvc := state.NewService(cfg.Global.BasePath, cfg.Log.StateLvl) + + config := state.Config{ + Path: cfg.Global.BasePath, + LogLevel: cfg.Log.StateLvl, + } + + stateSrvc := state.NewService(config) // start state service (initialise state database) err := stateSrvc.Start() diff --git a/dot/state/base.go b/dot/state/base.go index 72133a048c..08401b3c3f 100644 --- a/dot/state/base.go +++ b/dot/state/base.go @@ -21,10 +21,10 @@ import ( "encoding/json" "fmt" + "github.com/ChainSafe/chaindb" + "github.com/ChainSafe/gossamer/dot/state/pruner" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/genesis" - - "github.com/ChainSafe/chaindb" ) // BaseState is a wrapper for the chaindb.Database, without any prefixes @@ -154,3 +154,26 @@ func (s *BaseState) loadFirstSlot() (uint64, error) { return binary.LittleEndian.Uint64(data), nil } + +// storePruningData stores the pruner configuration. +func (s *BaseState) storePruningData(mode pruner.Config) error { + encMode, err := json.Marshal(mode) + if err != nil { + return fmt.Errorf("cannot scale encode pruning mode: %s", err) + } + + return s.db.Put(common.PruningKey, encMode) +} + +// loadPruningData retrieves pruner configuration from db. +func (s *BaseState) loadPruningData() (pruner.Config, error) { + data, err := s.db.Get(common.PruningKey) + if err != nil { + return pruner.Config{}, err + } + + var mode pruner.Config + err = json.Unmarshal(data, &mode) + + return mode, err +} diff --git a/dot/state/initialize.go b/dot/state/initialize.go index 11185a7862..e4a6afdac1 100644 --- a/dot/state/initialize.go +++ b/dot/state/initialize.go @@ -21,6 +21,8 @@ import ( "fmt" "path/filepath" + "github.com/ChainSafe/chaindb" + "github.com/ChainSafe/gossamer/dot/state/pruner" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/blocktree" "github.com/ChainSafe/gossamer/lib/genesis" @@ -29,8 +31,6 @@ import ( "github.com/ChainSafe/gossamer/lib/runtime/wasmer" "github.com/ChainSafe/gossamer/lib/trie" "github.com/ChainSafe/gossamer/lib/utils" - - "github.com/ChainSafe/chaindb" ) // Initialise initialises the genesis state of the DB using the given storage trie. The trie should be loaded with the genesis storage state. @@ -89,7 +89,7 @@ func (s *Service) Initialise(gen *genesis.Genesis, header *types.Header, t *trie } // create storage state from genesis trie - storageState, err := NewStorageState(db, blockState, t) + storageState, err := NewStorageState(db, blockState, t, pruner.Config{}) if err != nil { return fmt.Errorf("failed to create storage state from trie: %s", err) } @@ -174,6 +174,10 @@ func (s *Service) storeInitialValues(data *genesis.Data, header *types.Header, t return fmt.Errorf("failed to write genesis data to database: %s", err) } + if err := s.Base.storePruningData(s.PrunerCfg); err != nil { + return fmt.Errorf("failed to write pruning data to database: %s", err) + } + return nil } diff --git a/dot/state/prune.go b/dot/state/offline_pruner.go similarity index 89% rename from dot/state/prune.go rename to dot/state/offline_pruner.go index 2df24d848a..8ebe28f36e 100644 --- a/dot/state/prune.go +++ b/dot/state/offline_pruner.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/ChainSafe/chaindb" + "github.com/ChainSafe/gossamer/dot/state/pruner" "github.com/ChainSafe/gossamer/lib/blocktree" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/trie" @@ -14,11 +15,11 @@ import ( "github.com/dgraph-io/badger/v2/pb" ) -// Pruner is an offline tool to prune the stale state with the help of -// bloom filter, The workflow of pruner is very simple: +// OfflinePruner is a tool to prune the stale state with the help of +// bloom filter, The workflow of Pruner is very simple: // - iterate the storage state, reconstruct the relevant state tries // - iterate the database, stream all the targeted keys to new DB -type Pruner struct { +type OfflinePruner struct { inputDB *chaindb.BadgerDB storageState *StorageState blockState *BlockState @@ -30,8 +31,8 @@ type Pruner struct { prunedDBPath string } -// NewPruner creates an instance of Pruner. -func NewPruner(inputDBPath, prunedDBPath string, bloomSize uint64, retainBlockNum int64) (*Pruner, error) { +// NewOfflinePruner creates an instance of OfflinePruner. +func NewOfflinePruner(inputDBPath, prunedDBPath string, bloomSize uint64, retainBlockNum int64) (*OfflinePruner, error) { db, err := utils.LoadChainDB(inputDBPath) if err != nil { return nil, fmt.Errorf("failed to load DB %w", err) @@ -62,12 +63,12 @@ func NewPruner(inputDBPath, prunedDBPath string, bloomSize uint64, retainBlockNu } // load storage state - storageState, err := NewStorageState(db, blockState, trie.NewEmptyTrie()) + storageState, err := NewStorageState(db, blockState, trie.NewEmptyTrie(), pruner.Config{}) if err != nil { return nil, fmt.Errorf("failed to create new storage state %w", err) } - return &Pruner{ + return &OfflinePruner{ inputDB: db, storageState: storageState, blockState: blockState, @@ -80,7 +81,7 @@ func NewPruner(inputDBPath, prunedDBPath string, bloomSize uint64, retainBlockNu } // SetBloomFilter loads keys with storage prefix of last `retainBlockNum` blocks into the bloom filter -func (p *Pruner) SetBloomFilter() error { +func (p *OfflinePruner) SetBloomFilter() error { defer p.inputDB.Close() // nolint: errcheck finalisedHash, err := p.blockState.GetFinalizedHash(0, 0) if err != nil { @@ -134,7 +135,7 @@ func (p *Pruner) SetBloomFilter() error { } // Prune starts streaming the data from input db to the pruned db. -func (p *Pruner) Prune() error { +func (p *OfflinePruner) Prune() error { inputDB, err := utils.LoadBadgerDB(p.inputDBPath) if err != nil { return fmt.Errorf("failed to load DB %w", err) diff --git a/dot/state/pruner/pruner.go b/dot/state/pruner/pruner.go new file mode 100644 index 0000000000..b98ccf2843 --- /dev/null +++ b/dot/state/pruner/pruner.go @@ -0,0 +1,375 @@ +package pruner + +import ( + "fmt" + "sync" + "time" + + "github.com/ChainSafe/chaindb" + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/scale" + log "github.com/ChainSafe/log15" +) + +const ( + journalPrefix = "journal" + lastPrunedKey = "last_pruned" + pruneInterval = time.Second +) + +// nolint +const ( + Full = Mode("full") + Archive = Mode("archive") +) + +// Mode online pruning mode of historical state tries +type Mode string + +// IsValid checks whether the pruning mode is valid +func (p Mode) IsValid() bool { + switch p { + case Full: + return true + case Archive: + return true + default: + return false + } +} + +// Config holds state trie pruning mode and retained blocks +type Config struct { + Mode Mode + RetainedBlocks int64 +} + +// Pruner is implemented by FullNode and ArchiveNode. +type Pruner interface { + StoreJournalRecord(deleted, inserted []common.Hash, blockHash common.Hash, blockNum int64) error +} + +// ArchiveNode is a no-op since we don't prune nodes in archive mode. +type ArchiveNode struct{} + +// StoreJournalRecord for archive node doesn't do anything. +func (a *ArchiveNode) StoreJournalRecord(deleted, inserted []common.Hash, blockHash common.Hash, blockNum int64) error { + return nil +} + +type deathRecord struct { + blockHash common.Hash + deletedKeys map[common.Hash]int64 // Mapping from deleted key hash to block number. +} + +type deathRow []*deathRecord + +// FullNode stores state trie diff and allows online state trie pruning +type FullNode struct { + logger log.Logger + deathList []deathRow + storageDB chaindb.Database + journalDB chaindb.Database + deathIndex map[common.Hash]int64 // Mapping from deleted key hash to block number. + pendingNumber int64 // block number to be pruned. Initial value is set to 1 and is incremented after every block pruning. + retainBlocks int64 + sync.RWMutex +} + +type journalRecord struct { + // blockHash of the block corresponding to journal record + blockHash common.Hash + // Hash of keys that are inserted into state trie of the block + insertedKeys []common.Hash + // Hash of keys that are deleted from state trie of the block + deletedKeys []common.Hash +} + +type journalKey struct { + blockNum int64 + blockHash common.Hash +} + +func newJournalRecord(hash common.Hash, insertedKeys, deletedKeys []common.Hash) *journalRecord { + return &journalRecord{ + blockHash: hash, + insertedKeys: insertedKeys, + deletedKeys: deletedKeys, + } +} + +// NewFullNode creates a Pruner for full node. +func NewFullNode(db, storageDB chaindb.Database, retainBlocks int64, l log.Logger) (Pruner, error) { + p := &FullNode{ + deathList: make([]deathRow, 0), + deathIndex: make(map[common.Hash]int64), + storageDB: storageDB, + journalDB: chaindb.NewTable(db, journalPrefix), + retainBlocks: retainBlocks, + logger: l, + } + + blockNum, err := p.getLastPrunedIndex() + if err != nil { + return nil, err + } + + p.logger.Debug("last pruned block", "block num", blockNum) + blockNum++ + + p.pendingNumber = blockNum + + err = p.loadDeathList() + if err != nil { + return nil, err + } + + go p.start() + + return p, nil +} + +// StoreJournalRecord stores journal record into DB and add deathRow into deathList +func (p *FullNode) StoreJournalRecord(deleted, inserted []common.Hash, blockHash common.Hash, blockNum int64) error { + jr := newJournalRecord(blockHash, inserted, deleted) + + key := &journalKey{blockNum, blockHash} + err := p.storeJournal(key, jr) + if err != nil { + return fmt.Errorf("failed to store journal record for %d: %w", blockNum, err) + } + + p.logger.Debug("journal record stored", "block num", blockNum) + p.addDeathRow(jr, blockNum) + return nil +} + +func (p *FullNode) addDeathRow(jr *journalRecord, blockNum int64) { + if blockNum == 0 { + return + } + + p.Lock() + defer p.Unlock() + + // The block is already pruned. + if blockNum < p.pendingNumber { + return + } + + p.processInsertedKeys(jr.insertedKeys, jr.blockHash) + + // add deleted keys from journal to death index + for _, k := range jr.deletedKeys { + p.deathIndex[k] = blockNum + } + + deletedKeys := make(map[common.Hash]int64) + for _, data := range jr.deletedKeys { + deletedKeys[data] = blockNum + } + + blockIndex := blockNum - p.pendingNumber + for idx := blockIndex - int64(len(p.deathList)); idx >= 0; idx-- { + p.deathList = append(p.deathList, deathRow{}) + } + + record := &deathRecord{ + blockHash: jr.blockHash, + deletedKeys: deletedKeys, + } + + // add deathRow to deathList + p.deathList[blockIndex] = append(p.deathList[blockIndex], record) +} + +// Remove re-inserted keys +func (p *FullNode) processInsertedKeys(insKeys []common.Hash, blockHash common.Hash) { + for _, k := range insKeys { + num, ok := p.deathIndex[k] + if !ok { + continue + } + records := p.deathList[num-p.pendingNumber] + for _, v := range records { + if v.blockHash == blockHash { + delete(v.deletedKeys, k) + } + } + delete(p.deathIndex, k) + } +} + +func (p *FullNode) start() { + p.logger.Debug("pruning started") + + var canPrune bool + checkPruning := func() { + p.Lock() + defer p.Unlock() + if int64(len(p.deathList)) <= p.retainBlocks { + canPrune = false + return + } + canPrune = true + + // pop first element from death list + row := p.deathList[0] + blockNum := p.pendingNumber + + p.logger.Debug("pruning block", "block num", blockNum) + + sdbBatch := p.storageDB.NewBatch() + for _, record := range row { + err := p.deleteKeys(sdbBatch, record.deletedKeys) + if err != nil { + p.logger.Warn("failed to prune keys", "block num", blockNum, "error", err) + sdbBatch.Reset() + return + } + + for k := range record.deletedKeys { + delete(p.deathIndex, k) + } + } + + if err := sdbBatch.Flush(); err != nil { + p.logger.Warn("failed to prune keys", "block num", blockNum, "error", err) + return + } + + err := p.storeLastPrunedIndex(blockNum) + if err != nil { + p.logger.Warn("failed to store last pruned index", "block num", blockNum, "error", err) + return + } + + p.deathList = p.deathList[1:] + p.pendingNumber++ + + jdbBatch := p.journalDB.NewBatch() + for _, record := range row { + jk := &journalKey{blockNum, record.blockHash} + err = p.deleteJournalRecord(jdbBatch, jk) + if err != nil { + p.logger.Warn("failed to delete journal record", "block num", blockNum, "error", err) + jdbBatch.Reset() + return + } + } + + if err = jdbBatch.Flush(); err != nil { + p.logger.Warn("failed to delete journal record", "block num", blockNum, "error", err) + return + } + p.logger.Debug("pruned block", "block num", blockNum) + } + + for { + checkPruning() + // Don't sleep if we have data to prune. + if !canPrune { + time.Sleep(pruneInterval) + } + } +} + +func (p *FullNode) storeJournal(key *journalKey, jr *journalRecord) error { + encKey, err := scale.Encode(key) + if err != nil { + return fmt.Errorf("failed to encode journal key block num %d: %w", key.blockNum, err) + } + + encRecord, err := scale.Encode(jr) + if err != nil { + return fmt.Errorf("failed to encode journal record block num %d: %w", key.blockNum, err) + } + + err = p.journalDB.Put(encKey, encRecord) + if err != nil { + return err + } + + return nil +} + +// loadDeathList loads deathList and deathIndex from journalRecord. +func (p *FullNode) loadDeathList() error { + itr := p.journalDB.NewIterator() + defer itr.Release() + + for itr.Next() { + jk, err := scale.Decode(itr.Key(), new(journalKey)) + if err != nil { + return fmt.Errorf("failed to decode journal key %w", err) + } + + key := jk.(*journalKey) + val := itr.Value() + + jr, err := scale.Decode(val, new(journalRecord)) + if err != nil { + return fmt.Errorf("failed to decode journal record block num %d : %w", key.blockNum, err) + } + + p.addDeathRow(jr.(*journalRecord), key.blockNum) + } + return nil +} + +func (p *FullNode) deleteJournalRecord(b chaindb.Batch, key *journalKey) error { + encKey, err := scale.Encode(key) + if err != nil { + return err + } + + err = b.Del(encKey) + if err != nil { + return err + } + + return nil +} + +func (p *FullNode) storeLastPrunedIndex(blockNum int64) error { + encNum, err := scale.Encode(blockNum) + if err != nil { + return err + } + + err = p.journalDB.Put([]byte(lastPrunedKey), encNum) + if err != nil { + return err + } + + return nil +} + +func (p *FullNode) getLastPrunedIndex() (int64, error) { + val, err := p.journalDB.Get([]byte(lastPrunedKey)) + if err == chaindb.ErrKeyNotFound { + return 0, nil + } + + if err != nil { + return 0, err + } + + blockNum, err := scale.Decode(val, int64(0)) + if err != nil { + return 0, err + } + + return blockNum.(int64), nil +} + +func (p *FullNode) deleteKeys(b chaindb.Batch, nodesHash map[common.Hash]int64) error { + for k := range nodesHash { + err := b.Del(k.ToBytes()) + if err != nil { + return err + } + } + + return nil +} diff --git a/dot/state/service.go b/dot/state/service.go index 2a8f890065..fd187d6865 100644 --- a/dot/state/service.go +++ b/dot/state/service.go @@ -23,6 +23,7 @@ import ( "os" "path/filepath" + "github.com/ChainSafe/gossamer/dot/state/pruner" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/blocktree" "github.com/ChainSafe/gossamer/lib/trie" @@ -51,22 +52,33 @@ type Service struct { // Below are for testing only. BabeThresholdNumerator uint64 BabeThresholdDenominator uint64 + + // Below are for state trie online pruner + PrunerCfg pruner.Config +} + +// Config is the default configuration used by state service. +type Config struct { + Path string + LogLevel log.Lvl + PrunerCfg pruner.Config } // NewService create a new instance of Service -func NewService(path string, lvl log.Lvl) *Service { +func NewService(config Config) *Service { handler := log.StreamHandler(os.Stdout, log.TerminalFormat()) handler = log.CallerFileHandler(handler) - logger.SetHandler(log.LvlFilterHandler(lvl, handler)) + logger.SetHandler(log.LvlFilterHandler(config.LogLevel, handler)) return &Service{ - dbPath: path, - logLvl: lvl, - db: nil, - isMemDB: false, - Storage: nil, - Block: nil, - closeCh: make(chan interface{}), + dbPath: config.Path, + logLvl: config.LogLevel, + db: nil, + isMemDB: false, + Storage: nil, + Block: nil, + closeCh: make(chan interface{}), + PrunerCfg: config.PrunerCfg, } } @@ -140,8 +152,13 @@ func (s *Service) Start() error { s.Block.bt = blocktree.NewBlockTreeFromRoot(lastFinalised, db) } + pr, err := s.Base.loadPruningData() + if err != nil { + return err + } + // create storage state - s.Storage, err = NewStorageState(db, s.Block, trie.NewEmptyTrie()) + s.Storage, err = NewStorageState(db, s.Block, trie.NewEmptyTrie(), pr) if err != nil { return fmt.Errorf("failed to create storage state: %w", err) } diff --git a/dot/state/service_test.go b/dot/state/service_test.go index c16df057da..70d0a0d48d 100644 --- a/dot/state/service_test.go +++ b/dot/state/service_test.go @@ -17,11 +17,13 @@ package state import ( + "fmt" "io/ioutil" "math/big" "testing" "time" + "github.com/ChainSafe/gossamer/dot/state/pruner" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/genesis" @@ -48,13 +50,21 @@ func newTestGenesisWithTrieAndHeader(t *testing.T) (*genesis.Genesis, *trie.Trie // helper method to create and start test state service func newTestService(t *testing.T) (state *Service) { testDir := utils.NewTestDir(t) - state = NewService(testDir, log.LvlTrace) + config := Config{ + Path: testDir, + LogLevel: log.LvlInfo, + } + state = NewService(config) return state } func newTestMemDBService() *Service { testDatadirPath, _ := ioutil.TempDir("/tmp", "test-datadir-*") - state := NewService(testDatadirPath, log.LvlTrace) + config := Config{ + Path: testDatadirPath, + LogLevel: log.LvlInfo, + } + state := NewService(config) state.UseMemDB() return state } @@ -116,7 +126,11 @@ func TestService_BlockTree(t *testing.T) { // removes all data directories created within test directory defer utils.RemoveTestDir(t) - stateA := NewService(testDir, log.LvlTrace) + config := Config{ + Path: testDir, + LogLevel: log.LvlInfo, + } + stateA := NewService(config) genData, genTrie, genesisHeader := newTestGenesisWithTrieAndHeader(t) err := stateA.Initialise(genData, genesisHeader, genTrie) @@ -131,7 +145,7 @@ func TestService_BlockTree(t *testing.T) { err = stateA.Stop() require.NoError(t, err) - stateB := NewService(testDir, log.LvlTrace) + stateB := NewService(config) err = stateB.Start() require.NoError(t, err) @@ -141,11 +155,68 @@ func TestService_BlockTree(t *testing.T) { require.Equal(t, stateA.Block.BestBlockHash(), stateB.Block.BestBlockHash()) } +func TestService_StorageTriePruning(t *testing.T) { + testDir := utils.NewTestDir(t) + defer utils.RemoveTestDir(t) + + retainBlocks := 2 + config := Config{ + Path: testDir, + LogLevel: log.LvlInfo, + PrunerCfg: pruner.Config{ + Mode: pruner.Full, + RetainedBlocks: int64(retainBlocks), + }, + } + serv := NewService(config) + serv.UseMemDB() + + genData, genTrie, genesisHeader := newTestGenesisWithTrieAndHeader(t) + + err := serv.Initialise(genData, genesisHeader, genTrie) + require.NoError(t, err) + + err = serv.Start() + require.NoError(t, err) + + var blocks []*types.Block + parentHash := serv.Block.GenesisHash() + + totalBlock := 10 + for i := 1; i < totalBlock; i++ { + block, trieState := generateBlockWithRandomTrie(t, serv, &parentHash, int64(i)) + + err = serv.Storage.blockState.AddBlock(block) + require.NoError(t, err) + + err = serv.Storage.StoreTrie(trieState, block.Header) + require.NoError(t, err) + + blocks = append(blocks, block) + parentHash = block.Header.Hash() + } + + time.Sleep(2 * time.Second) + + for _, b := range blocks { + _, err := serv.Storage.LoadFromDB(b.Header.StateRoot) + if b.Header.Number.Int64() >= int64(totalBlock-retainBlocks-1) { + require.NoError(t, err, fmt.Sprintf("Got error for block %d", b.Header.Number.Int64())) + continue + } + require.ErrorIs(t, err, chaindb.ErrKeyNotFound, fmt.Sprintf("Expected error for block %d", b.Header.Number.Int64())) + } +} + func TestService_PruneStorage(t *testing.T) { testDir := utils.NewTestDir(t) defer utils.RemoveTestDir(t) - serv := NewService(testDir, log.LvlTrace) + config := Config{ + Path: testDir, + LogLevel: log.LvlInfo, + } + serv := NewService(config) serv.UseMemDB() genData, genTrie, genesisHeader := newTestGenesisWithTrieAndHeader(t) @@ -164,12 +235,12 @@ func TestService_PruneStorage(t *testing.T) { var toFinalize common.Hash for i := 0; i < 3; i++ { - block, trieState := generateBlockWithRandomTrie(t, serv, nil) + block, trieState := generateBlockWithRandomTrie(t, serv, nil, int64(i+1)) err = serv.Storage.blockState.AddBlock(block) require.NoError(t, err) - err = serv.Storage.StoreTrie(trieState) + err = serv.Storage.StoreTrie(trieState, nil) require.NoError(t, err) // Only finalise a block at height 3 @@ -179,15 +250,15 @@ func TestService_PruneStorage(t *testing.T) { } // add some blocks to prune, on a different chain from the finalised block - prunedArr := []prunedBlock{} + var prunedArr []prunedBlock parentHash := serv.Block.GenesisHash() for i := 0; i < 3; i++ { - block, trieState := generateBlockWithRandomTrie(t, serv, &parentHash) + block, trieState := generateBlockWithRandomTrie(t, serv, &parentHash, int64(i+1)) err = serv.Storage.blockState.AddBlock(block) require.NoError(t, err) - err = serv.Storage.StoreTrie(trieState) + err = serv.Storage.StoreTrie(trieState, nil) require.NoError(t, err) // Store the other blocks that will be pruned. @@ -220,7 +291,11 @@ func TestService_Rewind(t *testing.T) { testDir := utils.NewTestDir(t) defer utils.RemoveTestDir(t) - serv := NewService(testDir, log.LvlTrace) + config := Config{ + Path: testDir, + LogLevel: log.LvlInfo, + } + serv := NewService(config) serv.UseMemDB() genData, genTrie, genesisHeader := newTestGenesisWithTrieAndHeader(t) @@ -268,7 +343,11 @@ func TestService_Import(t *testing.T) { testDir := utils.NewTestDir(t) defer utils.RemoveTestDir(t) - serv := NewService(testDir, log.LvlTrace) + config := Config{ + Path: testDir, + LogLevel: log.LvlInfo, + } + serv := NewService(config) serv.UseMemDB() genData, genTrie, genesisHeader := newTestGenesisWithTrieAndHeader(t) diff --git a/dot/state/storage.go b/dot/state/storage.go index 8da6a9cd1a..104a8a0f67 100644 --- a/dot/state/storage.go +++ b/dot/state/storage.go @@ -23,6 +23,7 @@ import ( "sync" "github.com/ChainSafe/chaindb" + "github.com/ChainSafe/gossamer/dot/state/pruner" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" rtstorage "github.com/ChainSafe/gossamer/lib/runtime/storage" @@ -51,12 +52,12 @@ type StorageState struct { // change notifiers changedLock sync.RWMutex observerList []Observer - - syncing bool + pruner pruner.Pruner + syncing bool } // NewStorageState creates a new StorageState backed by the given trie and database located at basePath. -func NewStorageState(db chaindb.Database, blockState *BlockState, t *trie.Trie) (*StorageState, error) { +func NewStorageState(db chaindb.Database, blockState *BlockState, t *trie.Trie, onlinePruner pruner.Config) (*StorageState, error) { if db == nil { return nil, fmt.Errorf("cannot have nil database") } @@ -68,11 +69,25 @@ func NewStorageState(db chaindb.Database, blockState *BlockState, t *trie.Trie) tries := make(map[common.Hash]*trie.Trie) tries[t.MustHash()] = t + storageTable := chaindb.NewTable(db, storagePrefix) + + var p pruner.Pruner + if onlinePruner.Mode == pruner.Full { + var err error + p, err = pruner.NewFullNode(db, storageTable, onlinePruner.RetainedBlocks, logger) + if err != nil { + return nil, err + } + } else { + p = &pruner.ArchiveNode{} + } + return &StorageState{ blockState: blockState, tries: tries, - db: chaindb.NewTable(db, storagePrefix), + db: storageTable, observerList: []Observer{}, + pruner: p, }, nil } @@ -85,17 +100,11 @@ func (s *StorageState) pruneKey(keyHeader *types.Header) { s.lock.Lock() defer s.lock.Unlock() - _, ok := s.tries[keyHeader.StateRoot] - if !ok { - return - } - delete(s.tries, keyHeader.StateRoot) - // TODO: database pruning needs to be refactored since the trie is now stored by nodes } // StoreTrie stores the given trie in the StorageState and writes it to the database -func (s *StorageState) StoreTrie(ts *rtstorage.TrieState) error { +func (s *StorageState) StoreTrie(ts *rtstorage.TrieState, header *types.Header) error { s.lock.Lock() defer s.lock.Unlock() @@ -108,7 +117,24 @@ func (s *StorageState) StoreTrie(ts *rtstorage.TrieState) error { } s.tries[root] = ts.Trie() - logger.Debug("cached trie in storage state", "root", root) + if _, ok := s.pruner.(*pruner.FullNode); header == nil && ok { + return fmt.Errorf("block cannot be empty for Full node pruner") + } + + if header != nil { + insKeys, err := ts.GetInsertedNodeHashes() + if err != nil { + return fmt.Errorf("failed to get state trie inserted keys: block %s %w", header.Hash(), err) + } + + delKeys := ts.GetDeletedNodeHashes() + err = s.pruner.StoreJournalRecord(delKeys, insKeys, header.Hash(), header.Number.Int64()) + if err != nil { + return err + } + } + + logger.Trace("cached trie in storage state", "root", root) if err := s.tries[root].WriteDirty(s.db); err != nil { logger.Warn("failed to write trie to database", "root", root, "error", err) diff --git a/dot/state/storage_notify_test.go b/dot/state/storage_notify_test.go index f8e8e581b1..ff60221a8c 100644 --- a/dot/state/storage_notify_test.go +++ b/dot/state/storage_notify_test.go @@ -48,7 +48,7 @@ func TestStorageState_RegisterStorageObserver(t *testing.T) { defer ss.UnregisterStorageObserver(mockobs) ts.Set([]byte("mackcom"), []byte("wuz here")) - err = ss.StoreTrie(ts) + err = ss.StoreTrie(ts, nil) require.NoError(t, err) expectedResult := &SubscriptionResult{ @@ -93,7 +93,7 @@ func TestStorageState_RegisterStorageObserver_Multi(t *testing.T) { ts.Set(key1, value1) - err = ss.StoreTrie(ts) + err = ss.StoreTrie(ts, nil) require.NoError(t, err) time.Sleep(time.Millisecond * 10) @@ -143,7 +143,7 @@ func TestStorageState_RegisterStorageObserver_Multi_Filter(t *testing.T) { } ts.Set(key1, value1) - err = ss.StoreTrie(ts) + err = ss.StoreTrie(ts, nil) require.NoError(t, err) time.Sleep(time.Millisecond * 10) diff --git a/dot/state/storage_test.go b/dot/state/storage_test.go index dc72c5f017..ec13b11d16 100644 --- a/dot/state/storage_test.go +++ b/dot/state/storage_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/ChainSafe/gossamer/dot/state/pruner" "github.com/ChainSafe/gossamer/dot/types" runtime "github.com/ChainSafe/gossamer/lib/runtime/storage" "github.com/ChainSafe/gossamer/lib/trie" @@ -16,7 +17,7 @@ func newTestStorageState(t *testing.T) *StorageState { db := NewInMemoryDB(t) bs := newTestBlockState(t, testGenesisHeader) - s, err := NewStorageState(db, bs, trie.NewEmptyTrie()) + s, err := NewStorageState(db, bs, trie.NewEmptyTrie(), pruner.Config{}) require.NoError(t, err) return s } @@ -28,7 +29,7 @@ func TestStorage_StoreAndLoadTrie(t *testing.T) { root, err := ts.Root() require.NoError(t, err) - err = storage.StoreTrie(ts) + err = storage.StoreTrie(ts, nil) require.NoError(t, err) time.Sleep(time.Millisecond * 100) @@ -52,7 +53,7 @@ func TestStorage_GetStorageByBlockHash(t *testing.T) { root, err := ts.Root() require.NoError(t, err) - err = storage.StoreTrie(ts) + err = storage.StoreTrie(ts, nil) require.NoError(t, err) block := &types.Block{ @@ -79,7 +80,7 @@ func TestStorage_TrieState(t *testing.T) { root, err := ts.Root() require.NoError(t, err) - err = storage.StoreTrie(ts) + err = storage.StoreTrie(ts, nil) require.NoError(t, err) time.Sleep(time.Millisecond * 100) @@ -115,7 +116,7 @@ func TestStorage_LoadFromDB(t *testing.T) { require.NoError(t, err) // Write trie to disk. - err = storage.StoreTrie(ts) + err = storage.StoreTrie(ts, nil) require.NoError(t, err) // Clear trie from cache and fetch data from disk. @@ -154,7 +155,7 @@ func TestStorage_StoreTrie_Syncing(t *testing.T) { ts.Set(key, value) storage.SetSyncing(true) - err = storage.StoreTrie(ts) + err = storage.StoreTrie(ts, nil) require.NoError(t, err) require.Equal(t, 1, len(storage.tries)) } @@ -169,7 +170,7 @@ func TestStorage_StoreTrie_NotSyncing(t *testing.T) { ts.Set(key, value) storage.SetSyncing(false) - err = storage.StoreTrie(ts) + err = storage.StoreTrie(ts, nil) require.NoError(t, err) require.Equal(t, 2, len(storage.tries)) } diff --git a/dot/state/test_helpers.go b/dot/state/test_helpers.go index f75faa3e0a..02b2e70065 100644 --- a/dot/state/test_helpers.go +++ b/dot/state/test_helpers.go @@ -206,8 +206,8 @@ func AddBlocksToStateWithFixedBranches(t *testing.T, blockState *BlockState, dep } } -func generateBlockWithRandomTrie(t *testing.T, serv *Service, parent *common.Hash) (*types.Block, *runtime.TrieState) { - trieState, err := serv.Storage.TrieState(&trie.EmptyHash) +func generateBlockWithRandomTrie(t *testing.T, serv *Service, parent *common.Hash, bNum int64) (*types.Block, *runtime.TrieState) { + trieState, err := serv.Storage.TrieState(nil) require.NoError(t, err) // Generate random data for trie state. @@ -224,11 +224,10 @@ func generateBlockWithRandomTrie(t *testing.T, serv *Service, parent *common.Has parent = &bb } - // Generate a block with the above StateRoot. block := &types.Block{ Header: &types.Header{ ParentHash: *parent, - Number: big.NewInt(rand), + Number: big.NewInt(bNum), StateRoot: trieStateRoot, }, Body: types.NewBody([]byte{}), diff --git a/dot/sync/interface.go b/dot/sync/interface.go index 9258d1e263..f88bbd11cd 100644 --- a/dot/sync/interface.go +++ b/dot/sync/interface.go @@ -51,7 +51,7 @@ type BlockState interface { // StorageState is the interface for the storage state type StorageState interface { TrieState(root *common.Hash) (*rtstorage.TrieState, error) - StoreTrie(ts *rtstorage.TrieState) error + StoreTrie(ts *rtstorage.TrieState, header *types.Header) error LoadCodeHash(*common.Hash) (common.Hash, error) SetSyncing(bool) } diff --git a/dot/sync/syncer.go b/dot/sync/syncer.go index 47bac0e297..d67ef0327e 100644 --- a/dot/sync/syncer.go +++ b/dot/sync/syncer.go @@ -359,7 +359,7 @@ func (s *Service) handleBlock(block *types.Block) error { return fmt.Errorf("failed to execute block %d: %w", block.Header.Number, err) } - err = s.storageState.StoreTrie(ts) + err = s.storageState.StoreTrie(ts, block.Header) if err != nil { return err } diff --git a/dot/sync/test_helpers.go b/dot/sync/test_helpers.go index 8570ad86a6..ae2e43c80c 100644 --- a/dot/sync/test_helpers.go +++ b/dot/sync/test_helpers.go @@ -72,7 +72,12 @@ func NewTestSyncer(t *testing.T, usePolkadotGenesis bool) *Service { cfg := &Config{} testDatadirPath, _ := ioutil.TempDir("/tmp", "test-datadir-*") - stateSrvc := state.NewService(testDatadirPath, log.LvlInfo) + + scfg := state.Config{ + Path: testDatadirPath, + LogLevel: log.LvlInfo, + } + stateSrvc := state.NewService(scfg) stateSrvc.UseMemDB() gen, genTrie, genHeader := newTestGenesisWithTrieAndHeader(t, usePolkadotGenesis) diff --git a/go.mod b/go.mod index c27a74dda7..620a0385ed 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/ChainSafe/gossamer require ( - github.com/ChainSafe/chaindb v0.1.5-0.20210117220933-15e75f27268f + github.com/ChainSafe/chaindb v0.1.5-0.20210608140454-9606fe8c3985 github.com/ChainSafe/go-schnorrkel v0.0.0-20210222182958-bd440c890782 github.com/ChainSafe/log15 v1.0.0 github.com/OneOfOne/xxhash v1.2.5 @@ -12,7 +12,7 @@ require ( github.com/cosmos/go-bip39 v1.0.0 github.com/davidlazar/go-crypto v0.0.0-20190912175916-7055855a373f // indirect github.com/dgraph-io/badger/v2 v2.2007.2 - github.com/dgraph-io/ristretto v0.0.4-0.20210122082011-bb5d392ed82d + github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de github.com/disiqueira/gotree v1.0.0 github.com/docker/docker v1.13.1 github.com/elastic/gosigar v0.14.0 // indirect diff --git a/go.sum b/go.sum index 814e8c95c4..37101993d9 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9 h1:HD8gA2tkBy github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/ChainSafe/chaindb v0.1.5-0.20210117220933-15e75f27268f h1:qDmWdUIE1cgG19K/eVB9nkQMkldaGwcjU9U5OyUV11k= -github.com/ChainSafe/chaindb v0.1.5-0.20210117220933-15e75f27268f/go.mod h1:WBsCSLGM7+DvSYU6cFVUltahwU7Sw4cN3e8kiLdNFJM= +github.com/ChainSafe/chaindb v0.1.5-0.20210608140454-9606fe8c3985 h1:jyFsOjzTMoRwNvmW/OORZpffmItkoLvsbxB8koHX4ns= +github.com/ChainSafe/chaindb v0.1.5-0.20210608140454-9606fe8c3985/go.mod h1:P01m9E6xj6Mps1rtf7SurEX9oOcy1jYEyccZQAEw9+4= github.com/ChainSafe/go-schnorrkel v0.0.0-20210222182958-bd440c890782 h1:lwmjzta2Xu+3rPVY/VeNQj2xfNkyih4CwyRxYg3cpRQ= github.com/ChainSafe/go-schnorrkel v0.0.0-20210222182958-bd440c890782/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= github.com/ChainSafe/log15 v1.0.0 h1:vRDVtWtVwIH5uSCBvgTTZh6FA58UBJ6+QiiypaZfBf8= @@ -51,13 +51,11 @@ github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8Nz github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/cosiner/argv v0.1.0/go.mod h1:EusR6TucWKX+zFgtdUsKT2Cvg45K5rtpCcWz4hK06d8= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -77,9 +75,8 @@ github.com/dgraph-io/badger/v2 v2.2007.2 h1:EjjK0KqwaFMlPin1ajhP943VPENHJdEz1KLI github.com/dgraph-io/badger/v2 v2.2007.2/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE= github.com/dgraph-io/ristretto v0.0.2-0.20200115201040-8f368f2f2ab3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de h1:t0UHb5vdojIDUqktM6+xJAfScFBsVpXZmqC9dsgJmeA= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= -github.com/dgraph-io/ristretto v0.0.4-0.20210122082011-bb5d392ed82d h1:eQYOG6A4td1tht0NdJB9Ls6DsXRGb2Ft6X9REU/MbbE= -github.com/dgraph-io/ristretto v0.0.4-0.20210122082011-bb5d392ed82d/go.mod h1:tv2ec8nA7vRpSYX7/MbP52ihrUMXIHit54CQMq8npXQ= github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= @@ -106,7 +103,6 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= -github.com/go-delve/delve v1.5.0/go.mod h1:c6b3a1Gry6x8a4LGCe/CWzrocrfaHvkUxCj3k4bvSUQ= github.com/go-interpreter/wagon v0.6.0 h1:BBxDxjiJiHgw9EdkYXAWs8NHhwnazZ5P2EWBW5hFNWw= github.com/go-interpreter/wagon v0.6.0/go.mod h1:5+b/MBYkclRZngKF5s6qrgWxSLgE9F5dFdO1hAueZLc= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= @@ -152,7 +148,6 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-dap v0.2.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ= github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= github.com/google/gopacket v1.1.18 h1:lum7VRA9kdlvBi7/v2p7/zcbkduHaCH/SVVyurs7OpY= github.com/google/gopacket v1.1.18/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= @@ -269,7 +264,6 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d h1:68u9r4wEvL3gYg2jvAOgROwZ3H+Y3hIDk4tbbmIjcYQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -471,13 +465,11 @@ github.com/libp2p/go-yamux v1.4.0 h1:7nqe0T95T2CWh40IdJ/tp8RMor4ubc9/wYZpB2a/Hx0 github.com/libp2p/go-yamux v1.4.0/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mattn/go-colorable v0.0.0-20170327083344-ded68f7a9561/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -500,7 +492,6 @@ github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKU github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mmcloughlin/avo v0.0.0-20201105074841-5d2f697d268f/go.mod h1:6aKT4zZIrpGqB3RpFU14ByCSSyKY6LfJz4J/JJChHfI= github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.1/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= @@ -588,7 +579,6 @@ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYr github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/perlin-network/life v0.0.0-20191203030451-05c0e0f7eaea h1:okKoivlkNRRLqXraEtatHfEhW+D71QTwkaj+4n4M2Xc= github.com/perlin-network/life v0.0.0-20191203030451-05c0e0f7eaea/go.mod h1:3KEU5Dm8MAYWZqity880wOFJ9PhQjyKVZGwAEfc5Q4E= -github.com/peterh/liner v0.0.0-20170317030525-88609521dc4b/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= github.com/phuslu/iploc v1.0.20200807 h1:LIBm2Y9l5zmUvnJhQgMcLZ0iVwuG+5/L6AgbMwSOpE4= github.com/phuslu/iploc v1.0.20200807/go.mod h1:Q/0VX0txvbxekt4NhWIi3Q3eyZ139lHhnlzvDxyXhuc= github.com/pierrec/xxHash v0.1.5 h1:n/jBpwTHiER4xYvK3/CdPVnLDPchj8eTJFFLUb4QHBo= @@ -607,7 +597,6 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smola/gocompat v0.2.0/go.mod h1:1B0MlxbmoZNo3h8guHp8HztB3BSYR5itql9qtVc0ypY= github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a/go.mod h1:7AyxJNCJ7SBZ1MfVQCWD6Uqo2oubI2Eq2y2eqf+A5r0= github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU= @@ -617,10 +606,8 @@ github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0b github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.0-20170417170307-b6cb39589372/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v0.0.0-20170417173400-9e4c21054fa1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc= @@ -639,9 +626,8 @@ github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca h1:Ld/zXl5t4+D6 github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= +github.com/twitchyliquid64/golang-asm v0.0.0-20190126203739-365674df15fc h1:RTUQlKzoZZVG3umWNzOYeFecQLIh+dbxXvJp1zPQJTI= github.com/twitchyliquid64/golang-asm v0.0.0-20190126203739-365674df15fc/go.mod h1:NoCfSFWosfqMqmmD7hApkirIK9ozpHjxRnRxs1l413A= -github.com/twitchyliquid64/golang-asm v0.15.0 h1:WYZ15YKpC5xM8PwpBTDsAgemoLB/lyhRkzJSEw9eAew= -github.com/twitchyliquid64/golang-asm v0.15.0/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= @@ -661,7 +647,6 @@ github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 h1: github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7/go.mod h1:X2c0RVCI1eSUFI8eLcY3c0423ykwiUdxLJtkDvruhjI= github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -669,7 +654,6 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.starlark.net v0.0.0-20190702223751-32f345186213/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -685,8 +669,6 @@ go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= -golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= -golang.org/x/arch v0.0.0-20201008161808-52c3e6f60cff/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -716,8 +698,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNT golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -730,17 +710,15 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc h1:zK/HqS5bZxDptfPJNq8v7vJfXtkU7r9TLIoSr1bXaP4= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -753,7 +731,6 @@ golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190306220234-b354f8bf4d9e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190526052359-791d8a0f4d09/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -768,7 +745,6 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM= @@ -789,12 +765,9 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20201105001634-bc3cf281b174 h1:0rx0F4EjJNbxTuzWe0KjKcIzs+3VEb/Mrs/d1ciNz1c= -golang.org/x/tools v0.0.0-20201105001634-bc3cf281b174/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -853,4 +826,3 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/lib/babe/babe.go b/lib/babe/babe.go index 6618d0ae90..e8f2911e18 100644 --- a/lib/babe/babe.go +++ b/lib/babe/babe.go @@ -128,7 +128,6 @@ func NewService(cfg *ServiceConfig) (*Service, error) { dev: cfg.IsDev, } - var err error genCfg, err := babeService.rt.BabeConfiguration() if err != nil { return nil, err @@ -315,7 +314,7 @@ func (b *Service) safeSend(msg types.Block) error { defer b.Unlock() if b.IsStopped() { - return errors.New("Service has been stopped") + return errors.New("service has been stopped") } b.blockChan <- msg @@ -505,7 +504,7 @@ func (b *Service) handleSlot(slotNum uint64) error { return err } - err = b.storageState.StoreTrie(ts) + err = b.storageState.StoreTrie(ts, block.Header) if err != nil { logger.Error("failed to store trie in storage state", "error", err) } diff --git a/lib/babe/babe_test.go b/lib/babe/babe_test.go index 646a4b3633..600a6a9f90 100644 --- a/lib/babe/babe_test.go +++ b/lib/babe/babe_test.go @@ -109,7 +109,12 @@ func createTestService(t *testing.T, cfg *ServiceConfig) *Service { if cfg.BlockState == nil || cfg.StorageState == nil || cfg.EpochState == nil { testDatadirPath, err := ioutil.TempDir("/tmp", "test-datadir-*") //nolint require.NoError(t, err) - dbSrv := state.NewService(testDatadirPath, log.LvlInfo) + + config := state.Config{ + Path: testDatadirPath, + LogLevel: log.LvlInfo, + } + dbSrv := state.NewService(config) dbSrv.UseMemDB() if cfg.EpochLength > 0 { diff --git a/lib/babe/state.go b/lib/babe/state.go index c34b4cb13f..373ec91ccc 100644 --- a/lib/babe/state.go +++ b/lib/babe/state.go @@ -48,7 +48,7 @@ type BlockState interface { // StorageState interface for storage state methods type StorageState interface { TrieState(hash *common.Hash) (*rtstorage.TrieState, error) - StoreTrie(ts *rtstorage.TrieState) error + StoreTrie(ts *rtstorage.TrieState, header *types.Header) error } // TransactionState is the interface for transaction queue methods diff --git a/lib/babe/verify_test.go b/lib/babe/verify_test.go index 335208159b..50a03dddbe 100644 --- a/lib/babe/verify_test.go +++ b/lib/babe/verify_test.go @@ -36,7 +36,11 @@ func newTestVerificationManager(t *testing.T, genCfg *types.BabeConfiguration) * testDatadirPath, err := ioutil.TempDir("/tmp", "test-datadir-*") require.NoError(t, err) - dbSrv := state.NewService(testDatadirPath, log.LvlInfo) + config := state.Config{ + Path: testDatadirPath, + LogLevel: log.LvlInfo, + } + dbSrv := state.NewService(config) dbSrv.UseMemDB() if genCfg == nil { diff --git a/lib/common/db_keys.go b/lib/common/db_keys.go index 3d40451d17..15c628df66 100644 --- a/lib/common/db_keys.go +++ b/lib/common/db_keys.go @@ -33,6 +33,8 @@ var ( WorkingStorageHashKey = []byte("working_storage_hash") //NodeNameKey is the storage key to store de current node name and avoid create a new name every initialization NodeNameKey = []byte("node_name") + // PruningKey is the storage key to store the current pruning configuration. + PruningKey = []byte("prune") //CodeSubstitutedBlock is the storage key to store block hash of substituted (if there is currently code substituted) CodeSubstitutedBlock = []byte("code_substituted_block") ) diff --git a/lib/runtime/storage/trie.go b/lib/runtime/storage/trie.go index 34a3df09d4..7878fde7a0 100644 --- a/lib/runtime/storage/trie.go +++ b/lib/runtime/storage/trie.go @@ -269,3 +269,17 @@ func (s *TrieState) LoadCodeHash() (common.Hash, error) { code := s.LoadCode() return common.Blake2bHash(code) } + +// GetInsertedNodeHashes returns the hash of nodes inserted into state trie since last block produced +func (s *TrieState) GetInsertedNodeHashes() ([]common.Hash, error) { + s.lock.RLock() + defer s.lock.RUnlock() + return s.t.GetInsertedNodeHashes() +} + +// GetDeletedNodeHashes returns the hash of nodes that are deleted from state trie since last block produced +func (s *TrieState) GetDeletedNodeHashes() []common.Hash { + s.lock.RLock() + defer s.lock.RUnlock() + return s.t.GetDeletedNodeHash() +} diff --git a/lib/trie/database.go b/lib/trie/database.go index 447c53171e..8218bd1b38 100644 --- a/lib/trie/database.go +++ b/lib/trie/database.go @@ -287,3 +287,53 @@ func (t *Trie) writeDirty(db chaindb.Batch, curr node) error { curr.setDirty(false) return nil } + +// GetInsertedNodeHashes returns the hash of nodes that are inserted into state trie since last snapshot is called +// Since inserted nodes are newly created we need to compute their hash values. +func (t *Trie) GetInsertedNodeHashes() ([]common.Hash, error) { + return t.getInsertedNodeHashes(t.root) +} + +func (t *Trie) getInsertedNodeHashes(curr node) ([]common.Hash, error) { + var nodeHashes []common.Hash + if curr == nil || !curr.isDirty() { + return nil, nil + } + + enc, hash, err := curr.encodeAndHash() + if err != nil { + return nil, err + } + + if curr == t.root && len(enc) < 32 { + h, err := common.Blake2bHash(enc) //nolint + if err != nil { + return nil, err + } + + hash = h[:] + } + + nodeHash := common.BytesToHash(hash) + nodeHashes = append(nodeHashes, nodeHash) + + if c, ok := curr.(*branch); ok { + for _, child := range c.children { + if child == nil { + continue + } + nodes, err := t.getInsertedNodeHashes(child) + if err != nil { + return nil, err + } + nodeHashes = append(nodeHashes, nodes...) + } + } + + return nodeHashes, nil +} + +// GetDeletedNodeHash returns the hash of nodes that are deleted from state trie since last snapshot is called +func (t *Trie) GetDeletedNodeHash() []common.Hash { + return t.deletedKeys +} diff --git a/lib/trie/node.go b/lib/trie/node.go index f011cdf6cc..d3ef64954b 100644 --- a/lib/trie/node.go +++ b/lib/trie/node.go @@ -62,6 +62,8 @@ type node interface { setEncodingAndHash([]byte, []byte) getHash() []byte getGeneration() uint64 + setGeneration(uint64) + copy() node } type ( @@ -86,7 +88,15 @@ type ( } ) -func (b *branch) copy() *branch { +func (b *branch) setGeneration(generation uint64) { + b.generation = generation +} + +func (l *leaf) setGeneration(generation uint64) { + l.generation = generation +} + +func (b *branch) copy() node { cpy := &branch{ key: make([]byte, len(b.key)), children: b.children, @@ -109,7 +119,7 @@ func (b *branch) copy() *branch { return cpy } -func (l *leaf) copy() *leaf { +func (l *leaf) copy() node { cpy := &leaf{ key: make([]byte, len(l.key)), value: make([]byte, len(l.value)), diff --git a/lib/trie/trie.go b/lib/trie/trie.go index 8f3dc3addd..e163234da8 100644 --- a/lib/trie/trie.go +++ b/lib/trie/trie.go @@ -29,9 +29,10 @@ var EmptyHash, _ = NewEmptyTrie().Hash() // The zero value is an empty trie with no database. // Use NewTrie to create a trie that sits on top of a database. type Trie struct { - generation uint64 - root node - childTries map[common.Hash]*Trie // Used to store the child tries. + generation uint64 + root node + childTries map[common.Hash]*Trie // Used to store the child tries. + deletedKeys []common.Hash } // NewEmptyTrie creates a trie with a nil root @@ -42,41 +43,45 @@ func NewEmptyTrie() *Trie { // NewTrie creates a trie with an existing root node func NewTrie(root node) *Trie { return &Trie{ - root: root, - childTries: make(map[common.Hash]*Trie), - generation: 0, // Initially zero but increases after every snapshot. + root: root, + childTries: make(map[common.Hash]*Trie), + generation: 0, // Initially zero but increases after every snapshot. + deletedKeys: make([]common.Hash, 0), } } // Snapshot created a copy of the trie. func (t *Trie) Snapshot() *Trie { newTrie := &Trie{ - generation: t.generation + 1, - root: t.root, - childTries: t.childTries, + generation: t.generation + 1, + root: t.root, + childTries: t.childTries, + deletedKeys: make([]common.Hash, 0), } + return newTrie } -func (t *Trie) maybeUpdateLeafGeneration(n *leaf) *leaf { - // Make a copy if the generation is updated. - if n.getGeneration() < t.generation { - // Insert a new leaf node in the current generation. - newLeaf := n.copy() - newLeaf.generation = t.generation - return newLeaf +func (t *Trie) maybeUpdateGeneration(n node) node { + if n == nil { + return nil } - return n -} -func (t *Trie) maybeUpdateBranchGeneration(n *branch) *branch { // Make a copy if the generation is updated. if n.getGeneration() < t.generation { - // Insert a new branch node in the current generation. - newBranch := n.copy() - newBranch.generation = t.generation - return newBranch + // Insert a new node in the current generation. + newNode := n.copy() + newNode.setGeneration(t.generation) + + // Hash of old nodes should already be computed since it belongs to older generation. + oldNodeHash := n.getHash() + if len(oldNodeHash) > 0 { + hash := common.BytesToHash(oldNodeHash) + t.deletedKeys = append(t.deletedKeys, hash) + } + return newNode } + return n } @@ -245,14 +250,13 @@ func (t *Trie) tryPut(key, value []byte) { // TryPut attempts to insert a key with value into the trie func (t *Trie) insert(parent node, key []byte, value node) node { - switch p := parent.(type) { + switch p := t.maybeUpdateGeneration(parent).(type) { case *branch: - nn := t.maybeUpdateBranchGeneration(p) - n := t.updateBranch(nn, key, value) + n := t.updateBranch(p, key, value) - if nn != nil && n != nil && n.isDirty() { + if p != nil && n != nil && n.isDirty() { // TODO: set all `Copy` nodes as dirty? - nn.setDirty(true) + p.setDirty(true) } return n case nil: @@ -266,22 +270,20 @@ func (t *Trie) insert(parent node, key []byte, value node) node { return v } case *leaf: - nn := t.maybeUpdateLeafGeneration(p) - // if a value already exists in the trie at this key, overwrite it with the new value // if the values are the same, don't mark node dirty - if nn.value != nil && bytes.Equal(nn.key, key) { - if !bytes.Equal(value.(*leaf).value, nn.value) { - nn.value = value.(*leaf).value - nn.dirty = true + if p.value != nil && bytes.Equal(p.key, key) { + if !bytes.Equal(value.(*leaf).value, p.value) { + p.value = value.(*leaf).value + p.dirty = true } - return nn + return p } - length := lenCommonPrefix(key, nn.key) + length := lenCommonPrefix(key, p.key) // need to convert this leaf into a branch br := &branch{key: key[:length], dirty: true, generation: t.generation} - parentKey := nn.key + parentKey := p.key // value goes at this branch if len(key) == length { @@ -290,9 +292,9 @@ func (t *Trie) insert(parent node, key []byte, value node) node { // if we are not replacing previous leaf, then add it as a child to the new branch if len(parentKey) > len(key) { - nn.key = nn.key[length+1:] - br.children[parentKey[length]] = nn - nn.setDirty(true) + p.key = p.key[length+1:] + br.children[parentKey[length]] = p + p.setDirty(true) } return br @@ -300,16 +302,16 @@ func (t *Trie) insert(parent node, key []byte, value node) node { value.setKey(key[length+1:]) - if length == len(nn.key) { + if length == len(p.key) { // if leaf's key is covered by this branch, then make the leaf's // value the value at this branch - br.value = nn.value + br.value = p.value br.children[key[length]] = value } else { // otherwise, make the leaf a child of the branch and update its partial key - nn.key = nn.key[length+1:] - nn.setDirty(true) - br.children[parentKey[length]] = nn + p.key = p.key[length+1:] + p.setDirty(true) + br.children[parentKey[length]] = p br.children[key[length]] = value } @@ -393,7 +395,7 @@ func (t *Trie) LoadFromMap(data map[string]string) error { // GetKeysWithPrefix returns all keys in the trie that have the given prefix func (t *Trie) GetKeysWithPrefix(prefix []byte) [][]byte { - p := []byte{} + var p []byte if len(prefix) != 0 { p = keyToNibbles(prefix) if p[len(p)-1] == 0 { @@ -514,7 +516,8 @@ func (t *Trie) ClearPrefix(prefix []byte) { t.root, _ = t.clearPrefix(t.root, p) } -func (t *Trie) clearPrefix(curr node, prefix []byte) (node, bool) { +func (t *Trie) clearPrefix(cn node, prefix []byte) (node, bool) { + curr := t.maybeUpdateGeneration(cn) switch c := curr.(type) { case *branch: length := lenCommonPrefix(c.key, prefix) @@ -525,14 +528,13 @@ func (t *Trie) clearPrefix(curr node, prefix []byte) (node, bool) { } // Store the current node and return it, if the trie is not updated. - nn := t.maybeUpdateBranchGeneration(c) - if len(prefix) == len(nn.key)+1 && length == len(prefix)-1 { + if len(prefix) == len(c.key)+1 && length == len(prefix)-1 { // found prefix at child index, delete child - i := prefix[len(nn.key)] - nn.children[i] = nil - nn.setDirty(true) - curr = handleDeletion(nn, prefix) + i := prefix[len(c.key)] + c.children[i] = nil + c.setDirty(true) + curr = handleDeletion(c, prefix) return curr, true } @@ -542,12 +544,12 @@ func (t *Trie) clearPrefix(curr node, prefix []byte) (node, bool) { } var wasUpdated bool - i := prefix[len(nn.key)] + i := prefix[len(c.key)] - nn.children[i], wasUpdated = t.clearPrefix(nn.children[i], prefix[len(nn.key)+1:]) + c.children[i], wasUpdated = t.clearPrefix(c.children[i], prefix[len(c.key)+1:]) if wasUpdated { - nn.setDirty(true) - curr = handleDeletion(nn, prefix) + c.setDirty(true) + curr = handleDeletion(c, prefix) } return curr, curr.isDirty() @@ -571,28 +573,27 @@ func (t *Trie) Delete(key []byte) { } func (t *Trie) delete(parent node, key []byte) (node, bool) { - switch p := parent.(type) { + // Store the current node and return it, if the trie is not updated. + switch p := t.maybeUpdateGeneration(parent).(type) { case *branch: - // Store the current node and return it, if the trie is not updated. - nn := t.maybeUpdateBranchGeneration(p) - length := lenCommonPrefix(nn.key, key) - if bytes.Equal(nn.key, key) || len(key) == 0 { + length := lenCommonPrefix(p.key, key) + if bytes.Equal(p.key, key) || len(key) == 0 { // found the value at this node - nn.value = nil - nn.setDirty(true) - return handleDeletion(nn, key), true + p.value = nil + p.setDirty(true) + return handleDeletion(p, key), true } - n, del := t.delete(nn.children[key[length]], key[length+1:]) + n, del := t.delete(p.children[key[length]], key[length+1:]) if !del { // If nothing was deleted then don't copy the path. return p, false } - nn.children[key[length]] = n - nn.setDirty(true) - n = handleDeletion(nn, key) + p.children[key[length]] = n + p.setDirty(true) + n = handleDeletion(p, key) return n, true case *leaf: if bytes.Equal(key, p.key) || len(key) == 0 { @@ -651,6 +652,7 @@ func handleDeletion(p *branch, key []byte) node { // do nothing } n.setDirty(true) + } return n } diff --git a/lib/trie/trie_test.go b/lib/trie/trie_test.go index 4814270bfd..17838f9094 100644 --- a/lib/trie/trie_test.go +++ b/lib/trie/trie_test.go @@ -29,6 +29,7 @@ import ( "strings" "testing" + "github.com/ChainSafe/chaindb" "github.com/ChainSafe/gossamer/lib/common" "github.com/stretchr/testify/require" @@ -469,6 +470,74 @@ func TestDeleteOddKeyLengths(t *testing.T) { runTests(t, trie, tests) } +func TestTrieDiff(t *testing.T) { + testDataDirPath, _ := ioutil.TempDir(t.TempDir(), "test-badger-datadir") + defer os.RemoveAll(testDataDirPath) + + cfg := &chaindb.Config{ + DataDir: testDataDirPath, + InMemory: false, + } + + db, err := chaindb.NewBadgerDB(cfg) + require.NoError(t, err) + + storageDB := chaindb.NewTable(db, "storage") + + defer db.Close() + trie := NewEmptyTrie() + + var testKey = []byte("testKey") + + tests := []Test{ + {key: testKey, value: testKey}, + {key: []byte("testKey1"), value: []byte("testKey1")}, + {key: []byte("testKey2"), value: []byte("testKey2")}, + } + + for _, test := range tests { + trie.Put(test.key, test.value) + } + + newTrie := trie.Snapshot() + err = trie.Store(storageDB) + require.NoError(t, err) + + tests = []Test{ + {key: testKey, value: []byte("newTestKey2")}, + {key: []byte("testKey2"), value: []byte("newKey")}, + {key: []byte("testKey3"), value: []byte("testKey3")}, + {key: []byte("testKey4"), value: []byte("testKey2")}, + {key: []byte("testKey5"), value: []byte("testKey5")}, + } + + for _, test := range tests { + newTrie.Put(test.key, test.value) + } + deletedKeys := newTrie.deletedKeys + require.Len(t, deletedKeys, 3) + + err = newTrie.WriteDirty(storageDB) + require.NoError(t, err) + + for _, key := range deletedKeys { + err = storageDB.Del(key.ToBytes()) + require.NoError(t, err) + } + + dbTrie := NewEmptyTrie() + err = dbTrie.Load(storageDB, common.BytesToHash(newTrie.root.getHash())) + require.NoError(t, err) + + enc, err := dbTrie.Encode() + require.NoError(t, err) + + newEnc, err := newTrie.Encode() + require.NoError(t, err) + + require.Equal(t, enc, newEnc) +} + func TestDelete(t *testing.T) { trie := NewEmptyTrie() diff --git a/tests/utils/gossamer_utils.go b/tests/utils/gossamer_utils.go index e8c170b774..69149681b3 100644 --- a/tests/utils/gossamer_utils.go +++ b/tests/utils/gossamer_utils.go @@ -417,10 +417,12 @@ func GenerateGenesisSixAuth() { func generateDefaultConfig() *ctoml.Config { return &ctoml.Config{ Global: ctoml.GlobalConfig{ - Name: "Gossamer", - ID: "gssmr", - LogLvl: "crit", - MetricsPort: 9876, + Name: "Gossamer", + ID: "gssmr", + LogLvl: "crit", + MetricsPort: 9876, + RetainBlocks: 256, + Pruning: "archive", }, Log: ctoml.LogConfig{ CoreLvl: "info",