diff --git a/cmd/gossamer/config.go b/cmd/gossamer/config.go index 00136d6e59..2d29d7a018 100644 --- a/cmd/gossamer/config.go +++ b/cmd/gossamer/config.go @@ -43,6 +43,10 @@ var ( defaultGssmrConfigPath = "./chain/gssmr/config.toml" defaultKusamaConfigPath = "./chain/ksmcc/config.toml" defaultPolkadotConfigPath = "./chain/polkadot/config.toml" + + gossamerName = "gssmr" + kusamaName = "ksmcc" + polkadotName = "polkadot" ) // loadConfigFile loads a default config file if --chain is specified, a specific @@ -68,39 +72,49 @@ func loadConfigFile(ctx *cli.Context, cfg *ctoml.Config) (err error) { return err } -// createDotConfig creates a new dot configuration from the provided flag values -func createDotConfig(ctx *cli.Context) (cfg *dot.Config, err error) { +func setupConfigFromChain(ctx *cli.Context) (*ctoml.Config, *dot.Config, error) { tomlCfg := &ctoml.Config{} - cfg = DefaultCfg() + cfg := DefaultCfg() - err = loadConfigFile(ctx, tomlCfg) + err := loadConfigFile(ctx, tomlCfg) if err != nil { logger.Error("failed to load toml configuration", "error", err) - return nil, err + return nil, nil, err } // check --chain flag and load configuration from defaults.go if id := ctx.GlobalString(ChainFlag.Name); id != "" { switch id { - case "gssmr": + case gossamerName: logger.Info("loading toml configuration...", "config path", defaultGssmrConfigPath) tomlCfg = &ctoml.Config{} err = loadConfig(tomlCfg, defaultGssmrConfigPath) - case "ksmcc": + case kusamaName: logger.Info("loading toml configuration...", "config path", defaultKusamaConfigPath) tomlCfg = &ctoml.Config{} cfg = dot.KsmccConfig() err = loadConfig(tomlCfg, defaultKusamaConfigPath) - case "polkadot": + case polkadotName: logger.Info("loading toml configuration...", "config path", defaultPolkadotConfigPath) tomlCfg = &ctoml.Config{} cfg = dot.PolkadotConfig() err = loadConfig(tomlCfg, defaultPolkadotConfigPath) default: - return nil, fmt.Errorf("unknown chain id provided: %s", id) + return nil, nil, fmt.Errorf("unknown chain id provided: %s", id) } } + if err != nil { + logger.Error("failed to set chain configuration", "error", err) + return nil, nil, err + } + + return tomlCfg, cfg, nil +} + +// createDotConfig creates a new dot configuration from the provided flag values +func createDotConfig(ctx *cli.Context) (*dot.Config, error) { + tomlCfg, cfg, err := setupConfigFromChain(ctx) if err != nil { logger.Error("failed to set chain configuration", "error", err) return nil, err @@ -137,31 +151,7 @@ func createDotConfig(ctx *cli.Context) (cfg *dot.Config, err error) { // createInitConfig creates the configuration required to initialize a dot node func createInitConfig(ctx *cli.Context) (*dot.Config, error) { - tomlCfg := &ctoml.Config{} - cfg := DefaultCfg() - - err := loadConfigFile(ctx, tomlCfg) - if err != nil { - logger.Error("failed to load toml configuration", "error", err) - return nil, err - } - - // check --chain flag and load configuration from defaults.go - if id := ctx.GlobalString(ChainFlag.Name); id != "" { - switch id { - case "gssmr": - tomlCfg = &ctoml.Config{} - err = loadConfig(tomlCfg, defaultGssmrConfigPath) - case "ksmcc": - tomlCfg = &ctoml.Config{} - err = loadConfig(tomlCfg, defaultKusamaConfigPath) - case "polkadot": - tomlCfg = &ctoml.Config{} - err = loadConfig(tomlCfg, defaultPolkadotConfigPath) - default: - return nil, fmt.Errorf("unknown chain id provided: %s", id) - } - } + tomlCfg, cfg, err := setupConfigFromChain(ctx) if err != nil { logger.Error("failed to set chain configuration", "error", err) return nil, err @@ -196,6 +186,18 @@ func createInitConfig(ctx *cli.Context) (*dot.Config, error) { return cfg, nil } +func createImportStateConfig(ctx *cli.Context) (*dot.Config, error) { + tomlCfg, cfg, err := setupConfigFromChain(ctx) + if err != nil { + logger.Error("failed to set chain configuration", "error", err) + return nil, err + } + + // set global configuration values + setDotGlobalConfig(ctx, tomlCfg, &cfg.Global) + return cfg, nil +} + func createBuildSpecConfig(ctx *cli.Context) (*dot.Config, error) { var tomlCfg *ctoml.Config cfg := &dot.Config{} diff --git a/cmd/gossamer/flags.go b/cmd/gossamer/flags.go index a02c681103..40cef5dfaf 100644 --- a/cmd/gossamer/flags.go +++ b/cmd/gossamer/flags.go @@ -109,6 +109,22 @@ var ( } ) +// ImportState-only flags +var ( + StateFlag = cli.StringFlag{ + Name: "state", + Usage: "Path to JSON file consisting of key-value pairs", + } + HeaderFlag = cli.StringFlag{ + Name: "header", + Usage: "Path to JSON file of block header corresponding to the given state", + } + FirstSlotFlag = cli.IntFlag{ + Name: "first-slot", + Usage: "The first BABE slot of the network", + } +) + // BuildSpec-only flags var ( RawFlag = cli.BoolFlag{ @@ -315,6 +331,15 @@ var ( Sr25519Flag, Secp256k1Flag, }, GlobalFlags...) + + ImportStateFlags = []cli.Flag{ + BasePathFlag, + ChainFlag, + ConfigFlag, + StateFlag, + HeaderFlag, + FirstSlotFlag, + } ) // FixFlagOrder allow us to use various flag order formats (ie, `gossamer init diff --git a/cmd/gossamer/main.go b/cmd/gossamer/main.go index 83f8967e51..0ef15bc6fe 100644 --- a/cmd/gossamer/main.go +++ b/cmd/gossamer/main.go @@ -17,6 +17,7 @@ package main import ( + "errors" "fmt" "io/ioutil" "os" @@ -93,6 +94,18 @@ var ( Description: "The convert-wasm command converts a .wasm file to a hex string to be used in a genesis file.\n" + "\tUsage: gossamer convert-wasm runtime.wasm\n", } + + importStateCommand = cli.Command{ + Action: FixFlagOrder(importStateAction), + Name: "import-state", + Usage: "Import state from a JSON file and set it as the chain head state", + ArgsUsage: "", + Flags: ImportStateFlags, + Category: "IMPORT-STATE", + Description: "The import-state command allows a JSON file containing a given state in the form of key-value pairs to be imported.\n" + + "Input can be generated by using the RPC function state_getPairs.\n" + + "\tUsage: gossamer import-state --state state.json --header header.json --first-slot \n", + } ) // init initializes the cli application @@ -109,6 +122,7 @@ func init() { accountCommand, buildSpecCommand, wasmToHexCommand, + importStateCommand, } app.Flags = RootFlags } @@ -121,6 +135,34 @@ func main() { } } +func importStateAction(ctx *cli.Context) error { + var ( + stateFP, headerFP string + firstSlot int + ) + + if stateFP = ctx.String(StateFlag.Name); stateFP == "" { + return errors.New("must provide argument to --state") + } + + if headerFP = ctx.String(HeaderFlag.Name); headerFP == "" { + return errors.New("must provide argument to --header") + } + + if firstSlot = ctx.Int(FirstSlotFlag.Name); firstSlot == 0 { + return errors.New("must provide argument to --first-slot") + } + + cfg, err := createImportStateConfig(ctx) + if err != nil { + logger.Error("failed to create node configuration", "error", err) + return err + } + cfg.Global.BasePath = utils.ExpandDir(cfg.Global.BasePath) + + return dot.ImportState(cfg.Global.BasePath, stateFP, headerFP, uint64(firstSlot)) +} + // wasmToHexAction converts a .wasm file to a hex string and outputs it to stdout func wasmToHexAction(ctx *cli.Context) error { arguments := ctx.Args() diff --git a/dot/import.go b/dot/import.go new file mode 100644 index 0000000000..25d0337b71 --- /dev/null +++ b/dot/import.go @@ -0,0 +1,150 @@ +// Copyright 2019 ChainSafe Systems (ON) Corp. +// This file is part of gossamer. +// +// The gossamer library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The gossamer library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the gossamer library. If not, see . + +package dot + +import ( + "bytes" + "encoding/json" + "errors" + "io/ioutil" + "math/big" + "path/filepath" + + "github.com/ChainSafe/gossamer/dot/state" + "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/trie" + + log "github.com/ChainSafe/log15" +) + +// ImportState imports the state in the given files to the database with the given path. +func ImportState(basepath, stateFP, headerFP string, firstSlot uint64) error { + tr, err := newTrieFromPairs(stateFP) + if err != nil { + return err + } + + header, err := newHeaderFromFile(headerFP) + if err != nil { + return err + } + + log.Info("ImportState", "header", header) + + srv := state.NewService(basepath, log.LvlInfo) + return srv.Import(header, tr, firstSlot) +} + +func newTrieFromPairs(filename string) (*trie.Trie, error) { + data, err := ioutil.ReadFile(filepath.Clean(filename)) + if err != nil { + return nil, err + } + + pairs := make([]interface{}, 0) + err = json.Unmarshal(data, &pairs) + if err != nil { + return nil, err + } + + entries := make(map[string]string) + for _, pair := range pairs { + pairArr := pair.([]interface{}) + if len(pairArr) != 2 { + return nil, errors.New("state file contains invalid pair") + } + entries[pairArr[0].(string)] = pairArr[1].(string) + } + + tr := trie.NewEmptyTrie() + err = tr.LoadFromMap(entries) + if err != nil { + return nil, err + } + + return tr, nil +} + +func newHeaderFromFile(filename string) (*types.Header, error) { + data, err := ioutil.ReadFile(filepath.Clean(filename)) + if err != nil { + return nil, err + } + + jsonHeader := make(map[string]interface{}) + err = json.Unmarshal(data, &jsonHeader) + if err != nil { + return nil, err + } + + hexNum, ok := jsonHeader["number"].(string) + if !ok { + return nil, errors.New("invalid number field in header JSON") + } + + numBytes := common.MustHexToBytes(hexNum) + num := big.NewInt(0).SetBytes(numBytes) + + parentHashStr, ok := jsonHeader["parentHash"].(string) + if !ok { + return nil, errors.New("invalid parentHash field in header JSON") + } + parentHash := common.MustHexToHash(parentHashStr) + + stateRootStr, ok := jsonHeader["stateRoot"].(string) + if !ok { + return nil, errors.New("invalid stateRoot field in header JSON") + } + stateRoot := common.MustHexToHash(stateRootStr) + + extrinsicsRootStr, ok := jsonHeader["extrinsicsRoot"].(string) + if !ok { + return nil, errors.New("invalid extrinsicsRoot field in header JSON") + } + extrinsicsRoot := common.MustHexToHash(extrinsicsRootStr) + + digestRaw, ok := jsonHeader["digest"].(map[string]interface{}) + if !ok { + return nil, errors.New("invalid digest field in header JSON") + } + logs := digestRaw["logs"].([]interface{}) + + digest := types.Digest{} + + for _, log := range logs { + digestBytes := common.MustHexToBytes(log.(string)) + r := &bytes.Buffer{} + _, _ = r.Write(digestBytes) + digestItem, err := types.DecodeDigestItem(r) + if err != nil { + return nil, err + } + + digest = append(digest, digestItem) + } + + header := &types.Header{ + ParentHash: parentHash, + Number: num, + StateRoot: stateRoot, + ExtrinsicsRoot: extrinsicsRoot, + Digest: digest, + } + + return header, nil +} diff --git a/dot/import_test.go b/dot/import_test.go new file mode 100644 index 0000000000..f9c50b8a8c --- /dev/null +++ b/dot/import_test.go @@ -0,0 +1,114 @@ +// Copyright 2019 ChainSafe Systems (ON) Corp. +// This file is part of gossamer. +// +// The gossamer library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The gossamer library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the gossamer library. If not, see . + +package dot + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "math/big" + "testing" + + "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/common" + + "github.com/stretchr/testify/require" +) + +func setupStateFile(t *testing.T) string { + filename := "../lib/runtime/test_data/block1482002.out" + + data, err := ioutil.ReadFile(filename) + require.NoError(t, err) + + rpcPairs := make(map[string]interface{}) + err = json.Unmarshal(data, &rpcPairs) + require.NoError(t, err) + pairs := rpcPairs["result"].([]interface{}) + + bz, err := json.Marshal(pairs) + require.NoError(t, err) + + fp := "./test_data/state.json" + err = ioutil.WriteFile(fp, bz, 0777) + require.NoError(t, err) + + return fp +} + +func setupHeaderFile(t *testing.T) string { + headerStr := "{\"digest\":{\"logs\":[\"0x0642414245b501013c0000009659bd0f0000000070edad1c9064fff78cb18435223d8adaf5ea04c24b1a8766e3dc01eb03cc6a0c11b79793d4e31cc0990838229c44fed1669a7c7c79e1e6d0a96374d6496728069d1ef739e290497a0e3b728fa88fcbdd3a5504e0efde0242e7a806dd4fa9260c\",\"0x054241424501019e7f28dddcf27c1e6b328d5694c368d5b2ec5dbe0e412ae1c98f88d53be4d8502fac571f3f19c9caaf281a673319241e0c5095a683ad34316204088a36a4bd86\"]},\"extrinsicsRoot\":\"0xda26dc8c1455f8f81cae12e4fc59e23ce961b2c837f6d3f664283af906d344e0\",\"number\":\"0x169d12\",\"parentHash\":\"0x3b45c9c22dcece75a30acc9c2968cb311e6b0557350f83b430f47559db786975\",\"stateRoot\":\"0x09f9ca28df0560c2291aa16b56e15e07d1e1927088f51356d522722aa90ca7cb\"}" + fp := "./test_data/header.json" + err := ioutil.WriteFile(fp, []byte(headerStr), 0777) + require.NoError(t, err) + return fp +} + +func TestNewTrieFromPairs(t *testing.T) { + fp := setupStateFile(t) + tr, err := newTrieFromPairs(fp) + require.NoError(t, err) + + expectedRoot := common.MustHexToHash("0x09f9ca28df0560c2291aa16b56e15e07d1e1927088f51356d522722aa90ca7cb") + require.Equal(t, expectedRoot, tr.MustHash()) +} + +func TestNewHeaderFromFile(t *testing.T) { + fp := setupHeaderFile(t) + header, err := newHeaderFromFile(fp) + require.NoError(t, err) + + digestBytes := common.MustHexToBytes("0x080642414245b501013c0000009659bd0f0000000070edad1c9064fff78cb18435223d8adaf5ea04c24b1a8766e3dc01eb03cc6a0c11b79793d4e31cc0990838229c44fed1669a7c7c79e1e6d0a96374d6496728069d1ef739e290497a0e3b728fa88fcbdd3a5504e0efde0242e7a806dd4fa9260c054241424501019e7f28dddcf27c1e6b328d5694c368d5b2ec5dbe0e412ae1c98f88d53be4d8502fac571f3f19c9caaf281a673319241e0c5095a683ad34316204088a36a4bd86") + r := &bytes.Buffer{} + _, _ = r.Write(digestBytes) + digest, err := types.DecodeDigest(r) + require.NoError(t, err) + require.Equal(t, 2, len(digest)) + + expected := &types.Header{ + ParentHash: common.MustHexToHash("0x3b45c9c22dcece75a30acc9c2968cb311e6b0557350f83b430f47559db786975"), + Number: big.NewInt(1482002), + StateRoot: common.MustHexToHash("0x09f9ca28df0560c2291aa16b56e15e07d1e1927088f51356d522722aa90ca7cb"), + ExtrinsicsRoot: common.MustHexToHash("0xda26dc8c1455f8f81cae12e4fc59e23ce961b2c837f6d3f664283af906d344e0"), + Digest: digest, + } + + require.Equal(t, expected, header) +} + +func TestImportState(t *testing.T) { + basepath, err := ioutil.TempDir("", "gossamer-test-*") + require.NoError(t, err) + + cfg := NewTestConfig(t) + require.NotNil(t, cfg) + + genFile := NewTestGenesisRawFile(t, cfg) + require.NotNil(t, genFile) + cfg.Init.GenesisRaw = genFile.Name() + + cfg.Global.BasePath = basepath + err = InitNode(cfg) + require.NoError(t, err) + + stateFP := setupStateFile(t) + headerFP := setupHeaderFile(t) + + firstSlot := uint64(262493679) + err = ImportState(basepath, stateFP, headerFP, firstSlot) + require.NoError(t, err) +} diff --git a/dot/state/epoch.go b/dot/state/epoch.go index 2d2d03d8d4..9afe2cb996 100644 --- a/dot/state/epoch.go +++ b/dot/state/epoch.go @@ -34,6 +34,7 @@ var ( firstSlotKey = []byte("firstslot") epochDataPrefix = []byte("epochinfo") configDataPrefix = []byte("configinfo") + skipToKey = []byte("skipto") ) func epochDataKey(epoch uint64) []byte { @@ -54,6 +55,7 @@ type EpochState struct { db chaindb.Database epochLength uint64 // measured in slots firstSlot uint64 + skipToEpoch uint64 } // NewEpochStateFromGenesis returns a new EpochState given information for the first epoch, fetched from the runtime @@ -107,6 +109,10 @@ func NewEpochStateFromGenesis(db chaindb.Database, genesisConfig *types.BabeConf return nil, err } + if err := storeSkipToEpoch(db, 0); err != nil { + return nil, err + } + return s, nil } @@ -122,11 +128,17 @@ func NewEpochState(db chaindb.Database) (*EpochState, error) { return nil, err } + skipToEpoch, err := loadSkipToEpoch(db) + if err != nil { + return nil, err + } + return &EpochState{ baseDB: db, db: chaindb.NewTable(db, epochPrefix), epochLength: epochLength, firstSlot: firstSlot, + skipToEpoch: skipToEpoch, }, nil } @@ -291,3 +303,33 @@ func (s *EpochState) SetFirstSlot(slot uint64) error { s.firstSlot = slot return storeFirstSlot(s.baseDB, slot) } + +func storeSkipToEpoch(db chaindb.Database, epoch uint64) error { + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, epoch) + return db.Put(skipToKey, buf) +} + +func loadSkipToEpoch(db chaindb.Database) (uint64, error) { + data, err := db.Get(skipToKey) + if err != nil { + return 0, err + } + + return binary.LittleEndian.Uint64(data), nil +} + +// SkipVerify returns whether verification for the given header should be skipped or not. +// Only used in the case of imported state. +func (s *EpochState) SkipVerify(header *types.Header) (bool, error) { + epoch, err := s.GetEpochForBlock(header) + if err != nil { + return false, err + } + + if epoch <= s.skipToEpoch { + return true, nil + } + + return false, nil +} diff --git a/dot/state/service.go b/dot/state/service.go index fb544ab12e..c20b74dc73 100644 --- a/dot/state/service.go +++ b/dot/state/service.go @@ -404,3 +404,98 @@ func (s *Service) Stop() error { return s.db.Close() } + +// Import imports the given state corresponding to the given header and sets the head of the chain +// to it. Additionally, it uses the first slot to correctly set the epoch number of the block. +func (s *Service) Import(header *types.Header, t *trie.Trie, firstSlot uint64) error { + cfg := &chaindb.Config{ + DataDir: s.dbPath, + } + + if s.isMemDB { + cfg.InMemory = true + } else { + var err error + + // initialize database using data directory + s.db, err = chaindb.NewBadgerDB(cfg) + if err != nil { + return fmt.Errorf("failed to create database: %s", err) + } + } + + block := &BlockState{ + db: chaindb.NewTable(s.db, blockPrefix), + } + + storage := &StorageState{ + db: chaindb.NewTable(s.db, storagePrefix), + } + + epoch, err := NewEpochState(s.db) + if err != nil { + return err + } + + logger.Info("storing first slot...", "slot", firstSlot) + if err = storeFirstSlot(s.db, firstSlot); err != nil { + return err + } + + epoch.firstSlot = firstSlot + blockEpoch, err := epoch.GetEpochForBlock(header) + if err != nil { + return err + } + + skipTo := blockEpoch + 1 + + if err := storeSkipToEpoch(s.db, skipTo); err != nil { + return err + } + logger.Debug("skip BABE verification up to epoch", "epoch", skipTo) + + if err := epoch.SetCurrentEpoch(blockEpoch); err != nil { + return err + } + + root := t.MustHash() + if root != header.StateRoot { + return fmt.Errorf("trie state root does not equal header state root") + } + + if err := StoreLatestStorageHash(s.db, root); err != nil { + return err + } + + logger.Info("importing storage trie...", "basepath", s.dbPath, "root", root) + + if err := StoreTrie(storage.db, t); err != nil { + return err + } + + bt := blocktree.NewBlockTreeFromRoot(header, s.db) + if err := bt.Store(); err != nil { + return err + } + + if err := StoreBestBlockHash(s.db, header.Hash()); err != nil { + return err + } + + if err := block.SetHeader(header); err != nil { + return err + } + + logger.Debug("Import", "best block hash", header.Hash(), "latest state root", root) + if err := s.db.Flush(); err != nil { + return err + } + + logger.Info("finished state import") + if s.isMemDB { + return nil + } + + return s.db.Close() +} diff --git a/dot/state/service_test.go b/dot/state/service_test.go index e34a1ff026..b338061c4a 100644 --- a/dot/state/service_test.go +++ b/dot/state/service_test.go @@ -237,3 +237,58 @@ func TestService_Rewind(t *testing.T) { require.NoError(t, err) require.Equal(t, big.NewInt(6), num) } + +func TestService_Import(t *testing.T) { + testDir := utils.NewTestDir(t) + defer utils.RemoveTestDir(t) + + serv := NewService(testDir, log.LvlTrace) + serv.UseMemDB() + + genData, genTrie, genesisHeader := newTestGenesisWithTrieAndHeader(t) + err := serv.Initialize(genData, genesisHeader, genTrie) + require.NoError(t, err) + + tr := trie.NewEmptyTrie() + var testCases = []string{ + "asdf", + "ghjk", + "qwerty", + "uiopl", + "zxcv", + "bnm", + } + for _, tc := range testCases { + tr.Put([]byte(tc), []byte(tc)) + } + + header := &types.Header{ + Number: big.NewInt(77), + StateRoot: tr.MustHash(), + Digest: types.Digest{types.NewBabeSecondaryPlainPreDigest(0, 177).ToPreRuntimeDigest()}, + } + + firstSlot := uint64(100) + + err = serv.Import(header, tr, firstSlot) + require.NoError(t, err) + + err = serv.Start() + require.NoError(t, err) + + bestBlockHeader, err := serv.Block.BestBlockHeader() + require.NoError(t, err) + require.Equal(t, header, bestBlockHeader) + + root, err := serv.Storage.StorageRoot() + require.NoError(t, err) + require.Equal(t, header.StateRoot, root) + + require.Equal(t, firstSlot, serv.Epoch.firstSlot) + skip, err := serv.Epoch.SkipVerify(header) + require.NoError(t, err) + require.True(t, skip) + + err = serv.Stop() + require.NoError(t, err) +} diff --git a/dot/sync/syncer.go b/dot/sync/syncer.go index 49cccc8963..d7aa8b768f 100644 --- a/dot/sync/syncer.go +++ b/dot/sync/syncer.go @@ -163,7 +163,7 @@ func (s *Service) ProcessBlockData(data []*types.BlockData) error { bestNum, err := s.blockState.BestBlockNumber() if err != nil { - return err + return fmt.Errorf("failed to get best block number: %w", err) } // TODO: return number of last successful block that was processed @@ -172,12 +172,12 @@ func (s *Service) ProcessBlockData(data []*types.BlockData) error { err := s.blockState.CompareAndSetBlockData(bd) if err != nil { - return err + return fmt.Errorf("failed to compare and set data: %w", err) } hasHeader, _ := s.blockState.HasHeader(bd.Hash) hasBody, _ := s.blockState.HasBlockBody(bd.Hash) - if hasHeader && hasBody && bd.Number().Int64() <= bestNum.Int64() { + if (hasHeader && hasBody) || (bd.Number() != nil && bd.Number().Int64() <= bestNum.Int64()) { // TODO: fix this; sometimes when the node shuts down the "best block" isn't stored properly, // so when the node restarts it has blocks higher than what it thinks is the best, causing it not to sync s.logger.Debug("skipping block, already have", "hash", bd.Hash) @@ -238,6 +238,7 @@ func (s *Service) ProcessBlockData(data []*types.BlockData) error { err = s.handleBlock(block) if err != nil { + s.logger.Error("failed to handle block", "number", block.Header.Number, "error", err) return err } @@ -287,7 +288,7 @@ func (s *Service) handleBlock(block *types.Block) error { parent, err := s.blockState.GetHeader(block.Header.ParentHash) if err != nil { - return err + return fmt.Errorf("failed to get parent hash: %w", err) } s.logger.Trace("getting parent state", "root", parent.StateRoot) diff --git a/dot/types/babe_digest.go b/dot/types/babe_digest.go index ef85661018..fd894ae1fc 100644 --- a/dot/types/babe_digest.go +++ b/dot/types/babe_digest.go @@ -168,6 +168,11 @@ func NewBabeSecondaryPlainPreDigest(authorityIndex uint32, slotNumber uint64) *B } } +// ToPreRuntimeDigest returns the BabePrimaryPreDigest as a PreRuntimeDigest +func (d *BabeSecondaryPlainPreDigest) ToPreRuntimeDigest() *PreRuntimeDigest { + return NewBABEPreRuntimeDigest(d.Encode()) +} + // Type returns BabeSecondaryPlainPreDigestType func (d *BabeSecondaryPlainPreDigest) Type() byte { return BabeSecondaryPlainPreDigestType diff --git a/lib/babe/state.go b/lib/babe/state.go index f13f96a84a..13c7e36264 100644 --- a/lib/babe/state.go +++ b/lib/babe/state.go @@ -73,4 +73,5 @@ type EpochState interface { GetEpochForBlock(header *types.Header) (uint64, error) SetFirstSlot(slot uint64) error GetLatestEpochData() (*types.EpochData, error) + SkipVerify(*types.Header) (bool, error) } diff --git a/lib/babe/verify.go b/lib/babe/verify.go index fefe8905b2..533459b77e 100644 --- a/lib/babe/verify.go +++ b/lib/babe/verify.go @@ -154,7 +154,6 @@ func (v *VerificationManager) VerifyBlock(header *types.Header) error { v.lock.Lock() if info, has = v.epochInfo[epoch]; !has { - // special case for block 1 - the network doesn't necessarily start in epoch 1. // if this happens, the database will be missing info for epochs before the first block. if header.Number.Cmp(big.NewInt(1)) == 0 { @@ -165,11 +164,13 @@ func (v *VerificationManager) VerifyBlock(header *types.Header) error { var firstSlot uint64 firstSlot, err = types.GetSlotFromHeader(header) if err != nil { + v.lock.Unlock() return fmt.Errorf("failed to get slot from block 1: %w", err) } err = v.epochState.SetFirstSlot(firstSlot) if err != nil { + v.lock.Unlock() return fmt.Errorf("failed to set current epoch after receiving block 1: %w", err) } @@ -180,6 +181,17 @@ func (v *VerificationManager) VerifyBlock(header *types.Header) error { if err != nil { v.lock.Unlock() + // SkipVerify is set to true only in the case where we have imported a state at a given height, + // thus missing the epoch data for previous epochs. + skip, err2 := v.epochState.SkipVerify(header) + if err2 != nil { + return fmt.Errorf("failed to check if verification can be skipped: %w", err) + } + + if skip { + return nil + } + return fmt.Errorf("failed to get verifier info for block %d: %w", header.Number, err) } diff --git a/lib/common/well_known_keys.go b/lib/common/well_known_keys.go index 4d74b2ece8..0b702e9012 100644 --- a/lib/common/well_known_keys.go +++ b/lib/common/well_known_keys.go @@ -6,6 +6,7 @@ var ( ) // BalanceKey returns the storage trie key for the balance of the account with the given public key +// TODO: deprecate func BalanceKey(key [32]byte) ([]byte, error) { accKey := append([]byte("balance:"), key[:]...) @@ -18,6 +19,7 @@ func BalanceKey(key [32]byte) ([]byte, error) { } // NonceKey returns the storage trie key for the nonce of the account with the given public key +// TODO: deprecate func NonceKey(key [32]byte) ([]byte, error) { accKey := append([]byte("nonce:"), key[:]...)