Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dot/sync): implement codeSubstitutes #1635

Merged
merged 8 commits into from
Jun 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
noot marked this conversation as resolved.
Show resolved Hide resolved
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())
}

noot marked this conversation as resolved.
Show resolved Hide resolved
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