Skip to content

Commit

Permalink
feat(dot/sync): implement codeSubstitutes (ChainSafe#1635)
Browse files Browse the repository at this point in the history
  • Loading branch information
edwardmack committed Jun 15, 2021
1 parent 57027db commit d87aaeb
Show file tree
Hide file tree
Showing 18 changed files with 348 additions and 86 deletions.
3 changes: 3 additions & 0 deletions chain/polkadot/genesis.json

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions dot/network/proto/mock_is_block_request__from_block.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dot/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ func NewNode(cfg *Config, ks *keystore.GlobalKeystore, stopFunc func()) (*Node,
nodeSrvcs = append(nodeSrvcs, fg)

// Syncer
syncer, err := createSyncService(cfg, stateSrvc, bp, fg, dh, ver, rt)
syncer, err := newSyncService(cfg, stateSrvc, bp, fg, dh, ver, rt)
if err != nil {
return nil, err
}
Expand Down
46 changes: 35 additions & 11 deletions dot/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"path/filepath"

"github.com/ChainSafe/chaindb"

"github.com/ChainSafe/gossamer/dot/core"
"github.com/ChainSafe/gossamer/dot/network"
"github.com/ChainSafe/gossamer/dot/rpc"
Expand All @@ -32,6 +31,7 @@ import (
"github.com/ChainSafe/gossamer/dot/system"
"github.com/ChainSafe/gossamer/dot/types"
"github.com/ChainSafe/gossamer/lib/babe"
"github.com/ChainSafe/gossamer/lib/common"
"github.com/ChainSafe/gossamer/lib/crypto"
"github.com/ChainSafe/gossamer/lib/crypto/ed25519"
"github.com/ChainSafe/gossamer/lib/crypto/sr25519"
Expand Down Expand Up @@ -95,6 +95,20 @@ func createRuntime(cfg *Config, st *state.Service, ks *keystore.GlobalKeystore,
return nil, fmt.Errorf("failed to retrieve :code from trie: %s", err)
}

// check if code substitute is in use, if so replace code
codeSubHash := st.Base.LoadCodeSubstitutedBlockHash()

if !codeSubHash.Equal(common.Hash{}) {
logger.Info("🔄 detected runtime code substitution, upgrading...", "block", codeSubHash)
genData, err := st.Base.LoadGenesisData() // nolint
if err != nil {
return nil, err
}
codeString := genData.CodeSubstitutes[codeSubHash.String()]

code = common.MustHexToBytes(codeString)
}

ts, err := st.Storage.TrieState(nil)
if err != nil {
return nil, err
Expand Down Expand Up @@ -376,17 +390,27 @@ func createBlockVerifier(st *state.Service) (*babe.VerificationManager, error) {
return ver, nil
}

func createSyncService(cfg *Config, st *state.Service, bp sync.BlockProducer, fg sync.FinalityGadget, dh *core.DigestHandler, verifier *babe.VerificationManager, rt runtime.Instance) (*sync.Service, error) {
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
}
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,
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,
}

return sync.NewService(syncCfg)
Expand Down
2 changes: 1 addition & 1 deletion dot/services_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func TestCreateSyncService(t *testing.T) {
ver, err := createBlockVerifier(stateSrvc)
require.NoError(t, err)

_, err = createSyncService(cfg, stateSrvc, sync.NewMockBlockProducer(), nil, nil, ver, rt)
_, err = newSyncService(cfg, stateSrvc, sync.NewMockBlockProducer(), nil, nil, ver, rt)
require.NoError(t, err)
}

Expand Down
15 changes: 15 additions & 0 deletions dot/state/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,21 @@ func (s *BaseState) LoadLatestStorageHash() (common.Hash, error) {
return common.NewHash(hashbytes), nil
}

// StoreCodeSubstitutedBlockHash stores the hash at the CodeSubstitutedBlock key
func (s *BaseState) StoreCodeSubstitutedBlockHash(hash common.Hash) error {
return s.db.Put(common.CodeSubstitutedBlock, hash[:])
}

// LoadCodeSubstitutedBlockHash loads the hash stored at CodeSubstitutedBlock key
func (s *BaseState) LoadCodeSubstitutedBlockHash() common.Hash {
hash, err := s.db.Get(common.CodeSubstitutedBlock)
if err != nil {
return common.Hash{}
}

return common.NewHash(hash)
}

func (s *BaseState) storeSkipToEpoch(epoch uint64) error {
buf := make([]byte, 8)
binary.LittleEndian.PutUint64(buf, epoch)
Expand Down
6 changes: 6 additions & 0 deletions dot/sync/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ type StorageState interface {
SetSyncing(bool)
}

// CodeSubstitutedState interface to handle storage of code substitute state
type CodeSubstitutedState interface {
LoadCodeSubstitutedBlockHash() common.Hash
StoreCodeSubstitutedBlockHash(hash common.Hash) error
}

// TransactionState is the interface for transaction queue methods
type TransactionState interface {
RemoveExtrinsic(ext types.Extrinsic)
Expand Down
26 changes: 5 additions & 21 deletions dot/sync/message_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@ package sync

import (
"math/big"
"os"
"testing"

"github.com/ChainSafe/gossamer/dot/network"
"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"
"github.com/ChainSafe/gossamer/lib/trie"
log "github.com/ChainSafe/log15"

"github.com/stretchr/testify/require"
)

Expand All @@ -39,22 +37,8 @@ func addTestBlocksToState(t *testing.T, depth int, blockState BlockState) {
}
}

func TestMain(m *testing.M) {
wasmFilePaths, err := runtime.GenerateRuntimeWasmFile()
if err != nil {
log.Error("failed to generate runtime wasm file", err)
os.Exit(1)
}

// Start all tests
code := m.Run()

runtime.RemoveFiles(wasmFilePaths)
os.Exit(code)
}

func TestService_CreateBlockResponse_MaxSize(t *testing.T) {
s := NewTestSyncer(t)
s := NewTestSyncer(t, false)
addTestBlocksToState(t, int(maxResponseSize), s.blockState)

start, err := variadic.NewUint64OrHash(uint64(1))
Expand Down Expand Up @@ -90,7 +74,7 @@ func TestService_CreateBlockResponse_MaxSize(t *testing.T) {
}

func TestService_CreateBlockResponse_StartHash(t *testing.T) {
s := NewTestSyncer(t)
s := NewTestSyncer(t, false)
addTestBlocksToState(t, int(maxResponseSize), s.blockState)

startHash, err := s.blockState.GetHashByNumber(big.NewInt(1))
Expand All @@ -115,7 +99,7 @@ func TestService_CreateBlockResponse_StartHash(t *testing.T) {
}

func TestService_CreateBlockResponse_Ascending(t *testing.T) {
s := NewTestSyncer(t)
s := NewTestSyncer(t, false)
addTestBlocksToState(t, int(maxResponseSize), s.blockState)

startHash, err := s.blockState.GetHashByNumber(big.NewInt(1))
Expand All @@ -141,7 +125,7 @@ func TestService_CreateBlockResponse_Ascending(t *testing.T) {

// tests the ProcessBlockRequestMessage method
func TestService_CreateBlockResponse(t *testing.T) {
s := NewTestSyncer(t)
s := NewTestSyncer(t, false)
addTestBlocksToState(t, 2, s.blockState)

bestHash := s.blockState.BestBlockHash()
Expand Down
114 changes: 93 additions & 21 deletions dot/sync/syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,25 @@ type Service struct {

// 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
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
}

// NewService returns a new *sync.Service
Expand Down Expand Up @@ -103,17 +109,19 @@ func NewService(cfg *Config) (*Service, error) {
}

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,
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,
}, nil
}

Expand Down Expand Up @@ -217,6 +225,11 @@ func (s *Service) ProcessBlockData(data []*types.BlockData) (int, error) {
s.handleJustification(header, bd.Justification.Value())
}

if err := s.handleCodeSubstitution(bd.Hash); err != nil {
logger.Warn("failed to handle code substitution", "error", err)
return i, err
}

continue
}

Expand Down Expand Up @@ -364,7 +377,7 @@ func (s *Service) handleBlock(block *types.Block) error {
}
} else {
logger.Debug("🔗 imported block", "number", block.Header.Number, "hash", block.Header.Hash())
err := telemetry.GetInstance().SendMessage(telemetry.NewTelemetryMessage(
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"),
Expand All @@ -379,6 +392,11 @@ func (s *Service) handleBlock(block *types.Block) error {
s.handleDigests(block.Header)
}

err = s.handleCodeSubstitution(block.Header.Hash())
if err != nil {
return err
}

return s.handleRuntimeChanges(ts)
}

Expand Down Expand Up @@ -424,13 +442,67 @@ func (s *Service) handleRuntimeChanges(newState *rtstorage.TrieState) error {
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
}

Expand Down
Loading

0 comments on commit d87aaeb

Please sign in to comment.