diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index face98d17..41c2758e2 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -501,12 +501,6 @@ func (bc *Blockchain) AddBlock(block *block.Block) error { return fmt.Errorf("%w: %v != %v", ErrHdrStateRootSetting, bc.config.StateRootInHeader, block.StateRootEnabled) } - if bc.config.StateRootInHeader { - if sr := bc.stateRoot.CurrentLocalStateRoot(); block.PrevStateRoot != sr { - return fmt.Errorf("%w: %s != %s", - ErrHdrInvalidStateRoot, block.PrevStateRoot.StringLE(), sr.StringLE()) - } - } if block.Index == bc.HeaderHeight()+1 { err := bc.addHeaders(bc.config.VerifyBlocks, &block.Header) @@ -737,7 +731,8 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error d := cache.DAO.(*dao.Simple) b := d.GetMPTBatch() - if err := bc.stateRoot.AddMPTBatch(block.Index, b); err != nil { + mpt, sr, err := bc.stateRoot.AddMPTBatch(block.Index, b, d.Store) + if err != nil { // Here MPT can be left in a half-applied state. // However if this error occurs, this is a bug somewhere in code // because changes applied are the ones from HALTed transactions. @@ -767,6 +762,8 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error return err } + mpt.Store = bc.dao.Store + bc.stateRoot.UpdateCurrentLocal(mpt, sr) bc.topBlock.Store(block) atomic.StoreUint32(&bc.blockHeight, block.Index) bc.memPool.RemoveStale(func(tx *transaction.Transaction) bool { return bc.IsTxStillRelevant(tx, txpool, false) }, bc) @@ -1391,6 +1388,12 @@ var ( ) func (bc *Blockchain) verifyHeader(currHeader, prevHeader *block.Header) error { + if bc.config.StateRootInHeader { + if sr := bc.stateRoot.CurrentLocalStateRoot(); currHeader.PrevStateRoot != sr { + return fmt.Errorf("%w: %s != %s", + ErrHdrInvalidStateRoot, currHeader.PrevStateRoot.StringLE(), sr.StringLE()) + } + } if prevHeader.Hash() != currHeader.PrevHash { return ErrHdrHashMismatch } diff --git a/pkg/core/native_management_test.go b/pkg/core/native_management_test.go index 790deb469..b0f441e68 100644 --- a/pkg/core/native_management_test.go +++ b/pkg/core/native_management_test.go @@ -557,7 +557,8 @@ func TestContractDestroy(t *testing.T) { err = bc.dao.PutStorageItem(cs1.ID, []byte{1, 2, 3}, state.StorageItem{3, 2, 1}) require.NoError(t, err) b := bc.dao.GetMPTBatch() - require.NoError(t, bc.GetStateModule().(*stateroot.Module).AddMPTBatch(bc.BlockHeight(), b)) + _, _, err = bc.GetStateModule().(*stateroot.Module).AddMPTBatch(bc.BlockHeight(), b, bc.dao.Store) + require.NoError(t, err) t.Run("no contract", func(t *testing.T) { res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "destroy") diff --git a/pkg/core/stateroot/module.go b/pkg/core/stateroot/module.go index fa6f5127d..ec0aa6fb6 100644 --- a/pkg/core/stateroot/module.go +++ b/pkg/core/stateroot/module.go @@ -110,20 +110,33 @@ func (s *Module) Init(height uint32, enableRefCount bool) error { } // AddMPTBatch updates using provided batch. -func (s *Module) AddMPTBatch(index uint32, b mpt.Batch) error { - if _, err := s.mpt.PutBatch(b); err != nil { - return err +func (s *Module) AddMPTBatch(index uint32, b mpt.Batch, cache *storage.MemCachedStore) (*mpt.Trie, *state.MPTRoot, error) { + mpt := *s.mpt + mpt.Store = cache + if _, err := mpt.PutBatch(b); err != nil { + return nil, nil, err } - s.mpt.Flush() - err := s.addLocalStateRoot(&state.MPTRoot{ + mpt.Flush() + sr := &state.MPTRoot{ Index: index, - Root: s.mpt.StateRoot(), - }) - if err != nil { - return err + Root: mpt.StateRoot(), + } + err := s.addLocalStateRoot(cache, sr) + if err != nil { + return nil, nil, err + } + return &mpt, sr, err +} + +// UpdateCurrentLocal updates local caches using provided state root. +func (s *Module) UpdateCurrentLocal(mpt *mpt.Trie, sr *state.MPTRoot) { + s.mpt = mpt + s.currentLocal.Store(sr.Root) + s.localHeight.Store(sr.Index) + if s.bc.GetConfig().StateRootInHeader { + s.validatedHeight.Store(sr.Index) + updateStateHeightMetric(sr.Index) } - _, err = s.Store.Persist() - return err } // VerifyStateRoot checks if state root is valid. diff --git a/pkg/core/stateroot/store.go b/pkg/core/stateroot/store.go index 865073159..9efcf7c5f 100644 --- a/pkg/core/stateroot/store.go +++ b/pkg/core/stateroot/store.go @@ -2,42 +2,41 @@ package stateroot import ( "encoding/binary" + "errors" + "fmt" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/io" ) +var ( + // ErrStateMismatch means that local state root doesn't match the one + // signed by state validators. + ErrStateMismatch = errors.New("stateroot mismatch") +) + const ( prefixGC = 0x01 prefixLocal = 0x02 prefixValidated = 0x03 ) -func (s *Module) addLocalStateRoot(sr *state.MPTRoot) error { +func (s *Module) addLocalStateRoot(store *storage.MemCachedStore, sr *state.MPTRoot) error { key := makeStateRootKey(sr.Index) - if err := s.putStateRoot(key, sr); err != nil { + if err := putStateRoot(store, key, sr); err != nil { return err } data := make([]byte, 4) binary.LittleEndian.PutUint32(data, sr.Index) - if err := s.Store.Put([]byte{byte(storage.DataMPT), prefixLocal}, data); err != nil { - return err - } - s.currentLocal.Store(sr.Root) - s.localHeight.Store(sr.Index) - if s.bc.GetConfig().StateRootInHeader { - s.validatedHeight.Store(sr.Index) - updateStateHeightMetric(sr.Index) - } - return nil + return store.Put([]byte{byte(storage.DataMPT), prefixLocal}, data) } -func (s *Module) putStateRoot(key []byte, sr *state.MPTRoot) error { +func putStateRoot(store *storage.MemCachedStore, key []byte, sr *state.MPTRoot) error { w := io.NewBufBinWriter() sr.EncodeBinary(w.BinWriter) - return s.Store.Put(key, w.Bytes()) + return store.Put(key, w.Bytes()) } func (s *Module) getStateRoot(key []byte) (*state.MPTRoot, error) { @@ -69,10 +68,13 @@ func (s *Module) AddStateRoot(sr *state.MPTRoot) error { if err != nil { return err } + if !local.Root.Equals(sr.Root) { + return fmt.Errorf("%w at block %d: %v vs %v", ErrStateMismatch, sr.Index, local.Root, sr.Root) + } if len(local.Witness) != 0 { return nil } - if err := s.putStateRoot(key, sr); err != nil { + if err := putStateRoot(s.Store, key, sr); err != nil { return err } diff --git a/pkg/services/stateroot/network.go b/pkg/services/stateroot/network.go index 21174c3a5..af8e6fceb 100644 --- a/pkg/services/stateroot/network.go +++ b/pkg/services/stateroot/network.go @@ -81,6 +81,7 @@ func (s *service) sendValidatedRoot(r *state.MPTRoot, priv *keys.PrivateKey) { m := NewMessage(RootT, r) m.EncodeBinary(w.BinWriter) ep := &payload.Extensible{ + Category: Category, ValidBlockStart: r.Index, ValidBlockEnd: r.Index + transaction.MaxValidUntilBlockIncrement, Sender: priv.GetScriptHash(), diff --git a/pkg/services/stateroot/service.go b/pkg/services/stateroot/service.go index d8ebc4249..ba3a35998 100644 --- a/pkg/services/stateroot/service.go +++ b/pkg/services/stateroot/service.go @@ -9,6 +9,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/blockchainer" "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/core/stateroot" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/network/payload" @@ -107,7 +108,12 @@ func (s *service) OnPayload(ep *payload.Extensible) error { if sr.Index == 0 { return nil } - return s.AddStateRoot(sr) + err := s.AddStateRoot(sr) + if errors.Is(err, stateroot.ErrStateMismatch) { + s.log.Error("can't add SV-signed state root", zap.Error(err)) + return nil + } + return err case VoteT: v := m.Payload.(*Vote) return s.AddSignature(v.Height, v.ValidatorIndex, v.Signature) diff --git a/pkg/services/stateroot/signature.go b/pkg/services/stateroot/signature.go index 3ebbcbeb2..5262473f0 100644 --- a/pkg/services/stateroot/signature.go +++ b/pkg/services/stateroot/signature.go @@ -33,12 +33,6 @@ type ( } ) -func newIncompleteRoot() *incompleteRoot { - return &incompleteRoot{ - sigs: make(map[string]*rootSig), - } -} - func (r *incompleteRoot) reverify(net netmode.Magic) { for _, sig := range r.sigs { if !sig.ok { diff --git a/pkg/services/stateroot/validators.go b/pkg/services/stateroot/validators.go index fbd9a88d2..3a56041c4 100644 --- a/pkg/services/stateroot/validators.go +++ b/pkg/services/stateroot/validators.go @@ -68,6 +68,7 @@ func (s *service) signAndSend(r *state.MPTRoot) error { return w.Err } e := &payload.Extensible{ + Category: Category, ValidBlockStart: r.Index, ValidBlockEnd: r.Index + transaction.MaxValidUntilBlockIncrement, Sender: s.getAccount().PrivateKey().GetScriptHash(),