diff --git a/dot/core/errors.go b/dot/core/errors.go index 5faf6f8b02..cbf554cd2f 100644 --- a/dot/core/errors.go +++ b/dot/core/errors.go @@ -21,32 +21,45 @@ import ( "fmt" ) -// ErrNilBlockState is returned when BlockState is nil -var ErrNilBlockState = errors.New("cannot have nil BlockState") +var ( + // ErrNilBlockState is returned when BlockState is nil + ErrNilBlockState = errors.New("cannot have nil BlockState") -// ErrNilStorageState is returned when StorageState is nil -var ErrNilStorageState = errors.New("cannot have nil StorageState") + // ErrNilStorageState is returned when StorageState is nil + ErrNilStorageState = errors.New("cannot have nil StorageState") -// ErrNilKeystore is returned when keystore is nil -var ErrNilKeystore = errors.New("cannot have nil keystore") + // ErrNilKeystore is returned when keystore is nil + ErrNilKeystore = errors.New("cannot have nil keystore") -// ErrServiceStopped is returned when the service has been stopped -var ErrServiceStopped = errors.New("service has been stopped") + // ErrServiceStopped is returned when the service has been stopped + ErrServiceStopped = errors.New("service has been stopped") -// ErrInvalidBlock is returned when a block cannot be verified -var ErrInvalidBlock = errors.New("could not verify block") + // ErrInvalidBlock is returned when a block cannot be verified + ErrInvalidBlock = errors.New("could not verify block") -// ErrNilVerifier is returned when trying to instantiate a Syncer without a Verifier -var ErrNilVerifier = errors.New("cannot have nil Verifier") + // ErrNilVerifier is returned when trying to instantiate a Syncer without a Verifier + ErrNilVerifier = errors.New("cannot have nil Verifier") -// ErrNilRuntime is returned when trying to instantiate a Service or Syncer without a runtime -var ErrNilRuntime = errors.New("cannot have nil runtime") + // ErrNilRuntime is returned when trying to instantiate a Service or Syncer without a runtime + ErrNilRuntime = errors.New("cannot have nil runtime") -// ErrNilBlockProducer is returned when trying to instantiate a block producing Service without a block producer -var ErrNilBlockProducer = errors.New("cannot have nil BlockProducer") + // ErrNilBlockProducer is returned when trying to instantiate a block producing Service without a block producer + ErrNilBlockProducer = errors.New("cannot have nil BlockProducer") -// ErrNilConsensusMessageHandler is returned when trying to instantiate a Service without a FinalityMessageHandler -var ErrNilConsensusMessageHandler = errors.New("cannot have nil ErrNilFinalityMessageHandler") + // ErrNilConsensusMessageHandler is returned when trying to instantiate a Service without a FinalityMessageHandler + ErrNilConsensusMessageHandler = errors.New("cannot have nil ErrNilFinalityMessageHandler") + + // ErrNilNetwork is returned when the Network interface is nil + ErrNilNetwork = errors.New("cannot have nil Network") + + // ErrEmptyRuntimeCode is returned when the storage :code is empty + ErrEmptyRuntimeCode = errors.New("new :code is empty") + + // ErrNilDigestHandler is returned when the DigestHandler interface is nil + ErrNilDigestHandler = errors.New("cannot have nil DigestHandler") + + errNilCodeSubstitutedState = errors.New("cannot have nil CodeSubstitutedStat") +) // ErrNilChannel is returned if a channel is nil func ErrNilChannel(s string) error { diff --git a/dot/core/interface.go b/dot/core/interface.go index bab25e3099..48e70ec5ae 100644 --- a/dot/core/interface.go +++ b/dot/core/interface.go @@ -22,7 +22,6 @@ import ( "github.com/ChainSafe/gossamer/dot/network" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" - "github.com/ChainSafe/gossamer/lib/grandpa" rtstorage "github.com/ChainSafe/gossamer/lib/runtime/storage" "github.com/ChainSafe/gossamer/lib/transaction" ) @@ -56,6 +55,7 @@ type StorageState interface { LoadCode(root *common.Hash) ([]byte, error) LoadCodeHash(root *common.Hash) (common.Hash, error) TrieState(root *common.Hash) (*rtstorage.TrieState, error) + StoreTrie(*rtstorage.TrieState, *types.Header) error GetStateRootFromBlock(bhash *common.Hash) (*common.Hash, error) } @@ -68,17 +68,6 @@ type TransactionState interface { PendingInPool() []*transaction.ValidTransaction } -// BlockProducer is the interface that a block production service must implement -type BlockProducer interface { - GetBlockChannel() <-chan types.Block - SetOnDisabled(authorityIndex uint32) -} - -// Verifier is the interface for the block verifier -type Verifier interface { - SetOnDisabled(authorityIndex uint32, block *types.Header) error -} - // Network is the interface for the network service type Network interface { SendMessage(network.NotificationsMessage) @@ -87,17 +76,17 @@ type Network interface { // EpochState is the interface for state.EpochState type EpochState interface { GetEpochForBlock(header *types.Header) (uint64, error) - SetEpochData(epoch uint64, info *types.EpochData) error - SetConfigData(epoch uint64, info *types.ConfigData) error SetCurrentEpoch(epoch uint64) error GetCurrentEpoch() (uint64, error) } -// GrandpaState is the interface for the state.GrandpaState -type GrandpaState interface { - SetNextChange(authorities []*grandpa.Voter, number *big.Int) error - IncrementSetID() error - SetNextPause(number *big.Int) error - SetNextResume(number *big.Int) error - GetCurrentSetID() (uint64, error) +// CodeSubstitutedState interface to handle storage of code substitute state +type CodeSubstitutedState interface { + LoadCodeSubstitutedBlockHash() common.Hash + StoreCodeSubstitutedBlockHash(hash common.Hash) error +} + +// DigestHandler is the interface for the consensus digest handler +type DigestHandler interface { + HandleDigests(header *types.Header) } diff --git a/dot/core/messages.go b/dot/core/messages.go index 8b65d31790..e604da2ff3 100644 --- a/dot/core/messages.go +++ b/dot/core/messages.go @@ -26,9 +26,6 @@ import ( // adds valid transactions to the transaction queue of the BABE session func (s *Service) HandleTransactionMessage(msg *network.TransactionMessage) error { logger.Debug("received TransactionMessage") - if !s.isBlockProducer { - return nil - } // get transactions from message extrinsics txs := msg.Extrinsics diff --git a/dot/core/messages_test.go b/dot/core/messages_test.go index cbeaceb990..701c5db2b4 100644 --- a/dot/core/messages_test.go +++ b/dot/core/messages_test.go @@ -30,6 +30,7 @@ import ( "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/lib/runtime" "github.com/ChainSafe/gossamer/lib/scale" + "github.com/centrifuge/go-substrate-rpc-client/v2/signature" ctypes "github.com/centrifuge/go-substrate-rpc-client/v2/types" "github.com/stretchr/testify/require" @@ -39,13 +40,9 @@ func TestService_ProcessBlockAnnounceMessage(t *testing.T) { // TODO: move to sync package net := new(MockNetwork) - newBlocks := make(chan types.Block) - cfg := &Config{ - Network: net, - Keystore: keystore.NewGlobalKeystore(), - NewBlocks: newBlocks, - IsBlockProducer: false, + Network: net, + Keystore: keystore.NewGlobalKeystore(), } s := NewTestService(t, cfg) @@ -53,10 +50,11 @@ func TestService_ProcessBlockAnnounceMessage(t *testing.T) { require.Nil(t, err) // simulate block sent from BABE session - newBlock := types.Block{ + newBlock := &types.Block{ Header: &types.Header{ Number: big.NewInt(1), ParentHash: s.blockState.BestBlockHash(), + Digest: types.Digest{types.NewBabeSecondaryPlainPreDigest(0, 1).ToPreRuntimeDigest()}, }, Body: types.NewBody([]byte{}), } @@ -72,10 +70,14 @@ func TestService_ProcessBlockAnnounceMessage(t *testing.T) { //setup the SendMessage function net.On("SendMessage", expected) - newBlocks <- newBlock - time.Sleep(time.Second * 2) + state, err := s.storageState.TrieState(nil) + require.NoError(t, err) + + err = s.HandleBlockProduced(newBlock, state) + require.NoError(t, err) + time.Sleep(time.Second) net.AssertCalled(t, "SendMessage", expected) } @@ -141,8 +143,6 @@ func TestService_HandleTransactionMessage(t *testing.T) { cfg := &Config{ Keystore: ks, TransactionState: state.NewTransactionState(), - IsBlockProducer: true, - BlockProducer: bp, } s := NewTestService(t, cfg) diff --git a/dot/core/mocks/digest_handler.go b/dot/core/mocks/digest_handler.go new file mode 100644 index 0000000000..77c8e1a76c --- /dev/null +++ b/dot/core/mocks/digest_handler.go @@ -0,0 +1,18 @@ +// Code generated by mockery v2.8.0. DO NOT EDIT. + +package core + +import ( + types "github.com/ChainSafe/gossamer/dot/types" + mock "github.com/stretchr/testify/mock" +) + +// MockDigestHandler is an autogenerated mock type for the DigestHandler type +type MockDigestHandler struct { + mock.Mock +} + +// HandleDigests provides a mock function with given fields: header +func (_m *MockDigestHandler) HandleDigests(header *types.Header) { + _m.Called(header) +} diff --git a/dot/core/service.go b/dot/core/service.go index 747944587f..8ee7d402f0 100644 --- a/dot/core/service.go +++ b/dot/core/service.go @@ -16,16 +16,21 @@ package core import ( + "bytes" "context" + "fmt" + "math/big" "os" "sync" "github.com/ChainSafe/gossamer/dot/network" "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/blocktree" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/crypto" "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/lib/runtime" + rtstorage "github.com/ChainSafe/gossamer/lib/runtime/storage" "github.com/ChainSafe/gossamer/lib/scale" "github.com/ChainSafe/gossamer/lib/services" "github.com/ChainSafe/gossamer/lib/transaction" @@ -41,43 +46,35 @@ var ( // BABE session, and network service. It deals with the validation of transactions // and blocks by calling their respective validation functions in the runtime. type Service struct { - ctx context.Context - cancel context.CancelFunc + ctx context.Context + cancel context.CancelFunc + blockAddCh chan *types.Block // for asynchronous block handling + sync.Mutex // lock for channel - // State interfaces + // Service interfaces blockState BlockState epochState EpochState storageState StorageState transactionState TransactionState + net Network + digestHandler DigestHandler // Current runtime and hash of the current runtime code rt runtime.Instance codeHash common.Hash - // Block production variables - blockProducer BlockProducer - isBlockProducer bool - - // Block verification - verifier Verifier + // map of code substitutions keyed by block hash + codeSubstitute map[common.Hash]string + codeSubstitutedState CodeSubstitutedState // Keystore keys *keystore.GlobalKeystore - - // Channels and interfaces for inter-process communication - blkRec <-chan types.Block // receive blocks from BABE session - net Network - - blockAddCh chan *types.Block // receive blocks added to blocktree - blockAddChID byte - - // State variables - lock *sync.Mutex // channel lock } // Config holds the configuration for the core Service. type Config struct { - LogLvl log.Lvl + LogLvl log.Lvl + BlockState BlockState EpochState EpochState StorageState StorageState @@ -85,11 +82,10 @@ type Config struct { Network Network Keystore *keystore.GlobalKeystore Runtime runtime.Instance - BlockProducer BlockProducer - IsBlockProducer bool - Verifier Verifier + DigestHandler DigestHandler - NewBlocks chan types.Block // only used for testing purposes + CodeSubstitutes map[common.Hash]string + CodeSubstitutedState CodeSubstitutedState } // NewService returns a new core service that connects the runtime, BABE @@ -111,8 +107,16 @@ func NewService(cfg *Config) (*Service, error) { return nil, ErrNilRuntime } - if cfg.IsBlockProducer && cfg.BlockProducer == nil { - return nil, ErrNilBlockProducer + if cfg.Network == nil { + return nil, ErrNilNetwork + } + + if cfg.DigestHandler == nil { + return nil, ErrNilDigestHandler + } + + if cfg.CodeSubstitutedState == nil { + return nil, errNilCodeSubstitutedState } h := log.StreamHandler(os.Stdout, log.TerminalFormat()) @@ -129,38 +133,24 @@ func NewService(cfg *Config) (*Service, error) { return nil, err } - blockAddCh := make(chan *types.Block, 16) - id, err := cfg.BlockState.RegisterImportedChannel(blockAddCh) - if err != nil { - return nil, err - } + blockAddCh := make(chan *types.Block, 256) ctx, cancel := context.WithCancel(context.Background()) - srv := &Service{ - ctx: ctx, - cancel: cancel, - rt: cfg.Runtime, - codeHash: codeHash, - keys: cfg.Keystore, - blkRec: cfg.NewBlocks, - blockState: cfg.BlockState, - epochState: cfg.EpochState, - storageState: cfg.StorageState, - transactionState: cfg.TransactionState, - net: cfg.Network, - isBlockProducer: cfg.IsBlockProducer, - blockProducer: cfg.BlockProducer, - verifier: cfg.Verifier, - lock: &sync.Mutex{}, - blockAddCh: blockAddCh, - blockAddChID: id, - } - - if cfg.NewBlocks != nil { - srv.blkRec = cfg.NewBlocks - } else if cfg.IsBlockProducer { - srv.blkRec = cfg.BlockProducer.GetBlockChannel() + ctx: ctx, + cancel: cancel, + rt: cfg.Runtime, + codeHash: codeHash, + keys: cfg.Keystore, + blockState: cfg.BlockState, + epochState: cfg.EpochState, + storageState: cfg.StorageState, + transactionState: cfg.TransactionState, + net: cfg.Network, + blockAddCh: blockAddCh, + codeSubstitute: cfg.CodeSubstitutes, + codeSubstitutedState: cfg.CodeSubstitutedState, + digestHandler: cfg.DigestHandler, } return srv, nil @@ -168,29 +158,17 @@ func NewService(cfg *Config) (*Service, error) { // Start starts the core service func (s *Service) Start() error { - // we can ignore the `cancel` function returned by `context.WithCancel` since Stop() cancels the parent context, - // so all the child contexts should also be canceled. potentially update if there is a better way to do this - - // start receiving blocks from BABE session - go s.receiveBlocks(s.ctx) - - // start receiving messages from network service - - // start handling imported blocks - go s.handleBlocks(s.ctx) - + go s.handleBlocksAsync() return nil } // Stop stops the core service func (s *Service) Stop() error { - s.lock.Lock() - defer s.lock.Unlock() - s.cancel() + s.Lock() + defer s.Unlock() - s.blockState.UnregisterImportedChannel(s.blockAddChID) + s.cancel() close(s.blockAddCh) - return nil } @@ -208,32 +186,161 @@ func (s *Service) StorageRoot() (common.Hash, error) { return ts.Root() } -func (s *Service) handleBlocks(ctx context.Context) { - for { - //prev := s.blockState.BestBlockHash() +// HandleBlockImport handles a block that was imported via the network +func (s *Service) HandleBlockImport(block *types.Block, state *rtstorage.TrieState) error { + return s.handleBlock(block, state) +} - select { - case block := <-s.blockAddCh: - if block == nil { - continue - } +// HandleBlockProduced handles a block that was produced by us +// It is handled the same as an imported block in terms of state updates; the only difference +// is we send a BlockAnnounceMessage to our peers. +func (s *Service) HandleBlockProduced(block *types.Block, state *rtstorage.TrieState) error { + msg := &network.BlockAnnounceMessage{ + ParentHash: block.Header.ParentHash, + Number: block.Header.Number, + StateRoot: block.Header.StateRoot, + ExtrinsicsRoot: block.Header.ExtrinsicsRoot, + Digest: block.Header.Digest, + BestBlock: true, + } - if err := s.handleCurrentSlot(block.Header); err != nil { - logger.Warn("failed to handle epoch for block", "block", block.Header.Hash(), "error", err) - } + s.net.SendMessage(msg) + return s.handleBlock(block, state) +} - // TODO: add inherent check - // if err := s.handleChainReorg(prev, block.Header.Hash()); err != nil { - // logger.Warn("failed to re-add transactions to chain upon re-org", "error", err) - // } +func (s *Service) handleBlock(block *types.Block, state *rtstorage.TrieState) error { + if block == nil || block.Header == nil || state == nil { + return nil + } - if err := s.maintainTransactionPool(block); err != nil { - logger.Warn("failed to maintain transaction pool", "error", err) - } - case <-ctx.Done(): + // store updates state trie nodes in database + err := s.storageState.StoreTrie(state, block.Header) + if err != nil { + logger.Warn("failed to store state trie for imported block", "block", block.Header.Hash(), "error", err) + return err + } + + // store block in database + if err := s.blockState.AddBlock(block); err != nil { + if err == blocktree.ErrParentNotFound && block.Header.Number.Cmp(big.NewInt(0)) != 0 { + return err + } else if err == blocktree.ErrBlockExists || block.Header.Number.Cmp(big.NewInt(0)) == 0 { + // this is fine + } else { + return err + } + } + + logger.Debug("imported block and stored state trie", "block", block.Header.Hash(), "state root", state.MustRoot()) + + // handle consensus digests + s.digestHandler.HandleDigests(block.Header) + + // check for runtime changes + if err := s.handleRuntimeChanges(state); err != nil { + logger.Crit("failed to update runtime code", "error", err) + return err + } + + // check if there was a runtime code substitution + if err := s.handleCodeSubstitution(block.Header.Hash()); err != nil { + logger.Crit("failed to substitute runtime code", "error", err) + return err + } + + // check if block production epoch transitioned + if err := s.handleCurrentSlot(block.Header); err != nil { + logger.Warn("failed to handle epoch for block", "block", block.Header.Hash(), "error", err) + return err + } + + go func() { + s.Lock() + defer s.Unlock() + if s.ctx.Err() != nil { return } + + s.blockAddCh <- block + }() + + return nil +} + +func (s *Service) handleRuntimeChanges(newState *rtstorage.TrieState) error { + currCodeHash, err := newState.LoadCodeHash() + if err != nil { + return err } + + if bytes.Equal(s.codeHash[:], currCodeHash[:]) { + return nil + } + + logger.Info("πŸ”„ detected runtime code change, upgrading...", "block", s.blockState.BestBlockHash(), "previous code hash", s.codeHash, "new code hash", currCodeHash) + code := newState.LoadCode() + if len(code) == 0 { + return ErrEmptyRuntimeCode + } + + codeSubBlockHash := s.codeSubstitutedState.LoadCodeSubstitutedBlockHash() + + if !codeSubBlockHash.Equal(common.Hash{}) { + // don't do runtime change if using code substitution and runtime change spec version are equal + // (do a runtime change if code substituted and runtime spec versions are different, or code not substituted) + newVersion, err := s.rt.CheckRuntimeVersion(code) //nolint + if err != nil { + return err + } + + previousVersion, _ := s.rt.Version() + if previousVersion.SpecVersion() == newVersion.SpecVersion() { + return nil + } + + logger.Info("πŸ”„ detected runtime code change, upgrading...", "block", s.blockState.BestBlockHash(), + "previous code hash", s.codeHash, "new code hash", currCodeHash, + "previous spec version", previousVersion.SpecVersion(), "new spec version", newVersion.SpecVersion()) + } + + err = s.rt.UpdateRuntimeCode(code) + if err != nil { + return err + } + + s.codeHash = currCodeHash + + err = s.codeSubstitutedState.StoreCodeSubstitutedBlockHash(common.Hash{}) + if err != nil { + return fmt.Errorf("failed to update code substituted block hash: %w", err) + } + + return nil +} + +func (s *Service) handleCodeSubstitution(hash common.Hash) error { + value := s.codeSubstitute[hash] + if value == "" { + return nil + } + + logger.Info("πŸ”„ detected runtime code substitution, upgrading...", "block", hash) + code := common.MustHexToBytes(value) + if len(code) == 0 { + return ErrEmptyRuntimeCode + } + + err := s.rt.UpdateRuntimeCode(code) + if err != nil { + return err + } + + err = s.codeSubstitutedState.StoreCodeSubstitutedBlockHash(hash) + if err != nil { + return err + } + + return nil } func (s *Service) handleCurrentSlot(header *types.Header) error { @@ -259,50 +366,30 @@ func (s *Service) handleCurrentSlot(header *types.Header) error { return s.epochState.SetCurrentEpoch(epoch) } -// receiveBlocks starts receiving blocks from the BABE session -func (s *Service) receiveBlocks(ctx context.Context) { +// handleBlocksAsync handles a block asynchronously; the handling performed by this function +// does not need to be completed before the next block can be imported. +func (s *Service) handleBlocksAsync() { for { select { - case block := <-s.blkRec: - if block.Header == nil { + case block := <-s.blockAddCh: + if block == nil { continue } - err := s.handleReceivedBlock(&block) - if err != nil { - logger.Warn("failed to handle block from BABE session", "err", err) + // TODO: add inherent check + // if err := s.handleChainReorg(prev, block.Header.Hash()); err != nil { + // logger.Warn("failed to re-add transactions to chain upon re-org", "error", err) + // } + + if err := s.maintainTransactionPool(block); err != nil { + logger.Warn("failed to maintain transaction pool", "error", err) } - case <-ctx.Done(): + case <-s.ctx.Done(): return } } } -// handleReceivedBlock handles blocks from the BABE session -func (s *Service) handleReceivedBlock(block *types.Block) (err error) { - if s.blockState == nil { - return ErrNilBlockState - } - - logger.Debug("got block from BABE", "header", block.Header, "body", block.Body) - - msg := &network.BlockAnnounceMessage{ - ParentHash: block.Header.ParentHash, - Number: block.Header.Number, - StateRoot: block.Header.StateRoot, - ExtrinsicsRoot: block.Header.ExtrinsicsRoot, - Digest: block.Header.Digest, - BestBlock: true, - } - - if s.net == nil { - return - } - - s.net.SendMessage(msg) - return nil -} - // handleChainReorg checks if there is a chain re-org (ie. new chain head is on a different chain than the // previous chain head). If there is a re-org, it moves the transactions that were included on the previous // chain back into the transaction pool. @@ -451,13 +538,9 @@ func (s *Service) GetRuntimeVersion(bhash *common.Hash) (runtime.Version, error) return s.rt.Version() } -// IsBlockProducer returns true if node is a block producer -func (s *Service) IsBlockProducer() bool { - return s.isBlockProducer -} - // HandleSubmittedExtrinsic is used to send a Transaction message containing a Extrinsic @ext func (s *Service) HandleSubmittedExtrinsic(ext types.Extrinsic) error { + logger.Crit("HandleSubmittedExtrinsic") if s.net == nil { return nil } @@ -470,11 +553,9 @@ func (s *Service) HandleSubmittedExtrinsic(ext types.Extrinsic) error { return err } - if s.isBlockProducer { - // add transaction to pool - vtx := transaction.NewValidTransaction(ext, txv) - s.transactionState.AddToPool(vtx) - } + // add transaction to pool + vtx := transaction.NewValidTransaction(ext, txv) + s.transactionState.AddToPool(vtx) // broadcast transaction msg := &network.TransactionMessage{Extrinsics: []types.Extrinsic{ext}} diff --git a/dot/core/service_test.go b/dot/core/service_test.go index 1419509ede..51edd90a1b 100644 --- a/dot/core/service_test.go +++ b/dot/core/service_test.go @@ -17,6 +17,7 @@ package core import ( + "io/ioutil" "math/big" "os" "sort" @@ -40,8 +41,8 @@ import ( coremocks "github.com/ChainSafe/gossamer/dot/core/mocks" ) -func addTestBlocksToState(t *testing.T, depth int, blockState BlockState) []*types.Header { - return addTestBlocksToStateWithParent(t, blockState.BestBlockHash(), depth, blockState) +func addTestBlocksToState(t *testing.T, depth int, blockState BlockState) { + _ = addTestBlocksToStateWithParent(t, blockState.BestBlockHash(), depth, blockState) } func addTestBlocksToStateWithParent(t *testing.T, previousHash common.Hash, depth int, blockState BlockState) []*types.Header { @@ -100,23 +101,21 @@ func TestStartService(t *testing.T) { func TestAnnounceBlock(t *testing.T) { net := new(coremocks.MockNetwork) - newBlocks := make(chan types.Block) - cfg := &Config{ - NewBlocks: newBlocks, - Network: net, + Network: net, } s := NewTestService(t, cfg) err := s.Start() - require.Nil(t, err) + require.NoError(t, err) defer s.Stop() // simulate block sent from BABE session - newBlock := types.Block{ + newBlock := &types.Block{ Header: &types.Header{ ParentHash: s.blockState.BestBlockHash(), Number: big.NewInt(1), + Digest: types.Digest{types.NewBabeSecondaryPlainPreDigest(0, 1).ToPreRuntimeDigest()}, }, Body: &types.Body{}, } @@ -132,9 +131,13 @@ func TestAnnounceBlock(t *testing.T) { net.On("SendMessage", expected) - newBlocks <- newBlock - time.Sleep(time.Second * 2) + state, err := s.storageState.TrieState(nil) + require.NoError(t, err) + + err = s.HandleBlockProduced(newBlock, state) + require.NoError(t, err) + time.Sleep(time.Second) net.AssertCalled(t, "SendMessage", expected) } @@ -425,15 +428,6 @@ func TestService_GetRuntimeVersion(t *testing.T) { require.Equal(t, rtExpected, rtv) } -func TestService_IsBlockProducer(t *testing.T) { - cfg := &Config{ - IsBlockProducer: false, - } - s := NewTestService(t, cfg) - bp := s.IsBlockProducer() - require.Equal(t, false, bp) -} - func TestService_HandleSubmittedExtrinsic(t *testing.T) { s := NewTestService(t, nil) @@ -457,3 +451,58 @@ func TestService_GetMetadata(t *testing.T) { require.NoError(t, err) require.Greater(t, len(res), 10000) } + +func TestService_HandleRuntimeChanges(t *testing.T) { + s := NewTestService(t, nil) + codeHashBefore := s.codeHash + + testRuntime, err := ioutil.ReadFile(runtime.POLKADOT_RUNTIME_FP) + require.NoError(t, err) + + ts, err := s.storageState.TrieState(nil) + require.NoError(t, err) + + ts.Set(common.CodeKey, testRuntime) + err = s.handleRuntimeChanges(ts) + require.NoError(t, err) + codeHashAfter := s.codeHash + require.NotEqualf(t, codeHashBefore, codeHashAfter, "expected different code hash after runtime update") +} + +func TestService_HandleCodeSubstitutes(t *testing.T) { + s := NewTestService(t, nil) + + testRuntime, err := ioutil.ReadFile(runtime.POLKADOT_RUNTIME_FP) + require.NoError(t, err) + + blockHash := common.MustHexToHash("0x86aa36a140dfc449c30dbce16ce0fea33d5c3786766baa764e33f336841b9e29") // hash for known test code substitution + s.codeSubstitute = map[common.Hash]string{ + blockHash: common.BytesToHex(testRuntime), + } + + err = s.handleCodeSubstitution(blockHash) + require.NoError(t, err) + codSub := s.codeSubstitutedState.LoadCodeSubstitutedBlockHash() + require.Equal(t, blockHash, codSub) +} + +func TestService_HandleRuntimeChangesAfterCodeSubstitutes(t *testing.T) { + s := NewTestService(t, nil) + codeHashBefore := s.codeHash + blockHash := common.MustHexToHash("0x86aa36a140dfc449c30dbce16ce0fea33d5c3786766baa764e33f336841b9e29") // hash for known test code substitution + + err := s.handleCodeSubstitution(blockHash) + require.NoError(t, err) + require.Equal(t, codeHashBefore, s.codeHash) // codeHash should remain unchanged after code substitute + + testRuntime, err := ioutil.ReadFile(runtime.POLKADOT_RUNTIME_FP) + require.NoError(t, err) + + ts, err := s.storageState.TrieState(nil) + require.NoError(t, err) + + ts.Set(common.CodeKey, testRuntime) + err = s.handleRuntimeChanges(ts) + require.NoError(t, err) + require.NotEqualf(t, codeHashBefore, s.codeHash, "expected different code hash after runtime update") // codeHash should change after runtime change +} diff --git a/dot/core/test_helpers.go b/dot/core/test_helpers.go index a7a4a226a9..fc9b786136 100644 --- a/dot/core/test_helpers.go +++ b/dot/core/test_helpers.go @@ -32,11 +32,10 @@ import ( "github.com/ChainSafe/gossamer/lib/runtime/wasmer" "github.com/ChainSafe/gossamer/lib/trie" log "github.com/ChainSafe/log15" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - // importing packagemocks coremocks "github.com/ChainSafe/gossamer/dot/core/mocks" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" ) func newTestGenesisWithTrieAndHeader(t *testing.T) (*genesis.Genesis, *trie.Trie, *types.Header) { @@ -57,11 +56,12 @@ func newTestGenesisWithTrieAndHeader(t *testing.T) (*genesis.Genesis, *trie.Trie // NewTestService creates a new test core service func NewTestService(t *testing.T, cfg *Config) *Service { if cfg == nil { - cfg = &Config{ - IsBlockProducer: false, - } + cfg = &Config{} } + cfg.DigestHandler = new(coremocks.MockDigestHandler) + cfg.DigestHandler.(*coremocks.MockDigestHandler).On("HandleDigests", mock.AnythingOfType("*types.Header")) + if cfg.Keystore == nil { cfg.Keystore = keystore.NewGlobalKeystore() kp, err := sr25519.GenerateKeypair() @@ -71,16 +71,6 @@ func NewTestService(t *testing.T, cfg *Config) *Service { cfg.Keystore.Acco.Insert(kp) } - if cfg.NewBlocks == nil { - cfg.NewBlocks = make(chan types.Block) - } - - if cfg.Verifier == nil { - verifier := new(coremocks.MockVerifier) - verifier.On("SetOnDisabled", mock.AnythingOfType("uint32"), mock.AnythingOfType("*types.Header")).Return(nil) - cfg.Verifier = nil - } - cfg.LogLvl = 3 var stateSrvc *state.Service @@ -89,7 +79,7 @@ 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 { + if cfg.BlockState == nil || cfg.StorageState == nil || cfg.TransactionState == nil || cfg.EpochState == nil || cfg.CodeSubstitutedState == nil { config := state.Config{ Path: testDatadirPath, LogLevel: log.LvlInfo, @@ -120,6 +110,10 @@ func NewTestService(t *testing.T, cfg *Config) *Service { cfg.EpochState = stateSrvc.Epoch } + if cfg.CodeSubstitutedState == nil { + cfg.CodeSubstitutedState = stateSrvc.Base + } + if cfg.Runtime == nil { rtCfg := &wasmer.Config{} rtCfg.Storage, err = rtstorage.NewTrieState(genTrie) @@ -140,8 +134,23 @@ func NewTestService(t *testing.T, cfg *Config) *Service { cfg.Network = createTestNetworkService(t, config) } + if cfg.CodeSubstitutes == nil { + cfg.CodeSubstitutes = make(map[common.Hash]string) + + genesisData, err := cfg.CodeSubstitutedState.(*state.BaseState).LoadGenesisData() //nolint + require.NoError(t, err) + + for k, v := range genesisData.CodeSubstitutes { + cfg.CodeSubstitutes[common.MustHexToHash(k)] = v + } + } + + if cfg.CodeSubstitutedState == nil { + cfg.CodeSubstitutedState = stateSrvc.Base + } + s, err := NewService(cfg) - require.Nil(t, err) + require.NoError(t, err) if net, ok := cfg.Network.(*network.Service); ok { net.SetTransactionHandler(s) diff --git a/dot/core/digest.go b/dot/digest/digest.go similarity index 81% rename from dot/core/digest.go rename to dot/digest/digest.go index 3034706499..f48c2d9c5a 100644 --- a/dot/core/digest.go +++ b/dot/digest/digest.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the gossamer library. If not, see . -package core +package digest import ( "context" @@ -23,12 +23,20 @@ import ( "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/scale" + "github.com/ChainSafe/gossamer/lib/services" + + log "github.com/ChainSafe/log15" ) var maxUint64 = uint64(2^64) - 1 -// DigestHandler is used to handle consensus messages and relevant authority updates to BABE and GRANDPA -type DigestHandler struct { +var ( + _ services.Service = &Handler{} + logger log.Logger = log.New("pkg", "digest") // TODO: add to config options +) + +// Handler is used to handle consensus messages and relevant authority updates to BABE and GRANDPA +type Handler struct { ctx context.Context cancel context.CancelFunc @@ -36,8 +44,6 @@ type DigestHandler struct { blockState BlockState epochState EpochState grandpaState GrandpaState - babe BlockProducer - verifier Verifier // block notification channels imported chan *types.Block @@ -65,8 +71,8 @@ type resume struct { atBlock *big.Int } -// NewDigestHandler returns a new DigestHandler -func NewDigestHandler(blockState BlockState, epochState EpochState, grandpaState GrandpaState, babe BlockProducer, verifier Verifier) (*DigestHandler, error) { +// NewHandler returns a new Handler +func NewHandler(blockState BlockState, epochState EpochState, grandpaState GrandpaState) (*Handler, error) { imported := make(chan *types.Block, 16) finalised := make(chan *types.FinalisationInfo, 16) iid, err := blockState.RegisterImportedChannel(imported) @@ -81,14 +87,12 @@ func NewDigestHandler(blockState BlockState, epochState EpochState, grandpaState ctx, cancel := context.WithCancel(context.Background()) - return &DigestHandler{ + return &Handler{ ctx: ctx, cancel: cancel, blockState: blockState, epochState: epochState, grandpaState: grandpaState, - babe: babe, - verifier: verifier, imported: imported, importedID: iid, finalised: finalised, @@ -96,15 +100,15 @@ func NewDigestHandler(blockState BlockState, epochState EpochState, grandpaState }, nil } -// Start starts the DigestHandler -func (h *DigestHandler) Start() error { +// Start starts the Handler +func (h *Handler) Start() error { go h.handleBlockImport(h.ctx) go h.handleBlockFinalisation(h.ctx) return nil } -// Stop stops the DigestHandler -func (h *DigestHandler) Stop() error { +// Stop stops the Handler +func (h *Handler) Stop() error { h.cancel() h.blockState.UnregisterImportedChannel(h.importedID) h.blockState.UnregisterFinalizedChannel(h.finalisedID) @@ -115,7 +119,7 @@ func (h *DigestHandler) Stop() error { // NextGrandpaAuthorityChange returns the block number of the next upcoming grandpa authorities change. // It returns 0 if no change is scheduled. -func (h *DigestHandler) NextGrandpaAuthorityChange() uint64 { +func (h *Handler) NextGrandpaAuthorityChange() uint64 { next := maxUint64 if h.grandpaScheduledChange != nil { @@ -137,8 +141,25 @@ func (h *DigestHandler) NextGrandpaAuthorityChange() uint64 { return next } -// HandleConsensusDigest is the function used by the syncer to handle a consensus digest -func (h *DigestHandler) HandleConsensusDigest(d *types.ConsensusDigest, header *types.Header) error { +// HandleDigests handles consensus digests for an imported block +func (h *Handler) HandleDigests(header *types.Header) { + for i, d := range header.Digest { + if d.Type() == types.ConsensusDigestType { + cd, ok := d.(*types.ConsensusDigest) + if !ok { + logger.Error("handleDigests", "block number", header.Number, "index", i, "error", "cannot cast invalid consensus digest item") + continue + } + + err := h.handleConsensusDigest(cd, header) + if err != nil { + logger.Error("handleDigests", "block number", header.Number, "index", i, "digest", cd, "error", err) + } + } + } +} + +func (h *Handler) handleConsensusDigest(d *types.ConsensusDigest, header *types.Header) error { t := d.DataType() if d.ConsensusEngineID == types.GrandpaEngineID { @@ -174,7 +195,7 @@ func (h *DigestHandler) HandleConsensusDigest(d *types.ConsensusDigest, header * return errors.New("unknown consensus engine ID") } -func (h *DigestHandler) handleBlockImport(ctx context.Context) { +func (h *Handler) handleBlockImport(ctx context.Context) { for { select { case block := <-h.imported: @@ -192,7 +213,7 @@ func (h *DigestHandler) handleBlockImport(ctx context.Context) { } } -func (h *DigestHandler) handleBlockFinalisation(ctx context.Context) { +func (h *Handler) handleBlockFinalisation(ctx context.Context) { for { select { case info := <-h.finalised: @@ -210,7 +231,7 @@ func (h *DigestHandler) handleBlockFinalisation(ctx context.Context) { } } -func (h *DigestHandler) handleGrandpaChangesOnImport(num *big.Int) error { +func (h *Handler) handleGrandpaChangesOnImport(num *big.Int) error { resume := h.grandpaResume if resume != nil && num.Cmp(resume.atBlock) > -1 { h.grandpaResume = nil @@ -235,7 +256,7 @@ func (h *DigestHandler) handleGrandpaChangesOnImport(num *big.Int) error { return nil } -func (h *DigestHandler) handleGrandpaChangesOnFinalization(num *big.Int) error { +func (h *Handler) handleGrandpaChangesOnFinalization(num *big.Int) error { pause := h.grandpaPause if pause != nil && num.Cmp(pause.atBlock) > -1 { h.grandpaPause = nil @@ -262,7 +283,7 @@ func (h *DigestHandler) handleGrandpaChangesOnFinalization(num *big.Int) error { return nil } -func (h *DigestHandler) handleScheduledChange(d *types.ConsensusDigest, header *types.Header) error { +func (h *Handler) handleScheduledChange(d *types.ConsensusDigest, header *types.Header) error { curr, err := h.blockState.BestBlockHeader() if err != nil { return err @@ -304,7 +325,7 @@ func (h *DigestHandler) handleScheduledChange(d *types.ConsensusDigest, header * ) } -func (h *DigestHandler) handleForcedChange(d *types.ConsensusDigest, header *types.Header) error { +func (h *Handler) handleForcedChange(d *types.ConsensusDigest, header *types.Header) error { if d.ConsensusEngineID != types.GrandpaEngineID { return nil } @@ -345,7 +366,7 @@ func (h *DigestHandler) handleForcedChange(d *types.ConsensusDigest, header *typ ) } -func (h *DigestHandler) handlePause(d *types.ConsensusDigest) error { +func (h *Handler) handlePause(d *types.ConsensusDigest) error { curr, err := h.blockState.BestBlockHeader() if err != nil { return err @@ -367,7 +388,7 @@ func (h *DigestHandler) handlePause(d *types.ConsensusDigest) error { return h.grandpaState.SetNextPause(h.grandpaPause.atBlock) } -func (h *DigestHandler) handleResume(d *types.ConsensusDigest) error { +func (h *Handler) handleResume(d *types.ConsensusDigest) error { curr, err := h.blockState.BestBlockHeader() if err != nil { return err @@ -403,25 +424,13 @@ func newGrandpaChange(raw []*types.GrandpaAuthoritiesRaw, delay uint32, currBloc }, nil } -func (h *DigestHandler) handleBABEOnDisabled(d *types.ConsensusDigest, header *types.Header) error { - od := new(types.BABEOnDisabled) - _, err := scale.Decode(d.Data[1:], od) - if err != nil { - return err - } - +func (h *Handler) handleBABEOnDisabled(d *types.ConsensusDigest, _ *types.Header) error { + od := &types.BABEOnDisabled{} logger.Debug("handling BABEOnDisabled", "data", od) - - err = h.verifier.SetOnDisabled(od.ID, header) - - if err != nil { - return err - } - h.babe.SetOnDisabled(od.ID) return nil } -func (h *DigestHandler) handleNextEpochData(d *types.ConsensusDigest, header *types.Header) error { +func (h *Handler) handleNextEpochData(d *types.ConsensusDigest, header *types.Header) error { od := &types.NextEpochData{} dec, err := scale.Decode(d.Data[1:], od) if err != nil { @@ -446,7 +455,7 @@ func (h *DigestHandler) handleNextEpochData(d *types.ConsensusDigest, header *ty return h.epochState.SetEpochData(currEpoch+1, data) } -func (h *DigestHandler) handleNextConfigData(d *types.ConsensusDigest, header *types.Header) error { +func (h *Handler) handleNextConfigData(d *types.ConsensusDigest, header *types.Header) error { od := &types.NextConfigData{} dec, err := scale.Decode(d.Data[1:], od) if err != nil { diff --git a/dot/core/digest_test.go b/dot/digest/digest_test.go similarity index 74% rename from dot/core/digest_test.go rename to dot/digest/digest_test.go index 308b06497e..7af5a21206 100644 --- a/dot/core/digest_test.go +++ b/dot/digest/digest_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the gossamer library. If not, see . -package core +package digest import ( "io/ioutil" @@ -24,18 +24,65 @@ import ( "github.com/ChainSafe/gossamer/dot/state" "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/crypto/ed25519" "github.com/ChainSafe/gossamer/lib/crypto/sr25519" + "github.com/ChainSafe/gossamer/lib/genesis" "github.com/ChainSafe/gossamer/lib/keystore" - - . "github.com/ChainSafe/gossamer/dot/core/mocks" + "github.com/ChainSafe/gossamer/lib/trie" log "github.com/ChainSafe/log15" - "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) -func newTestDigestHandler(t *testing.T, withBABE, withGrandpa bool) *DigestHandler { //nolint +// TODO: use these from core? +func addTestBlocksToState(t *testing.T, depth int, blockState BlockState) []*types.Header { + return addTestBlocksToStateWithParent(t, blockState.(*state.BlockState).BestBlockHash(), depth, blockState) +} + +func addTestBlocksToStateWithParent(t *testing.T, previousHash common.Hash, depth int, blockState BlockState) []*types.Header { + prevHeader, err := blockState.(*state.BlockState).GetHeader(previousHash) + require.NoError(t, err) + previousNum := prevHeader.Number + + headers := []*types.Header{} + + for i := 1; i <= depth; i++ { + block := &types.Block{ + Header: &types.Header{ + ParentHash: previousHash, + Number: big.NewInt(int64(i)).Add(previousNum, big.NewInt(int64(i))), + Digest: types.Digest{}, + }, + Body: &types.Body{}, + } + + previousHash = block.Header.Hash() + + err := blockState.(*state.BlockState).AddBlock(block) + require.NoError(t, err) + headers = append(headers, block.Header) + } + + return headers +} + +func newTestGenesisWithTrieAndHeader(t *testing.T) (*genesis.Genesis, *trie.Trie, *types.Header) { + gen, err := genesis.NewGenesisFromJSONRaw("../../chain/gssmr/genesis.json") + if err != nil { + gen, err = genesis.NewGenesisFromJSONRaw("../../../chain/gssmr/genesis.json") + require.NoError(t, err) + } + + genTrie, err := genesis.NewTrieFromGenesis(gen) + require.NoError(t, err) + + genesisHeader, err := types.NewHeader(common.NewHash([]byte{0}), genTrie.MustHash(), trie.EmptyHash, big.NewInt(0), types.Digest{}) + require.NoError(t, err) + return gen, genTrie, genesisHeader +} + +func newTestHandler(t *testing.T, withBABE, withGrandpa bool) *Handler { //nolint testDatadirPath, err := ioutil.TempDir("/tmp", "test-datadir-*") require.NoError(t, err) @@ -53,23 +100,13 @@ func newTestDigestHandler(t *testing.T, withBABE, withGrandpa bool) *DigestHandl err = stateSrvc.Start() require.NoError(t, err) - var bp *MockBlockProducer - if withBABE { - bp = new(MockBlockProducer) - blockC := make(chan types.Block) - bp.On("GetBlockChannel", nil).Return(blockC) - } - - verifier := new(MockVerifier) - verifier.On("SetOnDisabled", mock.Anything, mock.Anything).Return(nil) - - dh, err := NewDigestHandler(stateSrvc.Block, stateSrvc.Epoch, stateSrvc.Grandpa, nil, nil) + dh, err := NewHandler(stateSrvc.Block, stateSrvc.Epoch, stateSrvc.Grandpa) require.NoError(t, err) return dh } -func TestDigestHandler_GrandpaScheduledChange(t *testing.T) { - handler := newTestDigestHandler(t, false, true) +func TestHandler_GrandpaScheduledChange(t *testing.T) { + handler := newTestHandler(t, false, true) handler.Start() defer handler.Stop() @@ -95,21 +132,21 @@ func TestDigestHandler_GrandpaScheduledChange(t *testing.T) { Number: big.NewInt(1), } - err = handler.HandleConsensusDigest(d, header) + err = handler.handleConsensusDigest(d, header) require.NoError(t, err) headers := addTestBlocksToState(t, 2, handler.blockState) for _, h := range headers { - handler.blockState.SetFinalizedHash(h.Hash(), 0, 0) + handler.blockState.(*state.BlockState).SetFinalizedHash(h.Hash(), 0, 0) } // authorities should change on start of block 3 from start headers = addTestBlocksToState(t, 1, handler.blockState) for _, h := range headers { - handler.blockState.SetFinalizedHash(h.Hash(), 0, 0) + handler.blockState.(*state.BlockState).SetFinalizedHash(h.Hash(), 0, 0) } - time.Sleep(time.Millisecond * 100) + time.Sleep(time.Millisecond * 500) setID, err := handler.grandpaState.(*state.GrandpaState).GetCurrentSetID() require.NoError(t, err) require.Equal(t, uint64(1), setID) @@ -121,8 +158,8 @@ func TestDigestHandler_GrandpaScheduledChange(t *testing.T) { require.Equal(t, expected, auths) } -func TestDigestHandler_GrandpaForcedChange(t *testing.T) { - handler := newTestDigestHandler(t, false, true) +func TestHandler_GrandpaForcedChange(t *testing.T) { + handler := newTestHandler(t, false, true) handler.Start() defer handler.Stop() @@ -148,7 +185,7 @@ func TestDigestHandler_GrandpaForcedChange(t *testing.T) { Number: big.NewInt(1), } - err = handler.HandleConsensusDigest(d, header) + err = handler.handleConsensusDigest(d, header) require.NoError(t, err) addTestBlocksToState(t, 3, handler.blockState) @@ -168,8 +205,8 @@ func TestDigestHandler_GrandpaForcedChange(t *testing.T) { require.Equal(t, expected, auths) } -func TestDigestHandler_GrandpaPauseAndResume(t *testing.T) { - handler := newTestDigestHandler(t, false, true) +func TestHandler_GrandpaPauseAndResume(t *testing.T) { + handler := newTestHandler(t, false, true) handler.Start() defer handler.Stop() @@ -185,7 +222,7 @@ func TestDigestHandler_GrandpaPauseAndResume(t *testing.T) { Data: data, } - err = handler.HandleConsensusDigest(d, nil) + err = handler.handleConsensusDigest(d, nil) require.NoError(t, err) nextPause, err := handler.grandpaState.(*state.GrandpaState).GetNextPause() require.NoError(t, err) @@ -193,7 +230,7 @@ func TestDigestHandler_GrandpaPauseAndResume(t *testing.T) { headers := addTestBlocksToState(t, 3, handler.blockState) for _, h := range headers { - handler.blockState.SetFinalizedHash(h.Hash(), 0, 0) + handler.blockState.(*state.BlockState).SetFinalizedHash(h.Hash(), 0, 0) } time.Sleep(time.Millisecond * 100) @@ -211,7 +248,7 @@ func TestDigestHandler_GrandpaPauseAndResume(t *testing.T) { Data: data, } - err = handler.HandleConsensusDigest(d, nil) + err = handler.handleConsensusDigest(d, nil) require.NoError(t, err) addTestBlocksToState(t, 3, handler.blockState) @@ -224,7 +261,7 @@ func TestDigestHandler_GrandpaPauseAndResume(t *testing.T) { } func TestNextGrandpaAuthorityChange_OneChange(t *testing.T) { - handler := newTestDigestHandler(t, false, true) + handler := newTestHandler(t, false, true) handler.Start() defer handler.Stop() @@ -245,7 +282,7 @@ func TestNextGrandpaAuthorityChange_OneChange(t *testing.T) { Number: big.NewInt(1), } - err = handler.HandleConsensusDigest(d, header) + err = handler.handleConsensusDigest(d, header) require.NoError(t, err) next := handler.NextGrandpaAuthorityChange() @@ -260,7 +297,7 @@ func TestNextGrandpaAuthorityChange_OneChange(t *testing.T) { } func TestNextGrandpaAuthorityChange_MultipleChanges(t *testing.T) { - handler := newTestDigestHandler(t, false, true) + handler := newTestHandler(t, false, true) handler.Start() defer handler.Stop() @@ -285,7 +322,7 @@ func TestNextGrandpaAuthorityChange_MultipleChanges(t *testing.T) { Number: big.NewInt(1), } - err = handler.HandleConsensusDigest(d, header) + err = handler.handleConsensusDigest(d, header) require.NoError(t, err) nextSetID := uint64(1) @@ -311,7 +348,7 @@ func TestNextGrandpaAuthorityChange_MultipleChanges(t *testing.T) { Data: data, } - err = handler.HandleConsensusDigest(d, header) + err = handler.handleConsensusDigest(d, header) require.NoError(t, err) next := handler.NextGrandpaAuthorityChange() @@ -324,22 +361,12 @@ func TestNextGrandpaAuthorityChange_MultipleChanges(t *testing.T) { require.Equal(t, expected, auths) } -func TestDigestHandler_HandleBABEOnDisabled(t *testing.T) { - handler := newTestDigestHandler(t, true, false) - - babemock := new(MockBlockProducer) - babemock.On("SetOnDisabled", uint32(7)) - +func TestHandler_HandleBABEOnDisabled(t *testing.T) { + handler := newTestHandler(t, true, false) header := &types.Header{ Number: big.NewInt(1), } - verifier := new(MockVerifier) - verifier.On("SetOnDisabled", uint32(7), header).Return(nil) - - handler.babe = babemock - handler.verifier = verifier - digest := &types.BABEOnDisabled{ ID: 7, } @@ -352,11 +379,8 @@ func TestDigestHandler_HandleBABEOnDisabled(t *testing.T) { Data: data, } - err = handler.HandleConsensusDigest(d, header) - + err = handler.handleConsensusDigest(d, header) require.NoError(t, err) - - babemock.AssertCalled(t, "SetOnDisabled", uint32(7)) } func createHeaderWithPreDigest(slotNumber uint64) *types.Header { @@ -372,8 +396,8 @@ func createHeaderWithPreDigest(slotNumber uint64) *types.Header { } } -func TestDigestHandler_HandleNextEpochData(t *testing.T) { - handler := newTestDigestHandler(t, true, false) +func TestHandler_HandleNextEpochData(t *testing.T) { + handler := newTestHandler(t, true, false) handler.Start() defer handler.Stop() @@ -405,7 +429,7 @@ func TestDigestHandler_HandleNextEpochData(t *testing.T) { header := createHeaderWithPreDigest(10) - err = handler.HandleConsensusDigest(d, header) + err = handler.handleConsensusDigest(d, header) require.NoError(t, err) stored, err := handler.epochState.(*state.EpochState).GetEpochData(1) @@ -415,8 +439,8 @@ func TestDigestHandler_HandleNextEpochData(t *testing.T) { require.Equal(t, res, stored) } -func TestDigestHandler_HandleNextConfigData(t *testing.T) { - handler := newTestDigestHandler(t, true, false) +func TestHandler_HandleNextConfigData(t *testing.T) { + handler := newTestHandler(t, true, false) handler.Start() defer handler.Stop() @@ -436,7 +460,7 @@ func TestDigestHandler_HandleNextConfigData(t *testing.T) { header := createHeaderWithPreDigest(10) - err = handler.HandleConsensusDigest(d, header) + err = handler.handleConsensusDigest(d, header) require.NoError(t, err) stored, err := handler.epochState.(*state.EpochState).GetConfigData(1) diff --git a/dot/digest/interface.go b/dot/digest/interface.go new file mode 100644 index 0000000000..f3df3c7f60 --- /dev/null +++ b/dot/digest/interface.go @@ -0,0 +1,49 @@ +// 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 digest + +import ( + "math/big" + + "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/grandpa" +) + +// BlockState interface for block state methods +type BlockState interface { + BestBlockHeader() (*types.Header, error) + RegisterImportedChannel(ch chan<- *types.Block) (byte, error) + UnregisterImportedChannel(id byte) + RegisterFinalizedChannel(ch chan<- *types.FinalisationInfo) (byte, error) + UnregisterFinalizedChannel(id byte) +} + +// EpochState is the interface for state.EpochState +type EpochState interface { + GetEpochForBlock(header *types.Header) (uint64, error) + SetEpochData(epoch uint64, info *types.EpochData) error + SetConfigData(epoch uint64, info *types.ConfigData) error +} + +// GrandpaState is the interface for the state.GrandpaState +type GrandpaState interface { + SetNextChange(authorities []*grandpa.Voter, number *big.Int) error + IncrementSetID() error + SetNextPause(number *big.Int) error + SetNextResume(number *big.Int) error + GetCurrentSetID() (uint64, error) +} diff --git a/dot/node.go b/dot/node.go index 4da6ec9348..04572047bc 100644 --- a/dot/node.go +++ b/dot/node.go @@ -227,8 +227,6 @@ func NewNode(cfg *Config, ks *keystore.GlobalKeystore, stopFunc func()) (*Node, return nil, ErrNoKeysProvided } - // Node Services - logger.Info( "πŸ•ΈοΈ initialising node services...", "name", cfg.Global.Name, @@ -236,20 +234,16 @@ func NewNode(cfg *Config, ks *keystore.GlobalKeystore, stopFunc func()) (*Node, "basepath", cfg.Global.BasePath, ) - var nodeSrvcs []services.Service - - // State Service + var ( + nodeSrvcs []services.Service + networkSrvc *network.Service + ) - // create state service and append state service to node services stateSrvc, err := createStateService(cfg) - if err != nil { return nil, fmt.Errorf("failed to create state service: %s", err) } - // Network Service - var networkSrvc *network.Service - // check if network service is enabled if enabled := networkServiceEnabled(cfg); enabled { // create network service and append network service to node services @@ -274,65 +268,51 @@ func NewNode(cfg *Config, ks *keystore.GlobalKeystore, stopFunc func()) (*Node, return nil, err } - // create BABE service - bp, err := createBABEService(cfg, rt, stateSrvc, ks.Babe) + dh, err := createDigestHandler(stateSrvc) if err != nil { return nil, err } + nodeSrvcs = append(nodeSrvcs, dh) - nodeSrvcs = append(nodeSrvcs, bp) + coreSrvc, err := createCoreService(cfg, rt, ks, stateSrvc, networkSrvc, dh) + if err != nil { + return nil, fmt.Errorf("failed to create core service: %s", err) + } + nodeSrvcs = append(nodeSrvcs, coreSrvc) - dh, err := createDigestHandler(stateSrvc, bp, ver) + bp, err := createBABEService(cfg, rt, stateSrvc, ks.Babe, coreSrvc) if err != nil { return nil, err } - nodeSrvcs = append(nodeSrvcs, dh) + nodeSrvcs = append(nodeSrvcs, bp) - // create GRANDPA service fg, err := createGRANDPAService(cfg, rt, stateSrvc, dh, ks.Gran, networkSrvc) if err != nil { return nil, err } nodeSrvcs = append(nodeSrvcs, fg) - // Syncer - syncer, err := newSyncService(cfg, stateSrvc, bp, fg, dh, ver, rt) + syncer, err := newSyncService(cfg, stateSrvc, fg, ver, rt, coreSrvc) if err != nil { return nil, err } - // Core Service - - // create core service and append core service to node services - coreSrvc, err := createCoreService(cfg, bp, ver, rt, ks, stateSrvc, networkSrvc) - if err != nil { - return nil, fmt.Errorf("failed to create core service: %s", err) - } - nodeSrvcs = append(nodeSrvcs, coreSrvc) - if networkSrvc != nil { networkSrvc.SetSyncer(syncer) networkSrvc.SetTransactionHandler(coreSrvc) } - // System Service - - // create system service and append to node services sysSrvc, err := createSystemService(&cfg.System, stateSrvc) if err != nil { return nil, fmt.Errorf("failed to create system service: %s", err) } nodeSrvcs = append(nodeSrvcs, sysSrvc) - // RPC Service - // check if rpc service is enabled if enabled := cfg.RPC.Enabled; enabled { - // create rpc service and append rpc service to node services rpcSrvc := createRPCService(cfg, stateSrvc, coreSrvc, networkSrvc, bp, rt, sysSrvc) nodeSrvcs = append(nodeSrvcs, rpcSrvc) } else { - // do not create or append rpc service if rpc service is not enabled logger.Debug("rpc service disabled by default", "rpc", enabled) } diff --git a/dot/node_test.go b/dot/node_test.go index 455bebb1d4..253051cc2f 100644 --- a/dot/node_test.go +++ b/dot/node_test.go @@ -188,7 +188,9 @@ func TestStartNode(t *testing.T) { require.NoError(t, err) go func() { - time.Sleep(time.Second) + // TODO: need to wait until all services are started so that wg.Add is called, otherwise + // will call wg.Done before the counter is at 1 + time.Sleep(time.Second * 15) node.Stop() }() diff --git a/dot/rpc/modules/api.go b/dot/rpc/modules/api.go index ce7356d1d6..5e459b8e83 100644 --- a/dot/rpc/modules/api.go +++ b/dot/rpc/modules/api.go @@ -70,7 +70,6 @@ type CoreAPI interface { InsertKey(kp crypto.Keypair) HasKey(pubKeyStr string, keyType string) (bool, error) GetRuntimeVersion(bhash *common.Hash) (runtime.Version, error) - IsBlockProducer() bool HandleSubmittedExtrinsic(types.Extrinsic) error GetMetadata(bhash *common.Hash) ([]byte, error) } diff --git a/dot/rpc/modules/author.go b/dot/rpc/modules/author.go index 5b9d68c4bd..02dc2acf0e 100644 --- a/dot/rpc/modules/author.go +++ b/dot/rpc/modules/author.go @@ -165,7 +165,7 @@ func (cm *AuthorModule) SubmitExtrinsic(r *http.Request, req *Extrinsic, res *Ex return err } ext := types.Extrinsic(extBytes) - cm.logger.Trace("[rpc]", "extrinsic", ext) + cm.logger.Crit("[rpc]", "extrinsic", ext) err = cm.coreAPI.HandleSubmittedExtrinsic(ext) *res = ExtrinsicHashResponse(ext.Hash().String()) diff --git a/dot/rpc/modules/author_test.go b/dot/rpc/modules/author_test.go index ff3f48f785..ef4e1bf193 100644 --- a/dot/rpc/modules/author_test.go +++ b/dot/rpc/modules/author_test.go @@ -276,21 +276,23 @@ func newCoreService(t *testing.T, srvc *state.Service) *core.Service { } cfg := &core.Config{ - Runtime: rt, - Keystore: ks, - TransactionState: srvc.Transaction, - IsBlockProducer: false, - BlockState: srvc.Block, - StorageState: srvc.Storage, - EpochState: srvc.Epoch, - Network: &mockNetwork{}, + Runtime: rt, + Keystore: ks, + TransactionState: srvc.Transaction, + BlockState: srvc.Block, + StorageState: srvc.Storage, + EpochState: srvc.Epoch, + Network: &mockNetwork{}, + CodeSubstitutedState: srvc.Base, } return core.NewTestService(t, cfg) } func setupAuthModule(t *testing.T, txq *state.TransactionState) *AuthorModule { + fmt.Println("calling setupAuthModule") cs := newCoreService(t, nil) + fmt.Println("called newCoreService") rt := wasmer.NewTestInstance(t, runtime.NODE_RUNTIME) t.Cleanup(func() { rt.Stop() diff --git a/dot/rpc/modules/chain_test.go b/dot/rpc/modules/chain_test.go index 595f723e47..a6d2ad6279 100644 --- a/dot/rpc/modules/chain_test.go +++ b/dot/rpc/modules/chain_test.go @@ -265,7 +265,13 @@ func TestChainGetFinalizedHeadByRound(t *testing.T) { expected := genesisHeader.Hash() require.Equal(t, common.BytesToHex(expected[:]), res) - testhash := common.Hash{1, 2, 3, 4} + header := &types.Header{ + Number: big.NewInt(1), + } + err = state.Block.SetHeader(header) + require.NoError(t, err) + + testhash := header.Hash() err = state.Block.SetFinalizedHash(testhash, 77, 1) require.NoError(t, err) diff --git a/dot/rpc/modules/dev_test.go b/dot/rpc/modules/dev_test.go index 258cdd2e54..d018587658 100644 --- a/dot/rpc/modules/dev_test.go +++ b/dot/rpc/modules/dev_test.go @@ -7,6 +7,7 @@ import ( "github.com/ChainSafe/gossamer/dot/state" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/babe" + babemocks "github.com/ChainSafe/gossamer/lib/babe/mocks" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/crypto/sr25519" "github.com/ChainSafe/gossamer/lib/keystore" @@ -47,11 +48,12 @@ func newBABEService(t *testing.T) *babe.Service { tt.Put(common.MustHexToBytes("0x886726f904d8372fdabb7707870c2fad"), common.MustHexToBytes("0x24d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48010000000000000090b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe220100000000000000306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc200100000000000000e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e01000000000000001cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c01000000000000004603307f855321776922daeea21ee31720388d097cdaac66f05a6f8462b317570100000000000000be1d9d59de1283380100550a7b024501cb62d6cc40e3db35fcc5cf341814986e01000000000000001206960f920a23f7f4c43cc9081ec2ed0721f31a9bef2c10fd7602e16e08a32c0100000000000000")) cfg := &babe.ServiceConfig{ - BlockState: bs, - EpochState: es, - Keypair: kr.Alice().(*sr25519.Keypair), - Runtime: rt, - IsDev: true, + BlockState: bs, + EpochState: es, + Keypair: kr.Alice().(*sr25519.Keypair), + Runtime: rt, + IsDev: true, + BlockImportHandler: new(babemocks.BlockImportHandler), } babe, err := babe.NewService(cfg) diff --git a/dot/rpc/subscription/websocket.go b/dot/rpc/subscription/websocket.go index 911c2aba7c..ab1b367449 100644 --- a/dot/rpc/subscription/websocket.go +++ b/dot/rpc/subscription/websocket.go @@ -347,9 +347,7 @@ func (c *WSConn) initExtrinsicWatch(reqID float64, params interface{}) (uint, er // TODO (ed) since HandleSubmittedExtrinsic has been called we assume the extrinsic is in the tx queue // should we add a channel to tx queue so we're notified when it's in the queue (See issue #1535) - if c.CoreAPI.IsBlockProducer() { - c.safeSend(newSubscriptionResponse(AuthorExtrinsicUpdates, esl.subID, "ready")) - } + c.safeSend(newSubscriptionResponse(AuthorExtrinsicUpdates, esl.subID, "ready")) // todo (ed) determine which peer extrinsic has been broadcast to, and set status return esl.subID, err diff --git a/dot/services.go b/dot/services.go index 6b7c5697b1..fcd6c48073 100644 --- a/dot/services.go +++ b/dot/services.go @@ -23,6 +23,7 @@ import ( "github.com/ChainSafe/chaindb" "github.com/ChainSafe/gossamer/dot/core" + "github.com/ChainSafe/gossamer/dot/digest" "github.com/ChainSafe/gossamer/dot/network" "github.com/ChainSafe/gossamer/dot/rpc" "github.com/ChainSafe/gossamer/dot/rpc/modules" @@ -185,7 +186,7 @@ func createRuntime(cfg *Config, st *state.Service, ks *keystore.GlobalKeystore, return rt, nil } -func createBABEService(cfg *Config, rt runtime.Instance, st *state.Service, ks keystore.Keystore) (*babe.Service, error) { +func createBABEService(cfg *Config, rt runtime.Instance, st *state.Service, ks keystore.Keystore, cs *core.Service) (*babe.Service, error) { logger.Info( "creating BABE service...", "authority", cfg.Core.BabeAuthority, @@ -202,16 +203,17 @@ func createBABEService(cfg *Config, rt runtime.Instance, st *state.Service, ks k } bcfg := &babe.ServiceConfig{ - LogLvl: cfg.Log.BlockProducerLvl, - Runtime: rt, - BlockState: st.Block, - StorageState: st.Storage, - TransactionState: st.Transaction, - EpochState: st.Epoch, - EpochLength: cfg.Core.EpochLength, - SlotDuration: cfg.Core.SlotDuration, // TODO: remove this, should only be modified via runtime constant - Authority: cfg.Core.BabeAuthority, - IsDev: cfg.Global.ID == "dev", + LogLvl: cfg.Log.BlockProducerLvl, + Runtime: rt, + BlockState: st.Block, + StorageState: st.Storage, + TransactionState: st.Transaction, + EpochState: st.Epoch, + BlockImportHandler: cs, + EpochLength: cfg.Core.EpochLength, + SlotDuration: cfg.Core.SlotDuration, // TODO: remove this, should only be modified via runtime constant + Authority: cfg.Core.BabeAuthority, + IsDev: cfg.Global.ID == "dev", } if cfg.Core.BabeAuthority { @@ -231,25 +233,35 @@ func createBABEService(cfg *Config, rt runtime.Instance, st *state.Service, ks k // Core Service // createCoreService creates the core service from the provided core configuration -func createCoreService(cfg *Config, bp core.BlockProducer, verifier *babe.VerificationManager, rt runtime.Instance, ks *keystore.GlobalKeystore, stateSrvc *state.Service, net *network.Service) (*core.Service, error) { +func createCoreService(cfg *Config, rt runtime.Instance, ks *keystore.GlobalKeystore, st *state.Service, net *network.Service, dh *digest.Handler) (*core.Service, error) { logger.Debug( "creating core service...", "authority", cfg.Core.Roles == types.AuthorityRole, ) + genesisData, err := st.Base.LoadGenesisData() + if err != nil { + return nil, err + } + + codeSubs := make(map[common.Hash]string) + for k, v := range genesisData.CodeSubstitutes { + codeSubs[common.MustHexToHash(k)] = v + } + // set core configuration coreConfig := &core.Config{ - LogLvl: cfg.Log.CoreLvl, - BlockState: stateSrvc.Block, - EpochState: stateSrvc.Epoch, - StorageState: stateSrvc.Storage, - TransactionState: stateSrvc.Transaction, - BlockProducer: bp, - Keystore: ks, - Runtime: rt, - IsBlockProducer: cfg.Core.BabeAuthority, - Verifier: verifier, - Network: net, + LogLvl: cfg.Log.CoreLvl, + BlockState: st.Block, + EpochState: st.Epoch, + StorageState: st.Storage, + TransactionState: st.Transaction, + Keystore: ks, + Runtime: rt, + Network: net, + DigestHandler: dh, + CodeSubstitutes: codeSubs, + CodeSubstitutedState: st.Base, } // create new core service @@ -353,7 +365,7 @@ func createSystemService(cfg *types.SystemInfo, stateSrvc *state.Service) (*syst } // createGRANDPAService creates a new GRANDPA service -func createGRANDPAService(cfg *Config, rt runtime.Instance, st *state.Service, dh *core.DigestHandler, ks keystore.Keystore, net *network.Service) (*grandpa.Service, error) { +func createGRANDPAService(cfg *Config, rt runtime.Instance, st *state.Service, dh *digest.Handler, ks keystore.Keystore, net *network.Service) (*grandpa.Service, error) { ad, err := rt.GrandpaAuthorities() if err != nil { return nil, err @@ -396,32 +408,21 @@ func createBlockVerifier(st *state.Service) (*babe.VerificationManager, error) { return ver, nil } -func newSyncService(cfg *Config, st *state.Service, bp sync.BlockProducer, fg sync.FinalityGadget, dh *core.DigestHandler, verifier *babe.VerificationManager, rt runtime.Instance) (*sync.Service, error) { - genesisData, err := st.Base.LoadGenesisData() - if err != nil { - return nil, err - } - codeSubs := make(map[common.Hash]string) - for k, v := range genesisData.CodeSubstitutes { - codeSubs[common.MustHexToHash(k)] = v - } +func newSyncService(cfg *Config, st *state.Service, fg sync.FinalityGadget, verifier *babe.VerificationManager, rt runtime.Instance, cs *core.Service) (*sync.Service, error) { syncCfg := &sync.Config{ - LogLvl: cfg.Log.SyncLvl, - BlockState: st.Block, - StorageState: st.Storage, - TransactionState: st.Transaction, - BlockProducer: bp, - FinalityGadget: fg, - Verifier: verifier, - Runtime: rt, - DigestHandler: dh, - CodeSubstitutes: codeSubs, - CodeSubstitutedState: st.Base, + LogLvl: cfg.Log.SyncLvl, + BlockState: st.Block, + StorageState: st.Storage, + TransactionState: st.Transaction, + FinalityGadget: fg, + Verifier: verifier, + Runtime: rt, + BlockImportHandler: cs, } return sync.NewService(syncCfg) } -func createDigestHandler(st *state.Service, bp core.BlockProducer, verifier *babe.VerificationManager) (*core.DigestHandler, error) { - return core.NewDigestHandler(st.Block, st.Epoch, st.Grandpa, bp, verifier) +func createDigestHandler(st *state.Service) (*digest.Handler, error) { + return digest.NewHandler(st.Block, st.Epoch, st.Grandpa) } diff --git a/dot/services_test.go b/dot/services_test.go index adb9a7ea36..67ed1d4638 100644 --- a/dot/services_test.go +++ b/dot/services_test.go @@ -23,7 +23,6 @@ import ( "time" "github.com/ChainSafe/gossamer/dot/network" - "github.com/ChainSafe/gossamer/dot/sync" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/lib/utils" @@ -86,8 +85,11 @@ func TestCreateCoreService(t *testing.T) { rt, err := createRuntime(cfg, stateSrvc, ks, networkSrvc) require.NoError(t, err) - coreSrvc, err := createCoreService(cfg, nil, nil, rt, ks, stateSrvc, networkSrvc) - require.Nil(t, err) + dh, err := createDigestHandler(stateSrvc) + require.NoError(t, err) + + coreSrvc, err := createCoreService(cfg, rt, ks, stateSrvc, networkSrvc, dh) + require.NoError(t, err) require.NotNil(t, coreSrvc) } @@ -103,7 +105,7 @@ func TestCreateBlockVerifier(t *testing.T) { cfg.Init.Genesis = genFile.Name() err := InitNode(cfg) - require.Nil(t, err) + require.NoError(t, err) stateSrvc, err := createStateService(cfg) require.NoError(t, err) @@ -124,7 +126,7 @@ func TestCreateSyncService(t *testing.T) { cfg.Init.Genesis = genFile.Name() err := InitNode(cfg) - require.Nil(t, err) + require.NoError(t, err) stateSrvc, err := createStateService(cfg) require.NoError(t, err) @@ -137,7 +139,13 @@ func TestCreateSyncService(t *testing.T) { ver, err := createBlockVerifier(stateSrvc) require.NoError(t, err) - _, err = newSyncService(cfg, stateSrvc, sync.NewMockBlockProducer(), nil, nil, ver, rt) + dh, err := createDigestHandler(stateSrvc) + require.NoError(t, err) + + coreSrvc, err := createCoreService(cfg, rt, ks, stateSrvc, &network.Service{}, dh) + require.NoError(t, err) + + _, err = newSyncService(cfg, stateSrvc, nil, ver, rt, coreSrvc) require.NoError(t, err) } @@ -154,13 +162,13 @@ func TestCreateNetworkService(t *testing.T) { cfg.Init.Genesis = genFile.Name() err := InitNode(cfg) - require.Nil(t, err) + require.NoError(t, err) stateSrvc, err := createStateService(cfg) - require.Nil(t, err) + require.NoError(t, err) networkSrvc, err := createNetworkService(cfg, stateSrvc) - require.Nil(t, err) + require.NoError(t, err) // TODO: improve dot tests #687 require.NotNil(t, networkSrvc) @@ -183,10 +191,10 @@ func TestCreateRPCService(t *testing.T) { cfg.Init.Genesis = genFile.Name() err := InitNode(cfg) - require.Nil(t, err) + require.NoError(t, err) stateSrvc, err := createStateService(cfg) - require.Nil(t, err) + require.NoError(t, err) networkSrvc := &network.Service{} @@ -197,8 +205,11 @@ func TestCreateRPCService(t *testing.T) { rt, err := createRuntime(cfg, stateSrvc, ks, networkSrvc) require.NoError(t, err) - coreSrvc, err := createCoreService(cfg, nil, nil, rt, ks, stateSrvc, networkSrvc) - require.Nil(t, err) + dh, err := createDigestHandler(stateSrvc) + require.NoError(t, err) + + coreSrvc, err := createCoreService(cfg, rt, ks, stateSrvc, networkSrvc, dh) + require.NoError(t, err) sysSrvc, err := createSystemService(&cfg.System, stateSrvc) require.NoError(t, err) @@ -221,20 +232,26 @@ func TestCreateBABEService(t *testing.T) { cfg.Init.Genesis = genFile.Name() err := InitNode(cfg) - require.Nil(t, err) + require.NoError(t, err) stateSrvc, err := createStateService(cfg) - require.Nil(t, err) + require.NoError(t, err) ks := keystore.NewGlobalKeystore() kr, err := keystore.NewSr25519Keyring() - require.Nil(t, err) + require.NoError(t, err) ks.Babe.Insert(kr.Alice()) rt, err := createRuntime(cfg, stateSrvc, ks, &network.Service{}) require.NoError(t, err) - bs, err := createBABEService(cfg, rt, stateSrvc, ks.Babe) + dh, err := createDigestHandler(stateSrvc) + require.NoError(t, err) + + coreSrvc, err := createCoreService(cfg, rt, ks, stateSrvc, &network.Service{}, dh) + require.NoError(t, err) + + bs, err := createBABEService(cfg, rt, stateSrvc, ks.Babe, coreSrvc) require.NoError(t, err) require.NotNil(t, bs) } @@ -266,7 +283,7 @@ func TestCreateGrandpaService(t *testing.T) { rt, err := createRuntime(cfg, stateSrvc, ks, &network.Service{}) require.NoError(t, err) - dh, err := createDigestHandler(stateSrvc, nil, nil) + dh, err := createDigestHandler(stateSrvc) require.NoError(t, err) gs, err := createGRANDPAService(cfg, rt, stateSrvc, dh, ks.Gran, &network.Service{}) @@ -318,7 +335,10 @@ func TestNewWebSocketServer(t *testing.T) { rt, err := createRuntime(cfg, stateSrvc, ks, networkSrvc) require.NoError(t, err) - coreSrvc, err := createCoreService(cfg, nil, nil, rt, ks, stateSrvc, networkSrvc) + dh, err := createDigestHandler(stateSrvc) + require.NoError(t, err) + + coreSrvc, err := createCoreService(cfg, rt, ks, stateSrvc, networkSrvc, dh) require.Nil(t, err) sysSrvc, err := createSystemService(&cfg.System, stateSrvc) diff --git a/dot/state/block.go b/dot/state/block.go index 4ff653f9ce..82b7aeab49 100644 --- a/dot/state/block.go +++ b/dot/state/block.go @@ -402,6 +402,11 @@ func (bs *BlockState) SetFinalizedHash(hash common.Hash, round, setID uint64) er bs.Lock() defer bs.Unlock() + has, _ := bs.HasHeader(hash) + if !has { + return fmt.Errorf("cannot finalise unknown block %s", hash) + } + go bs.notifyFinalized(hash, round, setID) if round > 0 { err := bs.SetRound(round) diff --git a/dot/state/block_test.go b/dot/state/block_test.go index fc54bbceea..0f19f5c224 100644 --- a/dot/state/block_test.go +++ b/dot/state/block_test.go @@ -274,6 +274,9 @@ func TestFinalizedHash(t *testing.T) { require.Equal(t, testGenesisHeader.Hash(), h) testhash := common.Hash{1, 2, 3, 4} + err = bs.db.Put(headerKey(testhash), []byte{}) + require.NoError(t, err) + err = bs.SetFinalizedHash(testhash, 1, 1) require.NoError(t, err) diff --git a/dot/sync/errors.go b/dot/sync/errors.go index 708b9ee3f4..89d33e03cb 100644 --- a/dot/sync/errors.go +++ b/dot/sync/errors.go @@ -21,32 +21,25 @@ import ( "fmt" ) -// ErrNilBlockState is returned when BlockState is nil -var ErrNilBlockState = errors.New("cannot have nil BlockState") +var ( + errNilBlockState = errors.New("cannot have nil BlockState") + errNilStorageState = errors.New("cannot have nil StorageState") + errNilVerifier = errors.New("cannot have nil Verifier") + errNilRuntime = errors.New("cannot have nil runtime") + errNilBlockImportHandler = errors.New("cannot have nil BlockImportHandler") -// ErrNilStorageState is returned when StorageState is nil -var ErrNilStorageState = errors.New("cannot have nil StorageState") + // ErrNilBlockData is returned when trying to process a BlockResponseMessage with nil BlockData + ErrNilBlockData = errors.New("got nil BlockData") -// ErrNilVerifier is returned when trying to instantiate a Syncer without a Verifier -var ErrNilVerifier = errors.New("cannot have nil Verifier") + // ErrServiceStopped is returned when the service has been stopped + ErrServiceStopped = errors.New("service has been stopped") -// ErrNilRuntime is returned when trying to instantiate a Service or Syncer without a runtime -var ErrNilRuntime = errors.New("cannot have nil runtime") + // ErrInvalidBlock is returned when a block cannot be verified + ErrInvalidBlock = errors.New("could not verify block") -// ErrNilBlockData is returned when trying to process a BlockResponseMessage with nil BlockData -var ErrNilBlockData = errors.New("got nil BlockData") - -// ErrServiceStopped is returned when the service has been stopped -var ErrServiceStopped = errors.New("service has been stopped") - -// ErrInvalidBlock is returned when a block cannot be verified -var ErrInvalidBlock = errors.New("could not verify block") - -// ErrInvalidBlockRequest is returned when an invalid block request is received -var ErrInvalidBlockRequest = errors.New("invalid block request") - -// ErrEmptyRuntimeCode is returned when the storage :code is empty -var ErrEmptyRuntimeCode = errors.New("new :code is empty") + // ErrInvalidBlockRequest is returned when an invalid block request is received + ErrInvalidBlockRequest = errors.New("invalid block request") +) // ErrNilChannel is returned if a channel is nil func ErrNilChannel(s string) error { diff --git a/dot/sync/interface.go b/dot/sync/interface.go index f88bbd11cd..f294d9fe09 100644 --- a/dot/sync/interface.go +++ b/dot/sync/interface.go @@ -21,7 +21,6 @@ import ( "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" - "github.com/ChainSafe/gossamer/lib/runtime" rtstorage "github.com/ChainSafe/gossamer/lib/runtime/storage" ) @@ -46,6 +45,7 @@ type BlockState interface { SetFinalizedHash(hash common.Hash, round, setID uint64) error AddBlockToBlockTree(header *types.Header) error GetHashByNumber(*big.Int) (common.Hash, error) + GetBlockByHash(common.Hash) (*types.Block, error) } // StorageState is the interface for the storage state @@ -67,18 +67,6 @@ type TransactionState interface { RemoveExtrinsic(ext types.Extrinsic) } -// BlockProducer is the interface that a block production service must implement -type BlockProducer interface { - Pause() error - Resume() error - SetRuntime(rt runtime.Instance) -} - -// DigestHandler is the interface for the consensus digest handler -type DigestHandler interface { - HandleConsensusDigest(*types.ConsensusDigest, *types.Header) error -} - // Verifier deals with block verification type Verifier interface { VerifyBlock(header *types.Header) error @@ -88,3 +76,8 @@ type Verifier interface { type FinalityGadget interface { VerifyBlockJustification([]byte) error } + +// BlockImportHandler is the interface for the handler of newly imported blocks +type BlockImportHandler interface { + HandleBlockImport(block *types.Block, state *rtstorage.TrieState) error +} diff --git a/dot/sync/mocks/block_import_handler.go b/dot/sync/mocks/block_import_handler.go new file mode 100644 index 0000000000..8da0026722 --- /dev/null +++ b/dot/sync/mocks/block_import_handler.go @@ -0,0 +1,29 @@ +// Code generated by mockery v2.8.0. DO NOT EDIT. + +package sync + +import ( + storage "github.com/ChainSafe/gossamer/lib/runtime/storage" + mock "github.com/stretchr/testify/mock" + + types "github.com/ChainSafe/gossamer/dot/types" +) + +// MockBlockImportHandler is an autogenerated mock type for the BlockImportHandler type +type MockBlockImportHandler struct { + mock.Mock +} + +// HandleBlockImport provides a mock function with given fields: block, state +func (_m *MockBlockImportHandler) HandleBlockImport(block *types.Block, state *storage.TrieState) error { + ret := _m.Called(block, state) + + var r0 error + if rf, ok := ret.Get(0).(func(*types.Block, *storage.TrieState) error); ok { + r0 = rf(block, state) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/dot/sync/mocks/block_producer.go b/dot/sync/mocks/block_producer.go deleted file mode 100644 index 7d4d62556f..0000000000 --- a/dot/sync/mocks/block_producer.go +++ /dev/null @@ -1,46 +0,0 @@ -// Code generated by mockery v2.8.0. DO NOT EDIT. - -package sync - -import ( - runtime "github.com/ChainSafe/gossamer/lib/runtime" - mock "github.com/stretchr/testify/mock" -) - -// MockBlockProducer is an autogenerated mock type for the BlockProducer type -type MockBlockProducer struct { - mock.Mock -} - -// Pause provides a mock function with given fields: -func (_m *MockBlockProducer) Pause() error { - ret := _m.Called() - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Resume provides a mock function with given fields: -func (_m *MockBlockProducer) Resume() error { - ret := _m.Called() - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// SetRuntime provides a mock function with given fields: rt -func (_m *MockBlockProducer) SetRuntime(rt runtime.Instance) { - _m.Called(rt) -} diff --git a/dot/sync/syncer.go b/dot/sync/syncer.go index d67ef0327e..c6e3b5cf5b 100644 --- a/dot/sync/syncer.go +++ b/dot/sync/syncer.go @@ -27,9 +27,8 @@ import ( "github.com/ChainSafe/gossamer/dot/telemetry" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/blocktree" - "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/runtime" - rtstorage "github.com/ChainSafe/gossamer/lib/runtime/storage" + log "github.com/ChainSafe/log15" ) @@ -37,14 +36,12 @@ var logger = log.New("pkg", "sync") // Service deals with chain syncing by sending block request messages and watching for responses. type Service struct { - codeHash common.Hash // cached hash of runtime code - // State interfaces - blockState BlockState // retrieve our current head of chain from BlockState - storageState StorageState - transactionState TransactionState - blockProducer BlockProducer - finalityGadget FinalityGadget + blockState BlockState // retrieve our current head of chain from BlockState + storageState StorageState + transactionState TransactionState + finalityGadget FinalityGadget + blockImportHandler BlockImportHandler // Synchronisation variables synced bool @@ -53,75 +50,56 @@ type Service struct { // BABE verification verifier Verifier - - // Consensus digest handling - digestHandler DigestHandler - - // map of code substitutions keyed by block hash - codeSubstitute map[common.Hash]string - codeSubstitutedState CodeSubstitutedState } // Config is the configuration for the sync Service. type Config struct { - LogLvl log.Lvl - BlockState BlockState - StorageState StorageState - BlockProducer BlockProducer - FinalityGadget FinalityGadget - TransactionState TransactionState - Runtime runtime.Instance - Verifier Verifier - DigestHandler DigestHandler - CodeSubstitutes map[common.Hash]string - CodeSubstitutedState CodeSubstitutedState + LogLvl log.Lvl + BlockState BlockState + StorageState StorageState + FinalityGadget FinalityGadget + TransactionState TransactionState + BlockImportHandler BlockImportHandler + Runtime runtime.Instance + Verifier Verifier } // NewService returns a new *sync.Service func NewService(cfg *Config) (*Service, error) { if cfg.BlockState == nil { - return nil, ErrNilBlockState + return nil, errNilBlockState } if cfg.StorageState == nil { - return nil, ErrNilStorageState + return nil, errNilStorageState } if cfg.Verifier == nil { - return nil, ErrNilVerifier + return nil, errNilVerifier } if cfg.Runtime == nil { - return nil, ErrNilRuntime + return nil, errNilRuntime } - if cfg.BlockProducer == nil { - cfg.BlockProducer = NewMockBlockProducer() + if cfg.BlockImportHandler == nil { + return nil, errNilBlockImportHandler } handler := log.StreamHandler(os.Stdout, log.TerminalFormat()) handler = log.CallerFileHandler(handler) logger.SetHandler(log.LvlFilterHandler(cfg.LogLvl, handler)) - codeHash, err := cfg.StorageState.LoadCodeHash(nil) - if err != nil { - return nil, err - } - return &Service{ - codeHash: codeHash, - blockState: cfg.BlockState, - storageState: cfg.StorageState, - blockProducer: cfg.BlockProducer, - finalityGadget: cfg.FinalityGadget, - synced: true, - highestSeenBlock: big.NewInt(0), - transactionState: cfg.TransactionState, - runtime: cfg.Runtime, - verifier: cfg.Verifier, - digestHandler: cfg.DigestHandler, - codeSubstitute: cfg.CodeSubstitutes, - codeSubstitutedState: cfg.CodeSubstitutedState, + blockState: cfg.BlockState, + storageState: cfg.StorageState, + finalityGadget: cfg.FinalityGadget, + blockImportHandler: cfg.BlockImportHandler, + synced: true, + highestSeenBlock: big.NewInt(0), + transactionState: cfg.TransactionState, + runtime: cfg.Runtime, + verifier: cfg.Verifier, }, nil } @@ -203,33 +181,36 @@ func (s *Service) ProcessBlockData(data []*types.BlockData) (int, error) { // so when the node restarts it has blocks higher than what it thinks is the best, causing it not to sync logger.Debug("skipping block, already have", "hash", bd.Hash) - header, err := s.blockState.GetHeader(bd.Hash) //nolint + block, err := s.blockState.GetBlockByHash(bd.Hash) //nolint if err != nil { logger.Debug("failed to get header", "hash", bd.Hash, "error", err) return i, err } - err = s.blockState.AddBlockToBlockTree(header) + err = s.blockState.AddBlockToBlockTree(block.Header) if err != nil && !errors.Is(err, blocktree.ErrBlockExists) { logger.Warn("failed to add block to blocktree", "hash", bd.Hash, "error", err) return i, err } - // handle consensus digests for authority changes - if s.digestHandler != nil { - s.handleDigests(header) - } - if bd.Justification != nil && bd.Justification.Exists() { - logger.Debug("handling Justification...", "number", header.Number, "hash", bd.Hash) - s.handleJustification(header, bd.Justification.Value()) + logger.Debug("handling Justification...", "number", block.Header.Number, "hash", bd.Hash) + s.handleJustification(block.Header, bd.Justification.Value()) } - if err := s.handleCodeSubstitution(bd.Hash); err != nil { - logger.Warn("failed to handle code substitution", "error", err) + // TODO: this is probably unnecessary, since the state is already in the database + // however, this case shouldn't be hit often, since it's only hit if the node state + // is rewinded or if the node shuts down unexpectedly + state, err := s.storageState.TrieState(&block.Header.StateRoot) + if err != nil { + logger.Warn("failed to load state for block", "block", block.Header.Hash(), "error", err) return i, err } + if err := s.blockImportHandler.HandleBlockImport(block, state); err != nil { + logger.Warn("failed to handle block import", "error", err) + } + continue } @@ -359,45 +340,22 @@ 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, block.Header) - if err != nil { + if err = s.blockImportHandler.HandleBlockImport(block, ts); err != nil { return err } - logger.Trace("executed block and stored resulting state", "state root", ts.MustRoot()) - - // TODO: batch writes in AddBlock - err = s.blockState.AddBlock(block) - if err != nil { - if err == blocktree.ErrParentNotFound && block.Header.Number.Cmp(big.NewInt(0)) != 0 { - return err - } else if err == blocktree.ErrBlockExists || block.Header.Number.Cmp(big.NewInt(0)) == 0 { - // this is fine - } else { - return err - } - } else { - logger.Debug("πŸ”— imported block", "number", block.Header.Number, "hash", block.Header.Hash()) - err := telemetry.GetInstance().SendMessage(telemetry.NewTelemetryMessage( // nolint - telemetry.NewKeyValue("best", block.Header.Hash().String()), - telemetry.NewKeyValue("height", block.Header.Number.Uint64()), - telemetry.NewKeyValue("msg", "block.import"), - telemetry.NewKeyValue("origin", "NetworkInitialSync"))) - if err != nil { - logger.Debug("problem sending block.import telemetry message", "error", err) - } - } - // handle consensus digest for authority changes - if s.digestHandler != nil { - s.handleDigests(block.Header) - } + logger.Debug("πŸ”— imported block", "number", block.Header.Number, "hash", block.Header.Hash()) - err = s.handleCodeSubstitution(block.Header.Hash()) + err = telemetry.GetInstance().SendMessage(telemetry.NewTelemetryMessage( // nolint + telemetry.NewKeyValue("best", block.Header.Hash().String()), + telemetry.NewKeyValue("height", block.Header.Number.Uint64()), + telemetry.NewKeyValue("msg", "block.import"), + telemetry.NewKeyValue("origin", "NetworkInitialSync"))) if err != nil { - return err + logger.Trace("problem sending block.import telemetry message", "error", err) } - return s.handleRuntimeChanges(ts) + return nil } func (s *Service) handleJustification(header *types.Header, justification []byte) { @@ -426,103 +384,6 @@ func (s *Service) handleJustification(header *types.Header, justification []byte logger.Info("πŸ”¨ finalised block", "number", header.Number, "hash", header.Hash()) } -func (s *Service) handleRuntimeChanges(newState *rtstorage.TrieState) error { - currCodeHash, err := newState.LoadCodeHash() - if err != nil { - return err - } - - if bytes.Equal(s.codeHash[:], currCodeHash[:]) { - return nil - } - - logger.Info("πŸ”„ detected runtime code change, upgrading...", "block", s.blockState.BestBlockHash(), "previous code hash", s.codeHash, "new code hash", currCodeHash) - code := newState.LoadCode() - if len(code) == 0 { - return ErrEmptyRuntimeCode - } - - codeSubBlockHash := s.codeSubstitutedState.LoadCodeSubstitutedBlockHash() - - if !codeSubBlockHash.Equal(common.Hash{}) { - // don't do runtime change if using code substitution and runtime change spec version are equal - // (do a runtime change if code substituted and runtime spec versions are different, or code not substituted) - newVersion, err := s.runtime.CheckRuntimeVersion(code) // nolint - if err != nil { - logger.Debug("problem checking runtime version", "error", err) - return err - } - - previousVersion, _ := s.runtime.Version() - if previousVersion.SpecVersion() == newVersion.SpecVersion() { - return nil - } - - logger.Info("πŸ”„ detected runtime code change, upgrading...", "block", s.blockState.BestBlockHash(), - "previous code hash", s.codeHash, "new code hash", currCodeHash, - "previous spec version", previousVersion.SpecVersion(), "new spec version", newVersion.SpecVersion()) - } - - err = s.runtime.UpdateRuntimeCode(code) - if err != nil { - logger.Crit("failed to update runtime code", "error", err) - return err - } - - s.codeHash = currCodeHash - - err = s.codeSubstitutedState.StoreCodeSubstitutedBlockHash(common.Hash{}) - if err != nil { - logger.Error("failed to update code substituted block hash", "error", err) - return err - } - - return nil -} - -func (s *Service) handleCodeSubstitution(hash common.Hash) error { - value := s.codeSubstitute[hash] - if value == "" { - return nil - } - - logger.Info("πŸ”„ detected runtime code substitution, upgrading...", "block", hash) - code := common.MustHexToBytes(value) - if len(code) == 0 { - return ErrEmptyRuntimeCode - } - - err := s.runtime.UpdateRuntimeCode(code) - if err != nil { - logger.Crit("failed to substitute runtime code", "error", err) - return err - } - - err = s.codeSubstitutedState.StoreCodeSubstitutedBlockHash(hash) - if err != nil { - return err - } - - return nil -} - -func (s *Service) handleDigests(header *types.Header) { - for i, d := range header.Digest { - if d.Type() == types.ConsensusDigestType { - cd, ok := d.(*types.ConsensusDigest) - if !ok { - logger.Error("handleDigests", "block number", header.Number, "index", i, "error", "cannot cast invalid consensus digest item") - continue - } - - err := s.digestHandler.HandleConsensusDigest(cd, header) - if err != nil { - logger.Error("handleDigests", "block number", header.Number, "index", i, "digest", cd, "error", err) - } - } - } -} - // IsSynced exposes the synced state func (s *Service) IsSynced() bool { return s.synced diff --git a/dot/sync/syncer_test.go b/dot/sync/syncer_test.go index 150f39df4d..12611722af 100644 --- a/dot/sync/syncer_test.go +++ b/dot/sync/syncer_test.go @@ -18,7 +18,6 @@ package sync import ( "errors" - "io/ioutil" "math/big" "os" "testing" @@ -27,7 +26,6 @@ import ( "github.com/ChainSafe/gossamer/dot/network" "github.com/ChainSafe/gossamer/dot/state" "github.com/ChainSafe/gossamer/dot/types" - "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/common/optional" "github.com/ChainSafe/gossamer/lib/common/variadic" "github.com/ChainSafe/gossamer/lib/runtime" @@ -213,59 +211,6 @@ func TestSyncer_ExecuteBlock(t *testing.T) { require.NoError(t, err) } -func TestSyncer_HandleRuntimeChanges(t *testing.T) { - syncer := NewTestSyncer(t, false) - codeHashBefore := syncer.codeHash - - testRuntime, err := ioutil.ReadFile(runtime.POLKADOT_RUNTIME_FP) - require.NoError(t, err) - - ts, err := syncer.storageState.TrieState(nil) - require.NoError(t, err) - - ts.Set(common.CodeKey, testRuntime) - err = syncer.handleRuntimeChanges(ts) - require.NoError(t, err) - codeHashAfter := syncer.codeHash - require.NotEqualf(t, codeHashBefore, codeHashAfter, "expected different code hash after runtime update") -} - -func TestSyncer_HandleCodeSubstitutes(t *testing.T) { - syncer := NewTestSyncer(t, true) - nonSubBlockHash := common.MustHexToHash("0x86aa36a140dfc449c30dbce16ce0fea33d5c3786766baa764e33f336841b9e28") - err := syncer.handleCodeSubstitution(nonSubBlockHash) - require.NoError(t, err) - codSub := syncer.codeSubstitutedState.LoadCodeSubstitutedBlockHash() - require.Equal(t, common.Hash{}, codSub) - - subBlockHash := common.MustHexToHash("0x86aa36a140dfc449c30dbce16ce0fea33d5c3786766baa764e33f336841b9e29") // hash for known test code substitution - err = syncer.handleCodeSubstitution(subBlockHash) - require.NoError(t, err) - codSub = syncer.codeSubstitutedState.LoadCodeSubstitutedBlockHash() - require.Equal(t, subBlockHash, codSub) -} - -func TestSyncer_HandleRuntimeChangesAfterCodeSubstitutes(t *testing.T) { - syncer := NewTestSyncer(t, true) - codeHashBefore := syncer.codeHash - blockHash := common.MustHexToHash("0x86aa36a140dfc449c30dbce16ce0fea33d5c3786766baa764e33f336841b9e29") // hash for known test code substitution - - err := syncer.handleCodeSubstitution(blockHash) - require.NoError(t, err) - require.Equal(t, codeHashBefore, syncer.codeHash) // codeHash should remain unchanged after code substitute - - testRuntime, err := ioutil.ReadFile(runtime.POLKADOT_RUNTIME_FP) - require.NoError(t, err) - - ts, err := syncer.storageState.TrieState(nil) - require.NoError(t, err) - - ts.Set(common.CodeKey, testRuntime) - err = syncer.handleRuntimeChanges(ts) - require.NoError(t, err) - require.NotEqualf(t, codeHashBefore, syncer.codeHash, "expected different code hash after runtime update") // codeHash should change after runtime change -} - func TestSyncer_HandleJustification(t *testing.T) { syncer := NewTestSyncer(t, false) @@ -275,6 +220,9 @@ func TestSyncer_HandleJustification(t *testing.T) { just := []byte("testjustification") + err := syncer.blockState.SetHeader(header) + require.NoError(t, err) + syncer.handleJustification(header, just) res, err := syncer.blockState.GetJustification(header.Hash()) diff --git a/dot/sync/test_helpers.go b/dot/sync/test_helpers.go index ae2e43c80c..0059d65b10 100644 --- a/dot/sync/test_helpers.go +++ b/dot/sync/test_helpers.go @@ -56,16 +56,6 @@ func NewMockVerifier() *syncmocks.MockVerifier { return m } -// NewMockBlockProducer create and return sync BlockProducer interface mock -func NewMockBlockProducer() *syncmocks.MockBlockProducer { - m := new(syncmocks.MockBlockProducer) - m.On("Pause").Return(nil) - m.On("Resume").Return(nil) - m.On("SetRuntime", mock.AnythingOfType("runtime.Instance")) - - return m -} - // NewTestSyncer ... func NewTestSyncer(t *testing.T, usePolkadotGenesis bool) *Service { wasmer.DefaultTestLogLvl = 3 @@ -95,9 +85,8 @@ func NewTestSyncer(t *testing.T, usePolkadotGenesis bool) *Service { cfg.StorageState = stateSrvc.Storage } - if cfg.BlockProducer == nil { - cfg.BlockProducer = NewMockBlockProducer() - } + cfg.BlockImportHandler = new(syncmocks.MockBlockImportHandler) + cfg.BlockImportHandler.(*syncmocks.MockBlockImportHandler).On("HandleBlockImport", mock.AnythingOfType("*types.Block"), mock.AnythingOfType("*storage.TrieState")).Return(nil) if cfg.Runtime == nil { // set state to genesis state @@ -129,21 +118,6 @@ func NewTestSyncer(t *testing.T, usePolkadotGenesis bool) *Service { cfg.FinalityGadget = NewMockFinalityGadget() } - if cfg.CodeSubstitutes == nil { - cfg.CodeSubstitutes = make(map[common.Hash]string) - - genesisData, err := stateSrvc.Base.LoadGenesisData() // nolint - require.NoError(t, err) - - for k, v := range genesisData.CodeSubstitutes { - cfg.CodeSubstitutes[common.MustHexToHash(k)] = v - } - } - - if cfg.CodeSubstitutedState == nil { - cfg.CodeSubstitutedState = stateSrvc.Base - } - syncer, err := NewService(cfg) require.NoError(t, err) return syncer diff --git a/lib/babe/babe.go b/lib/babe/babe.go index e8f2911e18..43a798f135 100644 --- a/lib/babe/babe.go +++ b/lib/babe/babe.go @@ -48,6 +48,8 @@ type Service struct { epochState EpochState epochLength uint64 + blockImportHandler BlockImportHandler + // BABE authority keypair keypair *sr25519.Keypair // TODO: change to BABE keystore @@ -60,9 +62,6 @@ type Service struct { slotToProof map[uint64]*VrfOutputAndProof // for slots where we are a producer, store the vrf output (bytes 0-32) + proof (bytes 32-96) isDisabled bool - // Channels for inter-process communication - blockChan chan types.Block // send blocks to core service - // State variables sync.RWMutex pause chan struct{} @@ -75,6 +74,7 @@ type ServiceConfig struct { StorageState StorageState TransactionState TransactionState EpochState EpochState + BlockImportHandler BlockImportHandler Keypair *sr25519.Keypair Runtime runtime.Instance AuthData []*types.Authority @@ -93,15 +93,19 @@ func NewService(cfg *ServiceConfig) (*Service, error) { } if cfg.BlockState == nil { - return nil, errors.New("blockState is nil") + return nil, errNilBlockState } if cfg.EpochState == nil { - return nil, errors.New("epochState is nil") + return nil, errNilEpochState } if cfg.Runtime == nil { - return nil, errors.New("runtime is nil") + return nil, errNilRuntime + } + + if cfg.BlockImportHandler == nil { + return nil, errNilBlockImportHandler } logger = log.New("pkg", "babe") @@ -112,20 +116,20 @@ func NewService(cfg *ServiceConfig) (*Service, error) { ctx, cancel := context.WithCancel(context.Background()) babeService := &Service{ - ctx: ctx, - cancel: cancel, - blockState: cfg.BlockState, - storageState: cfg.StorageState, - epochState: cfg.EpochState, - epochLength: cfg.EpochLength, - keypair: cfg.Keypair, - rt: cfg.Runtime, - transactionState: cfg.TransactionState, - slotToProof: make(map[uint64]*VrfOutputAndProof), - blockChan: make(chan types.Block, 16), - pause: make(chan struct{}), - authority: cfg.Authority, - dev: cfg.IsDev, + ctx: ctx, + cancel: cancel, + blockState: cfg.BlockState, + storageState: cfg.StorageState, + epochState: cfg.EpochState, + epochLength: cfg.EpochLength, + keypair: cfg.Keypair, + rt: cfg.Runtime, + transactionState: cfg.TransactionState, + slotToProof: make(map[uint64]*VrfOutputAndProof), + pause: make(chan struct{}), + authority: cfg.Authority, + dev: cfg.IsDev, + blockImportHandler: cfg.BlockImportHandler, } genCfg, err := babeService.rt.BabeConfiguration() @@ -269,15 +273,11 @@ func (b *Service) IsPaused() bool { // Stop stops the service. If stop is called, it cannot be resumed. func (b *Service) Stop() error { - b.Lock() - defer b.Unlock() - if b.ctx.Err() != nil { return errors.New("service already stopped") } b.cancel() - close(b.blockChan) return nil } @@ -286,11 +286,6 @@ func (b *Service) SetRuntime(rt runtime.Instance) { b.rt = rt } -// GetBlockChannel returns the channel where new blocks are passed -func (b *Service) GetBlockChannel() <-chan types.Block { - return b.blockChan -} - // SetOnDisabled sets the block producer with the given index as disabled // If this is our node, we stop producing blocks func (b *Service) SetOnDisabled(authorityIndex uint32) { // TODO: remove this @@ -309,18 +304,6 @@ func (b *Service) IsStopped() bool { return b.ctx.Err() != nil } -func (b *Service) safeSend(msg types.Block) error { - b.Lock() - defer b.Unlock() - - if b.IsStopped() { - return errors.New("service has been stopped") - } - - b.blockChan <- msg - return nil -} - func (b *Service) getAuthorityIndex(Authorities []*types.Authority) (uint32, error) { if !b.authority { return 0, ErrNotAuthority @@ -504,23 +487,19 @@ func (b *Service) handleSlot(slotNum uint64) error { return err } - err = b.storageState.StoreTrie(ts, block.Header) - if err != nil { - logger.Error("failed to store trie in storage state", "error", err) - } - - hash := block.Header.Hash() - logger.Info("built block", "hash", hash.String(), "number", block.Header.Number, "state root", block.Header.StateRoot, "slot", slotNum) - logger.Debug("built block", "header", block.Header, "body", block.Body, "parent", parent.Hash()) - - err = b.blockState.AddBlock(block) - if err != nil { - return err - } + logger.Info("built block", "hash", block.Header.Hash().String(), + "number", block.Header.Number, + "state root", block.Header.StateRoot, + "slot", slotNum, + ) + logger.Debug("built block", + "header", block.Header, + "body", block.Body, + "parent", parent.Hash(), + ) - err = b.safeSend(*block) - if err != nil { - logger.Error("failed to send block to core", "error", err) + if err := b.blockImportHandler.HandleBlockProduced(block, ts); err != nil { + logger.Warn("failed to import built block", "error", err) return err } diff --git a/lib/babe/babe_test.go b/lib/babe/babe_test.go index 600a6a9f90..5a9733f483 100644 --- a/lib/babe/babe_test.go +++ b/lib/babe/babe_test.go @@ -34,12 +34,14 @@ import ( "github.com/ChainSafe/gossamer/lib/trie" log "github.com/ChainSafe/log15" "github.com/stretchr/testify/require" + + "github.com/ChainSafe/gossamer/lib/babe/mocks" + mock "github.com/stretchr/testify/mock" ) var ( defaultTestLogLvl = log.LvlInfo emptyHash = trie.EmptyHash - testTimeout = time.Second * 5 testEpochIndex = uint64(0) maxThreshold = common.MaxUint128 @@ -89,6 +91,9 @@ func createTestService(t *testing.T, cfg *ServiceConfig) *Service { } } + cfg.BlockImportHandler = new(mocks.BlockImportHandler) + cfg.BlockImportHandler.(*mocks.BlockImportHandler).On("HandleBlockProduced", mock.AnythingOfType("*types.Block"), mock.AnythingOfType("*storage.TrieState")).Return(nil) + if cfg.Keypair == nil { cfg.Keypair, err = sr25519.GenerateKeypair() require.NoError(t, err) @@ -166,19 +171,16 @@ func TestMain(m *testing.M) { os.Exit(code) } -func TestRunEpochLengthConfig(t *testing.T) { +func TestService_RunEpochLengthConfig(t *testing.T) { cfg := &ServiceConfig{ EpochLength: 5, } babeService := createTestService(t, cfg) - - if babeService.epochLength != 5 { - t.Fatal("epoch length not set") - } + require.Equal(t, uint64(5), babeService.epochLength) } -func TestSlotDuration(t *testing.T) { +func TestService_SlotDuration(t *testing.T) { duration, err := time.ParseDuration("1000ms") require.NoError(t, err) @@ -190,7 +192,7 @@ func TestSlotDuration(t *testing.T) { require.Equal(t, dur.Milliseconds(), int64(1000)) } -func TestBabeAnnounceMessage(t *testing.T) { +func TestService_ProducesBlocks(t *testing.T) { babeService := createTestService(t, nil) babeService.epochData.authorityIndex = 0 @@ -201,21 +203,18 @@ func TestBabeAnnounceMessage(t *testing.T) { } babeService.epochData.threshold = maxThreshold - blockNumber := big.NewInt(int64(1)) err := babeService.Start() require.NoError(t, err) + defer func() { + _ = babeService.Stop() + }() - newBlocks := babeService.GetBlockChannel() - select { - case block := <-newBlocks: - require.Equal(t, blockNumber, block.Header.Number) - case <-time.After(testTimeout): - t.Fatal("did not receive block") - } + time.Sleep(babeService.slotDuration * 2) + babeService.blockImportHandler.(*mocks.BlockImportHandler).AssertCalled(t, "HandleBlockProduced", mock.AnythingOfType("*types.Block"), mock.AnythingOfType("*storage.TrieState")) } -func TestGetAuthorityIndex(t *testing.T) { +func TestService_GetAuthorityIndex(t *testing.T) { kpA, err := sr25519.GenerateKeypair() require.NoError(t, err) diff --git a/lib/babe/errors.go b/lib/babe/errors.go index a676b21a5e..16f513d839 100644 --- a/lib/babe/errors.go +++ b/lib/babe/errors.go @@ -21,46 +21,46 @@ import ( "github.com/ChainSafe/gossamer/lib/scale" ) -// ErrBadSlotClaim is returned when a slot claim is invalid -var ErrBadSlotClaim = errors.New("could not verify slot claim VRF proof") +var ( + // ErrBadSlotClaim is returned when a slot claim is invalid + ErrBadSlotClaim = errors.New("could not verify slot claim VRF proof") -// ErrBadSecondarySlotClaim is returned when a slot claim is invalid -var ErrBadSecondarySlotClaim = errors.New("invalid secondary slot claim") + // ErrBadSecondarySlotClaim is returned when a slot claim is invalid + ErrBadSecondarySlotClaim = errors.New("invalid secondary slot claim") -// ErrBadSignature is returned when a seal is invalid -var ErrBadSignature = errors.New("could not verify signature") + // ErrBadSignature is returned when a seal is invalid + ErrBadSignature = errors.New("could not verify signature") -// ErrProducerEquivocated is returned when a block producer has produced conflicting blocks -var ErrProducerEquivocated = errors.New("block producer equivocated") + // ErrProducerEquivocated is returned when a block producer has produced conflicting blocks + ErrProducerEquivocated = errors.New("block producer equivocated") -// ErrNilBlockState is returned when the BlockState is nil -var ErrNilBlockState = errors.New("cannot have nil BlockState") + // ErrNotAuthorized is returned when the node is not authorized to produce a block + ErrNotAuthorized = errors.New("not authorized to produce block") -// ErrNilEpochState is returned when the EpochState is nil -var ErrNilEpochState = errors.New("cannot have nil EpochState") + // ErrNoBABEHeader is returned when there is no BABE header found for a block, specifically when calculating randomness + ErrNoBABEHeader = errors.New("no BABE header found for block") -// ErrNotAuthorized is returned when the node is not authorized to produce a block -var ErrNotAuthorized = errors.New("not authorized to produce block") + // ErrVRFOutputOverThreshold is returned when the vrf output for a block is invalid + ErrVRFOutputOverThreshold = errors.New("vrf output over threshold") -// ErrNoBABEHeader is returned when there is no BABE header found for a block, specifically when calculating randomness -var ErrNoBABEHeader = errors.New("no BABE header found for block") + // ErrInvalidBlockProducerIndex is returned when the producer of a block isn't in the authority set + ErrInvalidBlockProducerIndex = errors.New("block producer is not in authority set") -// ErrVRFOutputOverThreshold is returned when the vrf output for a block is invalid -var ErrVRFOutputOverThreshold = errors.New("vrf output over threshold") + // ErrAuthorityAlreadyDisabled is returned when attempting to disabled an already-disabled authority + ErrAuthorityAlreadyDisabled = errors.New("authority has already been disabled") -// ErrInvalidBlockProducerIndex is returned when the producer of a block isn't in the authority set -var ErrInvalidBlockProducerIndex = errors.New("block producer is not in authority set") + // ErrAuthorityDisabled is returned when attempting to verify a block produced by a disabled authority + ErrAuthorityDisabled = errors.New("authority has been disabled for the remaining slots in the epoch") -// ErrAuthorityAlreadyDisabled is returned when attempting to disabled an already-disabled authority -var ErrAuthorityAlreadyDisabled = errors.New("authority has already been disabled") + // ErrNotAuthority is returned when trying to perform authority functions when not an authority + ErrNotAuthority = errors.New("node is not an authority") -// ErrAuthorityDisabled is returned when attempting to verify a block produced by a disabled authority -var ErrAuthorityDisabled = errors.New("authority has been disabled for the remaining slots in the epoch") - -// ErrNotAuthority is returned when trying to perform authority functions when not an authority -var ErrNotAuthority = errors.New("node is not an authority") - -var errInvalidResult = errors.New("invalid error value") + errNilBlockImportHandler = errors.New("cannot have nil BlockImportHandler") + errNilBlockState = errors.New("cannot have nil BlockState") + errNilEpochState = errors.New("cannot have nil EpochState") + errNilRuntime = errors.New("runtime is nil") + errInvalidResult = errors.New("invalid error value") +) // A DispatchOutcomeError is outcome of dispatching the extrinsic type DispatchOutcomeError struct { diff --git a/lib/babe/mocks/BlockImportHandler.go b/lib/babe/mocks/BlockImportHandler.go new file mode 100644 index 0000000000..4e2bc5f5f2 --- /dev/null +++ b/lib/babe/mocks/BlockImportHandler.go @@ -0,0 +1,28 @@ +// Code generated by mockery v2.8.0. DO NOT EDIT. + +package mocks + +import ( + types "github.com/ChainSafe/gossamer/dot/types" + storage "github.com/ChainSafe/gossamer/lib/runtime/storage" + mock "github.com/stretchr/testify/mock" +) + +// BlockImportHandler is an autogenerated mock type for the BlockImportHandler type +type BlockImportHandler struct { + mock.Mock +} + +// HandleBlockProduced provides a mock function with given fields: block, state +func (_m *BlockImportHandler) HandleBlockProduced(block *types.Block, state *storage.TrieState) error { + ret := _m.Called(block, state) + + var r0 error + if rf, ok := ret.Get(0).(func(*types.Block, *storage.TrieState) error); ok { + r0 = rf(block, state) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/lib/babe/state.go b/lib/babe/state.go index 373ec91ccc..c86705f978 100644 --- a/lib/babe/state.go +++ b/lib/babe/state.go @@ -73,3 +73,8 @@ type EpochState interface { GetLatestEpochData() (*types.EpochData, error) SkipVerify(*types.Header) (bool, error) } + +// BlockImportHandler is the interface for the handler of new blocks +type BlockImportHandler interface { + HandleBlockProduced(block *types.Block, state *rtstorage.TrieState) error +} diff --git a/lib/babe/verify.go b/lib/babe/verify.go index 533459b77e..080079626a 100644 --- a/lib/babe/verify.go +++ b/lib/babe/verify.go @@ -58,11 +58,11 @@ type VerificationManager struct { // NewVerificationManager returns a new NewVerificationManager func NewVerificationManager(blockState BlockState, epochState EpochState) (*VerificationManager, error) { if blockState == nil { - return nil, ErrNilBlockState + return nil, errNilBlockState } if epochState == nil { - return nil, ErrNilEpochState + return nil, errNilEpochState } return &VerificationManager{ @@ -304,7 +304,7 @@ type verifier struct { // newVerifier returns a Verifier for the epoch described by the given descriptor func newVerifier(blockState BlockState, epoch uint64, info *verifierInfo) (*verifier, error) { if blockState == nil { - return nil, ErrNilBlockState + return nil, errNilBlockState } return &verifier{ diff --git a/lib/grandpa/message_handler_test.go b/lib/grandpa/message_handler_test.go index 04e7f30dca..077c5fc1fb 100644 --- a/lib/grandpa/message_handler_test.go +++ b/lib/grandpa/message_handler_test.go @@ -239,6 +239,9 @@ func TestMessageHandler_CommitMessage_NoCatchUpRequest_ValidSig(t *testing.T) { fm := gs.newCommitMessage(gs.head, round) fm.Vote = NewVote(testHash, uint32(round)) + err := st.Block.SetHeader(testHeader) + require.NoError(t, err) + h := NewMessageHandler(gs, st.Block) out, err := h.handleMessage("", fm) require.NoError(t, err) @@ -325,7 +328,10 @@ func TestMessageHandler_CatchUpRequest_WithResponse(t *testing.T) { number: 1, } - err := gs.blockState.SetFinalizedHash(testHeader.Hash(), round, setID) + err := st.Block.SetHeader(testHeader) + require.NoError(t, err) + + err = gs.blockState.SetFinalizedHash(testHeader.Hash(), round, setID) require.NoError(t, err) err = gs.blockState.(*state.BlockState).SetHeader(testHeader) require.NoError(t, err) diff --git a/lib/grandpa/message_test.go b/lib/grandpa/message_test.go index f9b61282ec..bc985cd45f 100644 --- a/lib/grandpa/message_test.go +++ b/lib/grandpa/message_test.go @@ -97,7 +97,7 @@ func TestCommitMessageToConsensusMessage(t *testing.T) { } func TestNewCatchUpResponse(t *testing.T) { - gs, _ := newTestService(t) + gs, st := newTestService(t) round := uint64(1) setID := uint64(1) @@ -111,7 +111,10 @@ func TestNewCatchUpResponse(t *testing.T) { number: 1, } - err := gs.blockState.SetFinalizedHash(testHeader.Hash(), round, setID) + err := st.Block.SetHeader(testHeader) + require.NoError(t, err) + + err = gs.blockState.SetFinalizedHash(testHeader.Hash(), round, setID) require.NoError(t, err) err = gs.blockState.(*state.BlockState).SetHeader(testHeader) require.NoError(t, err)