Skip to content

Commit d87aaeb

Browse files
authored
feat(dot/sync): implement codeSubstitutes (#1635)
1 parent 57027db commit d87aaeb

File tree

18 files changed

+348
-86
lines changed

18 files changed

+348
-86
lines changed

chain/polkadot/genesis.json

Lines changed: 3 additions & 0 deletions
Large diffs are not rendered by default.

dot/network/proto/mock_is_block_request__from_block.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dot/node.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ func NewNode(cfg *Config, ks *keystore.GlobalKeystore, stopFunc func()) (*Node,
283283
nodeSrvcs = append(nodeSrvcs, fg)
284284

285285
// Syncer
286-
syncer, err := createSyncService(cfg, stateSrvc, bp, fg, dh, ver, rt)
286+
syncer, err := newSyncService(cfg, stateSrvc, bp, fg, dh, ver, rt)
287287
if err != nil {
288288
return nil, err
289289
}

dot/services.go

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"path/filepath"
2323

2424
"github.com/ChainSafe/chaindb"
25-
2625
"github.com/ChainSafe/gossamer/dot/core"
2726
"github.com/ChainSafe/gossamer/dot/network"
2827
"github.com/ChainSafe/gossamer/dot/rpc"
@@ -32,6 +31,7 @@ import (
3231
"github.com/ChainSafe/gossamer/dot/system"
3332
"github.com/ChainSafe/gossamer/dot/types"
3433
"github.com/ChainSafe/gossamer/lib/babe"
34+
"github.com/ChainSafe/gossamer/lib/common"
3535
"github.com/ChainSafe/gossamer/lib/crypto"
3636
"github.com/ChainSafe/gossamer/lib/crypto/ed25519"
3737
"github.com/ChainSafe/gossamer/lib/crypto/sr25519"
@@ -95,6 +95,20 @@ func createRuntime(cfg *Config, st *state.Service, ks *keystore.GlobalKeystore,
9595
return nil, fmt.Errorf("failed to retrieve :code from trie: %s", err)
9696
}
9797

98+
// check if code substitute is in use, if so replace code
99+
codeSubHash := st.Base.LoadCodeSubstitutedBlockHash()
100+
101+
if !codeSubHash.Equal(common.Hash{}) {
102+
logger.Info("🔄 detected runtime code substitution, upgrading...", "block", codeSubHash)
103+
genData, err := st.Base.LoadGenesisData() // nolint
104+
if err != nil {
105+
return nil, err
106+
}
107+
codeString := genData.CodeSubstitutes[codeSubHash.String()]
108+
109+
code = common.MustHexToBytes(codeString)
110+
}
111+
98112
ts, err := st.Storage.TrieState(nil)
99113
if err != nil {
100114
return nil, err
@@ -376,17 +390,27 @@ func createBlockVerifier(st *state.Service) (*babe.VerificationManager, error) {
376390
return ver, nil
377391
}
378392

379-
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) {
393+
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) {
394+
genesisData, err := st.Base.LoadGenesisData()
395+
if err != nil {
396+
return nil, err
397+
}
398+
codeSubs := make(map[common.Hash]string)
399+
for k, v := range genesisData.CodeSubstitutes {
400+
codeSubs[common.MustHexToHash(k)] = v
401+
}
380402
syncCfg := &sync.Config{
381-
LogLvl: cfg.Log.SyncLvl,
382-
BlockState: st.Block,
383-
StorageState: st.Storage,
384-
TransactionState: st.Transaction,
385-
BlockProducer: bp,
386-
FinalityGadget: fg,
387-
Verifier: verifier,
388-
Runtime: rt,
389-
DigestHandler: dh,
403+
LogLvl: cfg.Log.SyncLvl,
404+
BlockState: st.Block,
405+
StorageState: st.Storage,
406+
TransactionState: st.Transaction,
407+
BlockProducer: bp,
408+
FinalityGadget: fg,
409+
Verifier: verifier,
410+
Runtime: rt,
411+
DigestHandler: dh,
412+
CodeSubstitutes: codeSubs,
413+
CodeSubstitutedState: st.Base,
390414
}
391415

392416
return sync.NewService(syncCfg)

dot/services_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ func TestCreateSyncService(t *testing.T) {
137137
ver, err := createBlockVerifier(stateSrvc)
138138
require.NoError(t, err)
139139

140-
_, err = createSyncService(cfg, stateSrvc, sync.NewMockBlockProducer(), nil, nil, ver, rt)
140+
_, err = newSyncService(cfg, stateSrvc, sync.NewMockBlockProducer(), nil, nil, ver, rt)
141141
require.NoError(t, err)
142142
}
143143

dot/state/base.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,21 @@ func (s *BaseState) LoadLatestStorageHash() (common.Hash, error) {
110110
return common.NewHash(hashbytes), nil
111111
}
112112

113+
// StoreCodeSubstitutedBlockHash stores the hash at the CodeSubstitutedBlock key
114+
func (s *BaseState) StoreCodeSubstitutedBlockHash(hash common.Hash) error {
115+
return s.db.Put(common.CodeSubstitutedBlock, hash[:])
116+
}
117+
118+
// LoadCodeSubstitutedBlockHash loads the hash stored at CodeSubstitutedBlock key
119+
func (s *BaseState) LoadCodeSubstitutedBlockHash() common.Hash {
120+
hash, err := s.db.Get(common.CodeSubstitutedBlock)
121+
if err != nil {
122+
return common.Hash{}
123+
}
124+
125+
return common.NewHash(hash)
126+
}
127+
113128
func (s *BaseState) storeSkipToEpoch(epoch uint64) error {
114129
buf := make([]byte, 8)
115130
binary.LittleEndian.PutUint64(buf, epoch)

dot/sync/interface.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ type StorageState interface {
5656
SetSyncing(bool)
5757
}
5858

59+
// CodeSubstitutedState interface to handle storage of code substitute state
60+
type CodeSubstitutedState interface {
61+
LoadCodeSubstitutedBlockHash() common.Hash
62+
StoreCodeSubstitutedBlockHash(hash common.Hash) error
63+
}
64+
5965
// TransactionState is the interface for transaction queue methods
6066
type TransactionState interface {
6167
RemoveExtrinsic(ext types.Extrinsic)

dot/sync/message_test.go

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,15 @@ package sync
22

33
import (
44
"math/big"
5-
"os"
65
"testing"
76

87
"github.com/ChainSafe/gossamer/dot/network"
98
"github.com/ChainSafe/gossamer/dot/types"
109
"github.com/ChainSafe/gossamer/lib/common"
1110
"github.com/ChainSafe/gossamer/lib/common/optional"
1211
"github.com/ChainSafe/gossamer/lib/common/variadic"
13-
"github.com/ChainSafe/gossamer/lib/runtime"
1412
"github.com/ChainSafe/gossamer/lib/trie"
15-
log "github.com/ChainSafe/log15"
13+
1614
"github.com/stretchr/testify/require"
1715
)
1816

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

42-
func TestMain(m *testing.M) {
43-
wasmFilePaths, err := runtime.GenerateRuntimeWasmFile()
44-
if err != nil {
45-
log.Error("failed to generate runtime wasm file", err)
46-
os.Exit(1)
47-
}
48-
49-
// Start all tests
50-
code := m.Run()
51-
52-
runtime.RemoveFiles(wasmFilePaths)
53-
os.Exit(code)
54-
}
55-
5640
func TestService_CreateBlockResponse_MaxSize(t *testing.T) {
57-
s := NewTestSyncer(t)
41+
s := NewTestSyncer(t, false)
5842
addTestBlocksToState(t, int(maxResponseSize), s.blockState)
5943

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

9276
func TestService_CreateBlockResponse_StartHash(t *testing.T) {
93-
s := NewTestSyncer(t)
77+
s := NewTestSyncer(t, false)
9478
addTestBlocksToState(t, int(maxResponseSize), s.blockState)
9579

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

117101
func TestService_CreateBlockResponse_Ascending(t *testing.T) {
118-
s := NewTestSyncer(t)
102+
s := NewTestSyncer(t, false)
119103
addTestBlocksToState(t, int(maxResponseSize), s.blockState)
120104

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

142126
// tests the ProcessBlockRequestMessage method
143127
func TestService_CreateBlockResponse(t *testing.T) {
144-
s := NewTestSyncer(t)
128+
s := NewTestSyncer(t, false)
145129
addTestBlocksToState(t, 2, s.blockState)
146130

147131
bestHash := s.blockState.BestBlockHash()

dot/sync/syncer.go

Lines changed: 93 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -56,19 +56,25 @@ type Service struct {
5656

5757
// Consensus digest handling
5858
digestHandler DigestHandler
59+
60+
// map of code substitutions keyed by block hash
61+
codeSubstitute map[common.Hash]string
62+
codeSubstitutedState CodeSubstitutedState
5963
}
6064

6165
// Config is the configuration for the sync Service.
6266
type Config struct {
63-
LogLvl log.Lvl
64-
BlockState BlockState
65-
StorageState StorageState
66-
BlockProducer BlockProducer
67-
FinalityGadget FinalityGadget
68-
TransactionState TransactionState
69-
Runtime runtime.Instance
70-
Verifier Verifier
71-
DigestHandler DigestHandler
67+
LogLvl log.Lvl
68+
BlockState BlockState
69+
StorageState StorageState
70+
BlockProducer BlockProducer
71+
FinalityGadget FinalityGadget
72+
TransactionState TransactionState
73+
Runtime runtime.Instance
74+
Verifier Verifier
75+
DigestHandler DigestHandler
76+
CodeSubstitutes map[common.Hash]string
77+
CodeSubstitutedState CodeSubstitutedState
7278
}
7379

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

105111
return &Service{
106-
codeHash: codeHash,
107-
blockState: cfg.BlockState,
108-
storageState: cfg.StorageState,
109-
blockProducer: cfg.BlockProducer,
110-
finalityGadget: cfg.FinalityGadget,
111-
synced: true,
112-
highestSeenBlock: big.NewInt(0),
113-
transactionState: cfg.TransactionState,
114-
runtime: cfg.Runtime,
115-
verifier: cfg.Verifier,
116-
digestHandler: cfg.DigestHandler,
112+
codeHash: codeHash,
113+
blockState: cfg.BlockState,
114+
storageState: cfg.StorageState,
115+
blockProducer: cfg.BlockProducer,
116+
finalityGadget: cfg.FinalityGadget,
117+
synced: true,
118+
highestSeenBlock: big.NewInt(0),
119+
transactionState: cfg.TransactionState,
120+
runtime: cfg.Runtime,
121+
verifier: cfg.Verifier,
122+
digestHandler: cfg.DigestHandler,
123+
codeSubstitute: cfg.CodeSubstitutes,
124+
codeSubstitutedState: cfg.CodeSubstitutedState,
117125
}, nil
118126
}
119127

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

228+
if err := s.handleCodeSubstitution(bd.Hash); err != nil {
229+
logger.Warn("failed to handle code substitution", "error", err)
230+
return i, err
231+
}
232+
220233
continue
221234
}
222235

@@ -364,7 +377,7 @@ func (s *Service) handleBlock(block *types.Block) error {
364377
}
365378
} else {
366379
logger.Debug("🔗 imported block", "number", block.Header.Number, "hash", block.Header.Hash())
367-
err := telemetry.GetInstance().SendMessage(telemetry.NewTelemetryMessage(
380+
err := telemetry.GetInstance().SendMessage(telemetry.NewTelemetryMessage( // nolint
368381
telemetry.NewKeyValue("best", block.Header.Hash().String()),
369382
telemetry.NewKeyValue("height", block.Header.Number.Uint64()),
370383
telemetry.NewKeyValue("msg", "block.import"),
@@ -379,6 +392,11 @@ func (s *Service) handleBlock(block *types.Block) error {
379392
s.handleDigests(block.Header)
380393
}
381394

395+
err = s.handleCodeSubstitution(block.Header.Hash())
396+
if err != nil {
397+
return err
398+
}
399+
382400
return s.handleRuntimeChanges(ts)
383401
}
384402

@@ -424,13 +442,67 @@ func (s *Service) handleRuntimeChanges(newState *rtstorage.TrieState) error {
424442
return ErrEmptyRuntimeCode
425443
}
426444

445+
codeSubBlockHash := s.codeSubstitutedState.LoadCodeSubstitutedBlockHash()
446+
447+
if !codeSubBlockHash.Equal(common.Hash{}) {
448+
// don't do runtime change if using code substitution and runtime change spec version are equal
449+
// (do a runtime change if code substituted and runtime spec versions are different, or code not substituted)
450+
newVersion, err := s.runtime.CheckRuntimeVersion(code) // nolint
451+
if err != nil {
452+
logger.Debug("problem checking runtime version", "error", err)
453+
return err
454+
}
455+
456+
previousVersion, _ := s.runtime.Version()
457+
if previousVersion.SpecVersion() == newVersion.SpecVersion() {
458+
return nil
459+
}
460+
461+
logger.Info("🔄 detected runtime code change, upgrading...", "block", s.blockState.BestBlockHash(),
462+
"previous code hash", s.codeHash, "new code hash", currCodeHash,
463+
"previous spec version", previousVersion.SpecVersion(), "new spec version", newVersion.SpecVersion())
464+
}
465+
427466
err = s.runtime.UpdateRuntimeCode(code)
428467
if err != nil {
429468
logger.Crit("failed to update runtime code", "error", err)
430469
return err
431470
}
432471

433472
s.codeHash = currCodeHash
473+
474+
err = s.codeSubstitutedState.StoreCodeSubstitutedBlockHash(common.Hash{})
475+
if err != nil {
476+
logger.Error("failed to update code substituted block hash", "error", err)
477+
return err
478+
}
479+
480+
return nil
481+
}
482+
483+
func (s *Service) handleCodeSubstitution(hash common.Hash) error {
484+
value := s.codeSubstitute[hash]
485+
if value == "" {
486+
return nil
487+
}
488+
489+
logger.Info("🔄 detected runtime code substitution, upgrading...", "block", hash)
490+
code := common.MustHexToBytes(value)
491+
if len(code) == 0 {
492+
return ErrEmptyRuntimeCode
493+
}
494+
495+
err := s.runtime.UpdateRuntimeCode(code)
496+
if err != nil {
497+
logger.Crit("failed to substitute runtime code", "error", err)
498+
return err
499+
}
500+
501+
err = s.codeSubstitutedState.StoreCodeSubstitutedBlockHash(hash)
502+
if err != nil {
503+
return err
504+
}
505+
434506
return nil
435507
}
436508

0 commit comments

Comments
 (0)