forked from TrueCloudLab/neoneo-go
Merge pull request #1866 from nspcc-dev/fix-broken-stateroots
Fix broken stateroots
This commit is contained in:
commit
8d195e6687
8 changed files with 62 additions and 41 deletions
|
@ -501,12 +501,6 @@ func (bc *Blockchain) AddBlock(block *block.Block) error {
|
||||||
return fmt.Errorf("%w: %v != %v",
|
return fmt.Errorf("%w: %v != %v",
|
||||||
ErrHdrStateRootSetting, bc.config.StateRootInHeader, block.StateRootEnabled)
|
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 {
|
if block.Index == bc.HeaderHeight()+1 {
|
||||||
err := bc.addHeaders(bc.config.VerifyBlocks, &block.Header)
|
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)
|
d := cache.DAO.(*dao.Simple)
|
||||||
b := d.GetMPTBatch()
|
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.
|
// Here MPT can be left in a half-applied state.
|
||||||
// However if this error occurs, this is a bug somewhere in code
|
// However if this error occurs, this is a bug somewhere in code
|
||||||
// because changes applied are the ones from HALTed transactions.
|
// 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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mpt.Store = bc.dao.Store
|
||||||
|
bc.stateRoot.UpdateCurrentLocal(mpt, sr)
|
||||||
bc.topBlock.Store(block)
|
bc.topBlock.Store(block)
|
||||||
atomic.StoreUint32(&bc.blockHeight, block.Index)
|
atomic.StoreUint32(&bc.blockHeight, block.Index)
|
||||||
bc.memPool.RemoveStale(func(tx *transaction.Transaction) bool { return bc.IsTxStillRelevant(tx, txpool, false) }, bc)
|
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 {
|
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 {
|
if prevHeader.Hash() != currHeader.PrevHash {
|
||||||
return ErrHdrHashMismatch
|
return ErrHdrHashMismatch
|
||||||
}
|
}
|
||||||
|
|
|
@ -557,7 +557,8 @@ func TestContractDestroy(t *testing.T) {
|
||||||
err = bc.dao.PutStorageItem(cs1.ID, []byte{1, 2, 3}, state.StorageItem{3, 2, 1})
|
err = bc.dao.PutStorageItem(cs1.ID, []byte{1, 2, 3}, state.StorageItem{3, 2, 1})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
b := bc.dao.GetMPTBatch()
|
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) {
|
t.Run("no contract", func(t *testing.T) {
|
||||||
res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "destroy")
|
res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "destroy")
|
||||||
|
|
|
@ -110,20 +110,33 @@ func (s *Module) Init(height uint32, enableRefCount bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddMPTBatch updates using provided batch.
|
// AddMPTBatch updates using provided batch.
|
||||||
func (s *Module) AddMPTBatch(index uint32, b mpt.Batch) error {
|
func (s *Module) AddMPTBatch(index uint32, b mpt.Batch, cache *storage.MemCachedStore) (*mpt.Trie, *state.MPTRoot, error) {
|
||||||
if _, err := s.mpt.PutBatch(b); err != nil {
|
mpt := *s.mpt
|
||||||
return err
|
mpt.Store = cache
|
||||||
|
if _, err := mpt.PutBatch(b); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
s.mpt.Flush()
|
mpt.Flush()
|
||||||
err := s.addLocalStateRoot(&state.MPTRoot{
|
sr := &state.MPTRoot{
|
||||||
Index: index,
|
Index: index,
|
||||||
Root: s.mpt.StateRoot(),
|
Root: mpt.StateRoot(),
|
||||||
})
|
}
|
||||||
if err != nil {
|
err := s.addLocalStateRoot(cache, sr)
|
||||||
return err
|
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.
|
// VerifyStateRoot checks if state root is valid.
|
||||||
|
|
|
@ -2,42 +2,41 @@ package stateroot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"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/core/storage"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"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 (
|
const (
|
||||||
prefixGC = 0x01
|
prefixGC = 0x01
|
||||||
prefixLocal = 0x02
|
prefixLocal = 0x02
|
||||||
prefixValidated = 0x03
|
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)
|
key := makeStateRootKey(sr.Index)
|
||||||
if err := s.putStateRoot(key, sr); err != nil {
|
if err := putStateRoot(store, key, sr); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
data := make([]byte, 4)
|
data := make([]byte, 4)
|
||||||
binary.LittleEndian.PutUint32(data, sr.Index)
|
binary.LittleEndian.PutUint32(data, sr.Index)
|
||||||
if err := s.Store.Put([]byte{byte(storage.DataMPT), prefixLocal}, data); err != nil {
|
return store.Put([]byte{byte(storage.DataMPT), prefixLocal}, data)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Module) putStateRoot(key []byte, sr *state.MPTRoot) error {
|
func putStateRoot(store *storage.MemCachedStore, key []byte, sr *state.MPTRoot) error {
|
||||||
w := io.NewBufBinWriter()
|
w := io.NewBufBinWriter()
|
||||||
sr.EncodeBinary(w.BinWriter)
|
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) {
|
func (s *Module) getStateRoot(key []byte) (*state.MPTRoot, error) {
|
||||||
|
@ -69,10 +68,13 @@ func (s *Module) AddStateRoot(sr *state.MPTRoot) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
if len(local.Witness) != 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err := s.putStateRoot(key, sr); err != nil {
|
if err := putStateRoot(s.Store, key, sr); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,6 +81,7 @@ func (s *service) sendValidatedRoot(r *state.MPTRoot, priv *keys.PrivateKey) {
|
||||||
m := NewMessage(RootT, r)
|
m := NewMessage(RootT, r)
|
||||||
m.EncodeBinary(w.BinWriter)
|
m.EncodeBinary(w.BinWriter)
|
||||||
ep := &payload.Extensible{
|
ep := &payload.Extensible{
|
||||||
|
Category: Category,
|
||||||
ValidBlockStart: r.Index,
|
ValidBlockStart: r.Index,
|
||||||
ValidBlockEnd: r.Index + transaction.MaxValidUntilBlockIncrement,
|
ValidBlockEnd: r.Index + transaction.MaxValidUntilBlockIncrement,
|
||||||
Sender: priv.GetScriptHash(),
|
Sender: priv.GetScriptHash(),
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
"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/blockchainer"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"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/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
"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 {
|
if sr.Index == 0 {
|
||||||
return nil
|
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:
|
case VoteT:
|
||||||
v := m.Payload.(*Vote)
|
v := m.Payload.(*Vote)
|
||||||
return s.AddSignature(v.Height, v.ValidatorIndex, v.Signature)
|
return s.AddSignature(v.Height, v.ValidatorIndex, v.Signature)
|
||||||
|
|
|
@ -33,12 +33,6 @@ type (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func newIncompleteRoot() *incompleteRoot {
|
|
||||||
return &incompleteRoot{
|
|
||||||
sigs: make(map[string]*rootSig),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *incompleteRoot) reverify(net netmode.Magic) {
|
func (r *incompleteRoot) reverify(net netmode.Magic) {
|
||||||
for _, sig := range r.sigs {
|
for _, sig := range r.sigs {
|
||||||
if !sig.ok {
|
if !sig.ok {
|
||||||
|
|
|
@ -68,6 +68,7 @@ func (s *service) signAndSend(r *state.MPTRoot) error {
|
||||||
return w.Err
|
return w.Err
|
||||||
}
|
}
|
||||||
e := &payload.Extensible{
|
e := &payload.Extensible{
|
||||||
|
Category: Category,
|
||||||
ValidBlockStart: r.Index,
|
ValidBlockStart: r.Index,
|
||||||
ValidBlockEnd: r.Index + transaction.MaxValidUntilBlockIncrement,
|
ValidBlockEnd: r.Index + transaction.MaxValidUntilBlockIncrement,
|
||||||
Sender: s.getAccount().PrivateKey().GetScriptHash(),
|
Sender: s.getAccount().PrivateKey().GetScriptHash(),
|
||||||
|
|
Loading…
Reference in a new issue