From f7e2d3d71734ee7f485972a539e67de6f6db2017 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Fri, 22 Oct 2021 10:58:53 +0300 Subject: [PATCH] dao: add stateroot-related settings to `Version` Signed-off-by: Evgeniy Stratonikov --- pkg/core/blockchain.go | 31 +++++++++---- pkg/core/blockchain_test.go | 4 +- pkg/core/dao/dao.go | 72 +++++++++++++++++++------------ pkg/core/dao/dao_test.go | 20 +++++++-- pkg/core/statesync/module.go | 2 +- pkg/core/statesync/module_test.go | 2 +- pkg/core/statesync_test.go | 6 +-- 7 files changed, 91 insertions(+), 46 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 61547711b..1e819d35b 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -305,10 +305,17 @@ func (bc *Blockchain) init() error { ver, err := bc.dao.GetVersion() if err != nil { bc.log.Info("no storage version found! creating genesis block") - v := dao.Version{Prefix: storage.STStorage, Value: version} - if err = bc.dao.PutVersion(v); err != nil { + ver = dao.Version{ + StoragePrefix: storage.STStorage, + StateRootInHeader: bc.config.StateRootInHeader, + P2PSigExtensions: bc.config.P2PSigExtensions, + Value: version, + } + if err = bc.dao.PutVersion(ver); err != nil { return err } + bc.dao.Version = ver + bc.persistent.Version = ver genesisBlock, err := createGenesisBlock(bc.config) if err != nil { return err @@ -326,8 +333,16 @@ func (bc *Blockchain) init() error { if ver.Value != version { return fmt.Errorf("storage version mismatch betweeen %s and %s", version, ver.Value) } - bc.dao.StoragePrefix = ver.Prefix - bc.persistent.StoragePrefix = ver.Prefix // not strictly needed but we better be consistent here + if ver.StateRootInHeader != bc.config.StateRootInHeader { + return fmt.Errorf("StateRootInHeader setting mismatch (config=%t, db=%t)", + ver.StateRootInHeader, bc.config.StateRootInHeader) + } + if ver.P2PSigExtensions != bc.config.P2PSigExtensions { + return fmt.Errorf("P2PSigExtensions setting mismatch (old=%t, new=%t", + ver.P2PSigExtensions, bc.config.P2PSigExtensions) + } + bc.dao.Version = ver + bc.persistent.Version = ver // At this point there was no version found in the storage which // implies a creating fresh storage with the version specified @@ -487,7 +502,7 @@ func (bc *Blockchain) jumpToStateInternal(p uint32, stage stateJumpStage) error // Replace old storage items by new ones, it should be done step-by step. // Firstly, remove all old genesis-related items. b := bc.dao.Store.Batch() - bc.dao.Store.Seek([]byte{byte(bc.dao.StoragePrefix)}, func(k, _ []byte) { + bc.dao.Store.Seek([]byte{byte(bc.dao.Version.StoragePrefix)}, func(k, _ []byte) { // #1468, but don't need to copy here, because it is done by Store. b.Delete(k) }) @@ -498,16 +513,16 @@ func (bc *Blockchain) jumpToStateInternal(p uint32, stage stateJumpStage) error } fallthrough case oldStorageItemsRemoved: - newPrefix := statesync.TemporaryPrefix(bc.dao.StoragePrefix) + newPrefix := statesync.TemporaryPrefix(bc.dao.Version.StoragePrefix) v, err := bc.dao.GetVersion() if err != nil { return fmt.Errorf("failed to get dao.Version: %w", err) } - v.Prefix = newPrefix + v.StoragePrefix = newPrefix if err := bc.dao.PutVersion(v); err != nil { return fmt.Errorf("failed to update dao.Version: %w", err) } - bc.persistent.StoragePrefix = newPrefix + bc.persistent.Version = v err = bc.dao.Store.Put(jumpStageKey, []byte{byte(newStorageItemsAdded)}) if err != nil { diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index c930b46b3..5c2eff93b 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -1766,10 +1766,10 @@ func TestBlockchain_InitWithIncompleteStateJump(t *testing.T) { // put storage items with STTemp prefix batch := bcSpout.dao.Store.Batch() tempPrefix := storage.STTempStorage - if bcSpout.dao.StoragePrefix == tempPrefix { + if bcSpout.dao.Version.StoragePrefix == tempPrefix { tempPrefix = storage.STStorage } - bcSpout.dao.Store.Seek(bcSpout.dao.StoragePrefix.Bytes(), func(k, v []byte) { + bcSpout.dao.Store.Seek(bcSpout.dao.Version.StoragePrefix.Bytes(), func(k, v []byte) { key := slice.Copy(k) key[0] = byte(tempPrefix) value := slice.Copy(v) diff --git a/pkg/core/dao/dao.go b/pkg/core/dao/dao.go index fd2817c7a..a82519fb5 100644 --- a/pkg/core/dao/dao.go +++ b/pkg/core/dao/dao.go @@ -74,22 +74,20 @@ type DAO interface { // Simple is memCached wrapper around DB, simple DAO implementation. type Simple struct { - StoragePrefix storage.KeyPrefix - Store *storage.MemCachedStore - // stateRootInHeader specifies if block header contains state root. - stateRootInHeader bool - // p2pSigExtensions denotes whether P2PSignatureExtensions are enabled. - p2pSigExtensions bool + Version Version + Store *storage.MemCachedStore } // NewSimple creates new simple dao using provided backend store. func NewSimple(backend storage.Store, stateRootInHeader bool, p2pSigExtensions bool) *Simple { st := storage.NewMemCachedStore(backend) return &Simple{ - StoragePrefix: storage.STStorage, - Store: st, - stateRootInHeader: stateRootInHeader, - p2pSigExtensions: p2pSigExtensions, + Version: Version{ + StoragePrefix: storage.STStorage, + StateRootInHeader: stateRootInHeader, + P2PSigExtensions: p2pSigExtensions, + }, + Store: st, } } @@ -101,8 +99,8 @@ func (dao *Simple) GetBatch() *storage.MemBatch { // GetWrapped returns new DAO instance with another layer of wrapped // MemCachedStore around the current DAO Store. func (dao *Simple) GetWrapped() DAO { - d := NewSimple(dao.Store, dao.stateRootInHeader, dao.p2pSigExtensions) - d.StoragePrefix = dao.StoragePrefix + d := NewSimple(dao.Store, dao.Version.StateRootInHeader, dao.Version.P2PSigExtensions) + d.Version = dao.Version return d } @@ -284,7 +282,7 @@ func (dao *Simple) PutAppExecResult(aer *state.AppExecResult, buf *io.BufBinWrit // GetStorageItem returns StorageItem if it exists in the given store. func (dao *Simple) GetStorageItem(id int32, key []byte) state.StorageItem { - b, err := dao.Store.Get(makeStorageItemKey(dao.StoragePrefix, id, key)) + b, err := dao.Store.Get(makeStorageItemKey(dao.Version.StoragePrefix, id, key)) if err != nil { return nil } @@ -294,14 +292,14 @@ func (dao *Simple) GetStorageItem(id int32, key []byte) state.StorageItem { // PutStorageItem puts given StorageItem for given id with given // key into the given store. func (dao *Simple) PutStorageItem(id int32, key []byte, si state.StorageItem) error { - stKey := makeStorageItemKey(dao.StoragePrefix, id, key) + stKey := makeStorageItemKey(dao.Version.StoragePrefix, id, key) return dao.Store.Put(stKey, si) } // DeleteStorageItem drops storage item for the given id with the // given key from the store. func (dao *Simple) DeleteStorageItem(id int32, key []byte) error { - stKey := makeStorageItemKey(dao.StoragePrefix, id, key) + stKey := makeStorageItemKey(dao.Version.StoragePrefix, id, key) return dao.Store.Delete(stKey) } @@ -330,7 +328,7 @@ func (dao *Simple) GetStorageItemsWithPrefix(id int32, prefix []byte) ([]state.S // Seek executes f for all items with a given prefix. // If key is to be used outside of f, they may not be copied. func (dao *Simple) Seek(id int32, prefix []byte, f func(k, v []byte)) { - lookupKey := makeStorageItemKey(dao.StoragePrefix, id, nil) + lookupKey := makeStorageItemKey(dao.Version.StoragePrefix, id, nil) if prefix != nil { lookupKey = append(lookupKey, prefix...) } @@ -342,7 +340,7 @@ func (dao *Simple) Seek(id int32, prefix []byte, f func(k, v []byte)) { // SeekAsync sends all storage items matching given prefix to a channel and returns // the channel. Resulting keys and values may not be copied. func (dao *Simple) SeekAsync(ctx context.Context, id int32, prefix []byte) chan storage.KeyValue { - lookupKey := makeStorageItemKey(dao.StoragePrefix, id, nil) + lookupKey := makeStorageItemKey(dao.Version.StoragePrefix, id, nil) if prefix != nil { lookupKey = append(lookupKey, prefix...) } @@ -371,7 +369,7 @@ func (dao *Simple) GetBlock(hash util.Uint256) (*block.Block, error) { return nil, err } - block, err := block.NewBlockFromTrimmedBytes(dao.stateRootInHeader, b) + block, err := block.NewBlockFromTrimmedBytes(dao.Version.StateRootInHeader, b) if err != nil { return nil, err } @@ -380,10 +378,17 @@ func (dao *Simple) GetBlock(hash util.Uint256) (*block.Block, error) { // Version represents current dao version. type Version struct { - Prefix storage.KeyPrefix - Value string + StoragePrefix storage.KeyPrefix + StateRootInHeader bool + P2PSigExtensions bool + Value string } +const ( + stateRootInHeaderBit = 1 << iota + p2pSigExtensionsBit +) + // FromBytes decodes v from a byte-slice. func (v *Version) FromBytes(data []byte) error { if len(data) == 0 { @@ -398,14 +403,27 @@ func (v *Version) FromBytes(data []byte) error { return nil } + if len(data) != i+3 { + return errors.New("version is invalid") + } + v.Value = string(data[:i]) - v.Prefix = storage.KeyPrefix(data[i+1]) + v.StoragePrefix = storage.KeyPrefix(data[i+1]) + v.StateRootInHeader = data[i+2]&stateRootInHeaderBit != 0 + v.P2PSigExtensions = data[i+2]&p2pSigExtensionsBit != 0 return nil } // Bytes encodes v to a byte-slice. func (v *Version) Bytes() []byte { - return append([]byte(v.Value), '\x00', byte(v.Prefix)) + var mask byte + if v.StateRootInHeader { + mask |= stateRootInHeaderBit + } + if v.P2PSigExtensions { + mask |= p2pSigExtensionsBit + } + return append([]byte(v.Value), '\x00', byte(v.StoragePrefix), mask) } // GetVersion attempts to get the current version stored in the @@ -521,7 +539,7 @@ func (dao *Simple) GetTransaction(hash util.Uint256) (*transaction.Transaction, // PutVersion stores the given version in the underlying store. func (dao *Simple) PutVersion(v Version) error { - dao.StoragePrefix = v.Prefix + dao.Version = v return dao.Store.Put(storage.SYSVersion.Bytes(), v.Bytes()) } @@ -607,7 +625,7 @@ func (dao *Simple) DeleteBlock(h util.Uint256, w *io.BufBinWriter) error { return err } - b, err := block.NewBlockFromTrimmedBytes(dao.stateRootInHeader, bs) + b, err := block.NewBlockFromTrimmedBytes(dao.Version.StateRootInHeader, bs) if err != nil { return err } @@ -626,7 +644,7 @@ func (dao *Simple) DeleteBlock(h util.Uint256, w *io.BufBinWriter) error { for _, tx := range b.Transactions { copy(key[1:], tx.Hash().BytesBE()) batch.Delete(key) - if dao.p2pSigExtensions { + if dao.Version.P2PSigExtensions { for _, attr := range tx.GetAttributes(transaction.ConflictsT) { hash := attr.Value.(*transaction.Conflicts).Hash copy(key[1:], hash.BytesBE()) @@ -674,7 +692,7 @@ func (dao *Simple) StoreAsTransaction(tx *transaction.Transaction, index uint32, if err != nil { return err } - if dao.p2pSigExtensions { + if dao.Version.P2PSigExtensions { var value []byte for _, attr := range tx.GetAttributes(transaction.ConflictsT) { hash := attr.Value.(*transaction.Conflicts).Hash @@ -710,7 +728,7 @@ func (dao *Simple) PersistSync() (int, error) { // GetMPTBatch storage changes to be applied to MPT. func (dao *Simple) GetMPTBatch() mpt.Batch { var b mpt.Batch - dao.Store.MemoryStore.SeekAll([]byte{byte(dao.StoragePrefix)}, func(k, v []byte) { + dao.Store.MemoryStore.SeekAll([]byte{byte(dao.Version.StoragePrefix)}, func(k, v []byte) { b.Add(k[1:], v) }) return b diff --git a/pkg/core/dao/dao_test.go b/pkg/core/dao/dao_test.go index 7d1c3d9c2..29c6de4b8 100644 --- a/pkg/core/dao/dao_test.go +++ b/pkg/core/dao/dao_test.go @@ -120,13 +120,25 @@ func TestGetVersion_NoVersion(t *testing.T) { func TestGetVersion(t *testing.T) { dao := NewSimple(storage.NewMemoryStore(), false, false) - err := dao.PutVersion(Version{Prefix: 0x42, Value: "testVersion"}) + expected := Version{ + StoragePrefix: 0x42, + P2PSigExtensions: true, + StateRootInHeader: true, + Value: "testVersion", + } + err := dao.PutVersion(expected) require.NoError(t, err) - version, err := dao.GetVersion() + actual, err := dao.GetVersion() require.NoError(t, err) - require.EqualValues(t, 0x42, version.Prefix) - require.Equal(t, "testVersion", version.Value) + require.Equal(t, expected, actual) + t.Run("invalid", func(t *testing.T) { + dao := NewSimple(storage.NewMemoryStore(), false, false) + require.NoError(t, dao.Store.Put(storage.SYSVersion.Bytes(), []byte("0.1.2\x00x"))) + + _, err := dao.GetVersion() + require.Error(t, err) + }) t.Run("old format", func(t *testing.T) { dao := NewSimple(storage.NewMemoryStore(), false, false) require.NoError(t, dao.Store.Put(storage.SYSVersion.Bytes(), []byte("0.1.2"))) diff --git a/pkg/core/statesync/module.go b/pkg/core/statesync/module.go index a852f7f4d..9bdd1a775 100644 --- a/pkg/core/statesync/module.go +++ b/pkg/core/statesync/module.go @@ -208,7 +208,7 @@ func (s *Module) defineSyncStage() error { return fmt.Errorf("failed to get header to initialize MPT billet: %w", err) } s.billet = mpt.NewBillet(header.PrevStateRoot, s.bc.GetConfig().KeepOnlyLatestState, - TemporaryPrefix(s.dao.StoragePrefix), s.dao.Store) + TemporaryPrefix(s.dao.Version.StoragePrefix), s.dao.Store) s.log.Info("MPT billet initialized", zap.Uint32("height", s.syncPoint), zap.String("state root", header.PrevStateRoot.StringBE())) diff --git a/pkg/core/statesync/module_test.go b/pkg/core/statesync/module_test.go index 1af841129..538a273eb 100644 --- a/pkg/core/statesync/module_test.go +++ b/pkg/core/statesync/module_test.go @@ -57,7 +57,7 @@ func TestModule_PR2019_discussion_r689629704(t *testing.T) { mptpool: NewPool(), } stateSync.billet = mpt.NewBillet(sr, true, - TemporaryPrefix(stateSync.dao.StoragePrefix), actualStorage) + TemporaryPrefix(stateSync.dao.Version.StoragePrefix), actualStorage) stateSync.mptpool.Add(sr, []byte{}) // The test itself: we'll ask state sync module to restore each node exactly once. diff --git a/pkg/core/statesync_test.go b/pkg/core/statesync_test.go index bd493005d..7f7803d89 100644 --- a/pkg/core/statesync_test.go +++ b/pkg/core/statesync_test.go @@ -423,7 +423,7 @@ func TestStateSyncModule_RestoreBasicChain(t *testing.T) { // compare storage states fetchStorage := func(bc *Blockchain) []storage.KeyValue { var kv []storage.KeyValue - bc.dao.Store.Seek(bc.dao.StoragePrefix.Bytes(), func(k, v []byte) { + bc.dao.Store.Seek(bc.dao.Version.StoragePrefix.Bytes(), func(k, v []byte) { key := slice.Copy(k) value := slice.Copy(v) if key[0] == byte(storage.STTempStorage) { @@ -450,6 +450,6 @@ func TestStateSyncModule_RestoreBasicChain(t *testing.T) { bcBolt = initTestChain(t, bcBoltStore, boltCfg) go bcBolt.Run() defer bcBolt.Close() - require.Equal(t, storage.STTempStorage, bcBolt.dao.StoragePrefix) - require.Equal(t, storage.STTempStorage, bcBolt.persistent.StoragePrefix) + require.Equal(t, storage.STTempStorage, bcBolt.dao.Version.StoragePrefix) + require.Equal(t, storage.STTempStorage, bcBolt.persistent.Version.StoragePrefix) }