Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion cli/util/upload_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ loop:
attrs = []object.Attribute{
object.NewAttribute(attr, strconv.Itoa(int(height))),
object.NewAttribute("Timestamp", strconv.FormatInt(time.Now().Unix(), 10)),
object.NewAttribute("StateRoot", stateRoot.Root.StringLE()),
object.NewAttribute(neofs.DefaultStateRootAttribute, stateRoot.Root.StringLE()),
object.NewAttribute("StateSyncInterval", strconv.Itoa(syncInterval)),
object.NewAttribute("BlockTime", strconv.FormatUint(h.Timestamp, 10)),
}
Expand Down
7 changes: 6 additions & 1 deletion internal/fakechain/fakechain.go
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ func (s *FakeStateSync) AddMPTNodes(nodes [][]byte) error {
}

// AddContractStorageItems implements the StateSync interface.
func (s *FakeStateSync) AddContractStorageItems(kv []storage.KeyValue, syncHeight uint32, expectedRoot util.Uint256) error {
func (s *FakeStateSync) AddContractStorageItems(kv []storage.KeyValue, syncHeight uint32, expectedRoot util.Uint256, witness transaction.Witness) error {
panic("TODO")
}

Expand Down Expand Up @@ -527,3 +527,8 @@ func (s *FakeStateSync) GetStateSyncPoint() uint32 {
func (s *FakeStateSync) GetLastStoredKey() []byte {
panic("TODO")
}

// VerifyWitness implements the StateSync interface.
func (s *FakeStateSync) VerifyWitness(h util.Uint160, c hash.Hashable, w *transaction.Witness, gas int64) (int64, error) {
panic("TODO")
}
7 changes: 5 additions & 2 deletions pkg/core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -868,14 +868,17 @@ func (bc *Blockchain) jumpToStateInternal(p uint32, stage stateChangeStage) erro
}
root = blk.PrevStateRoot
}
bc.stateRoot.JumpToState(&state.MPTRoot{
err := bc.stateRoot.JumpToState(&state.MPTRoot{
Index: p,
Root: root,
})
if err != nil {
return fmt.Errorf("failed to update stateroot module state: %w", err)
}

bc.dao.Store.Delete(jumpStageKey)
bc.dao.Store.Delete([]byte{byte(storage.SYSStateSyncCheckpoint)})
_, err := bc.dao.Store.Persist()
_, err = bc.dao.Store.Persist()
if err != nil {
return fmt.Errorf("failed to persist %d stage of state jump: %w", staleBlocksRemoved, err)
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/core/dao/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,9 @@ func (dao *Simple) GetStateSyncPoint() (uint32, error) {
type StateSyncCheckpoint struct {
// MPTRoot is a computed intermediate MPT root at the StateSyncCheckpoint.
MPTRoot util.Uint256
// Witness is the actual witness of state root at the StateSyncPoint, it
// may be empty if the state object doesn't contain it.
Witness transaction.Witness
// IsMPTSynced indicates whether sync process is completed.
IsMPTSynced bool
// LastStoredKey is the last processed storage key.
Expand All @@ -593,13 +596,15 @@ func (s *StateSyncCheckpoint) EncodeBinary(w *io.BinWriter) {
w.WriteBytes(s.MPTRoot[:])
w.WriteBool(s.IsMPTSynced)
w.WriteVarBytes(s.LastStoredKey)
s.Witness.EncodeBinary(w)
}

// DecodeBinary decodes StateSyncCheckpoint from binary format.
func (s *StateSyncCheckpoint) DecodeBinary(br *io.BinReader) {
br.ReadBytes(s.MPTRoot[:])
s.IsMPTSynced = br.ReadBool()
s.LastStoredKey = br.ReadVarBytes()
s.Witness.DecodeBinary(br)
}

// GetStateSyncCheckpoint returns the current StateSyncCheckpoint.
Expand Down
4 changes: 4 additions & 0 deletions pkg/core/dao/dao_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,10 @@ func TestPutGetStateSyncCheckPoint(t *testing.T) {
MPTRoot: util.Uint256{1, 2, 3},
IsMPTSynced: true,
LastStoredKey: []byte{1, 2, 3},
Witness: transaction.Witness{
InvocationScript: []byte{1, 2, 3},
VerificationScript: []byte{1, 2, 3},
},
}
dao.PutStateSyncCheckpoint(expected)
actual, err := dao.GetStateSyncCheckpoint()
Expand Down
10 changes: 7 additions & 3 deletions pkg/core/stateroot/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ func (s *Module) CleanStorage() error {
}

// JumpToState performs jump to the state specified by given stateroot index.
func (s *Module) JumpToState(sr *state.MPTRoot) {
func (s *Module) JumpToState(sr *state.MPTRoot) error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always returns nil (at least as of the commit where it's added).

s.addLocalStateRoot(s.Store, sr)

data := make([]byte, 4)
Expand All @@ -234,6 +234,8 @@ func (s *Module) JumpToState(sr *state.MPTRoot) {
s.currentLocal.Store(sr.Root)
s.localHeight.Store(sr.Index)
s.mpt = mpt.NewTrie(mpt.NewHashNode(sr.Root), s.mode, s.Store)

return nil
}

// ResetState resets MPT state to the given height.
Expand Down Expand Up @@ -371,13 +373,15 @@ func (s *Module) VerifyStateRoot(r *state.MPTRoot) error {
return s.verifyWitness(r)
}

const maxVerificationGAS = 2_00000000
// MaxVerificationGAS is the maximum amount of GAS that can be spent for stateroot
// witness verification.
const MaxVerificationGAS = 2_00000000

// verifyWitness verifies state root witness.
func (s *Module) verifyWitness(r *state.MPTRoot) error {
s.mtx.Lock()
h := s.getKeyCacheForHeight(r.Index).validatorsHash
s.mtx.Unlock()
_, err := s.verifier(h, r, &r.Witness[0], maxVerificationGAS)
_, err := s.verifier(h, r, &r.Witness[0], MaxVerificationGAS)
return err
}
20 changes: 19 additions & 1 deletion pkg/core/statesync/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package statesync
//go:generate stringer -type=StorageSyncMode

import (
"bytes"
"encoding/binary"
"encoding/hex"
"errors"
Expand All @@ -34,6 +35,8 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/core/stateroot"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
Expand Down Expand Up @@ -89,6 +92,7 @@ type Ledger interface {
GetHeaderHash(uint32) util.Uint256
HeaderHeight() uint32
NativePolicyID() int32
VerifyWitness(h util.Uint160, c hash.Hashable, w *transaction.Witness, gas int64) (int64, error)
}

// Module represents state sync module and aimed to gather state-related data to
Expand Down Expand Up @@ -540,7 +544,7 @@ func (s *Module) AddMPTNodes(nodes [][]byte) error {
}

// AddContractStorageItems adds a batch of key-value pairs for storage-based sync.
func (s *Module) AddContractStorageItems(kvs []storage.KeyValue, syncHeight uint32, expectedRoot util.Uint256) error {
func (s *Module) AddContractStorageItems(kvs []storage.KeyValue, syncHeight uint32, expectedRoot util.Uint256, witness transaction.Witness) error {
if s.mode == MPTBased {
panic("contract storage items are not expected in MPT-based mode")
}
Expand Down Expand Up @@ -582,6 +586,7 @@ func (s *Module) AddContractStorageItems(kvs []storage.KeyValue, syncHeight uint
MPTRoot: s.localTrie.StateRoot(),
LastStoredKey: kvs[len(kvs)-1].Key,
IsMPTSynced: computedRoot.Equals(expectedRoot),
Witness: witness,
}
s.dao.PutStateSyncCheckpoint(ckpt)
if _, err := s.dao.Store.PersistSync(); err != nil {
Expand All @@ -599,6 +604,14 @@ func (s *Module) AddContractStorageItems(kvs []storage.KeyValue, syncHeight uint
if !header.PrevStateRoot.Equals(expectedRoot) {
return fmt.Errorf("state root mismatch: %s != %s", header.PrevStateRoot.StringLE(), expectedRoot.StringLE())
}
} else if len(witness.VerificationScript) > 0 {
header, err := s.bc.GetHeader(s.bc.GetHeaderHash(s.syncPoint))
if err != nil {
return fmt.Errorf("failed to get header to check state root: %w", err)
}
if !bytes.Equal(header.Script.VerificationScript, witness.VerificationScript) {
return fmt.Errorf("state root witness mismatch: %s != %s", hex.EncodeToString(header.Script.VerificationScript), hex.EncodeToString(witness.VerificationScript))
}
}
s.syncStage |= mptSynced
s.blockHeight = s.getLatestSavedBlock(s.syncPoint)
Expand Down Expand Up @@ -774,3 +787,8 @@ func (s *Module) GetStateSyncPoint() uint32 {

return s.syncPoint
}

// VerifyWitness implements [network.StateSync] interface.
func (s *Module) VerifyWitness(h util.Uint160, c hash.Hashable, w *transaction.Witness, gas int64) (int64, error) {
return s.bc.VerifyWitness(h, c, w, gas)
}
29 changes: 15 additions & 14 deletions pkg/core/statesync/neotest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/mpt"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
"github.com/nspcc-dev/neo-go/pkg/util"
Expand Down Expand Up @@ -169,13 +170,13 @@ func TestStateSyncModule_Init(t *testing.T) {
}
} else {
// check AddContractStorageItems parameters
require.ErrorContains(t, module.AddContractStorageItems([]storage.KeyValue{}, stateSyncPoint-3, sroot.Root), "invalid sync height:")
require.ErrorContains(t, module.AddContractStorageItems([]storage.KeyValue{}, stateSyncPoint, sroot.Root), "key-value pairs are empty")
require.ErrorContains(t, module.AddContractStorageItems([]storage.KeyValue{}, stateSyncPoint-3, sroot.Root, transaction.Witness{}), "invalid sync height:")
require.ErrorContains(t, module.AddContractStorageItems([]storage.KeyValue{}, stateSyncPoint, sroot.Root, transaction.Witness{}), "key-value pairs are empty")
var batch []storage.KeyValue
sm.SeekStates(sroot.Root, nil, func(k, v []byte) bool {
batch = append(batch, storage.KeyValue{Key: k, Value: v})
if len(batch) == 2 {
require.NoError(t, module.AddContractStorageItems(batch, stateSyncPoint, sroot.Root))
require.NoError(t, module.AddContractStorageItems(batch, stateSyncPoint, sroot.Root, transaction.Witness{}))
lastKey = batch[len(batch)-1].Key
return false // stop seeking
}
Expand Down Expand Up @@ -231,10 +232,10 @@ func TestStateSyncModule_Init(t *testing.T) {
}
return true // skip this key
}
require.NoError(t, module.AddContractStorageItems([]storage.KeyValue{{Key: k, Value: v}}, stateSyncPoint, sroot.Root))
require.NoError(t, module.AddContractStorageItems([]storage.KeyValue{{Key: k, Value: v}}, stateSyncPoint, sroot.Root, transaction.Witness{}))
return true
})
require.ErrorContains(t, module.AddContractStorageItems([]storage.KeyValue{{Key: []byte{1}, Value: []byte{1}}}, stateSyncPoint, sroot.Root), "contract storage items were not requested")
require.ErrorContains(t, module.AddContractStorageItems([]storage.KeyValue{{Key: []byte{1}, Value: []byte{1}}}, stateSyncPoint, sroot.Root, transaction.Witness{}), "contract storage items were not requested")
}
// check that module is active and storage data is in sync
require.True(t, module.IsActive())
Expand Down Expand Up @@ -269,7 +270,7 @@ func TestStateSyncModule_Init(t *testing.T) {
unknownNodes := module.GetUnknownMPTNodesBatch(2)
require.Equal(t, 0, len(unknownNodes))
} else {
require.ErrorContains(t, module.AddContractStorageItems([]storage.KeyValue{{Key: []byte{1}, Value: []byte{1}}}, stateSyncPoint, sroot.Root), "contract storage items were not requested")
require.ErrorContains(t, module.AddContractStorageItems([]storage.KeyValue{{Key: []byte{1}, Value: []byte{1}}}, stateSyncPoint, sroot.Root, transaction.Witness{}), "contract storage items were not requested")
}
require.Equal(t, uint32(stateSyncPoint-stateSyncInterval-1), module.BlockHeight())

Expand Down Expand Up @@ -308,7 +309,7 @@ func TestStateSyncModule_Init(t *testing.T) {
unknownNodes := module.GetUnknownMPTNodesBatch(1)
require.True(t, len(unknownNodes) == 0)
} else {
require.ErrorContains(t, module.AddContractStorageItems([]storage.KeyValue{{Key: []byte{1}, Value: []byte{1}}}, stateSyncPoint, sroot.Root), "contract storage items were not requested")
require.ErrorContains(t, module.AddContractStorageItems([]storage.KeyValue{{Key: []byte{1}, Value: []byte{1}}}, stateSyncPoint, sroot.Root, transaction.Witness{}), "contract storage items were not requested")
}
require.Equal(t, uint32(0), module.BlockHeight()) // inactive -> 0
require.Equal(t, uint32(stateSyncPoint), bcBolt.BlockHeight())
Expand All @@ -323,7 +324,7 @@ func TestStateSyncModule_Init(t *testing.T) {
unknownNodes := module.GetUnknownMPTNodesBatch(1)
require.True(t, len(unknownNodes) == 0)
} else {
require.Error(t, module.AddContractStorageItems([]storage.KeyValue{{Key: []byte{1}, Value: []byte{1}}}, stateSyncPoint, sroot.Root), "contract storage items were not requested")
require.Error(t, module.AddContractStorageItems([]storage.KeyValue{{Key: []byte{1}, Value: []byte{1}}}, stateSyncPoint, sroot.Root, transaction.Witness{}), "contract storage items were not requested")
}
require.Equal(t, uint32(0), module.BlockHeight()) // inactive -> 0
require.Equal(t, uint32(stateSyncPoint), bcBolt.BlockHeight())
Expand All @@ -340,7 +341,7 @@ func TestStateSyncModule_Init(t *testing.T) {
unknownNodes := module.GetUnknownMPTNodesBatch(1)
require.True(t, len(unknownNodes) == 0)
} else {
require.ErrorContains(t, module.AddContractStorageItems([]storage.KeyValue{{Key: []byte{1}, Value: []byte{1}}}, stateSyncPoint, sroot.Root), "contract storage items were not requested")
require.ErrorContains(t, module.AddContractStorageItems([]storage.KeyValue{{Key: []byte{1}, Value: []byte{1}}}, stateSyncPoint, sroot.Root, transaction.Witness{}), "contract storage items were not requested")
}
require.Equal(t, uint32(0), module.BlockHeight()) // inactive -> 0
require.Equal(t, uint32(stateSyncPoint)+1, bcBolt.BlockHeight())
Expand Down Expand Up @@ -439,12 +440,12 @@ func TestStateSyncModule_RestoreBasicChain(t *testing.T) {
})
})
t.Run("error: add ContractStorage items without initialisation", func(t *testing.T) {
require.Error(t, module.AddContractStorageItems([]storage.KeyValue{}, 123, util.Uint256{}))
require.Error(t, module.AddContractStorageItems([]storage.KeyValue{}, 123, util.Uint256{}, transaction.Witness{}))
})
} else {
t.Run("panic: add contract storage items in MPTBased mode", func(t *testing.T) {
require.Panics(t, func() {
err := module.AddContractStorageItems([]storage.KeyValue{}, 123, util.Uint256{})
err := module.AddContractStorageItems([]storage.KeyValue{}, 123, util.Uint256{}, transaction.Witness{})
if err != nil {
return
}
Expand Down Expand Up @@ -491,14 +492,14 @@ func TestStateSyncModule_RestoreBasicChain(t *testing.T) {
sm.SeekStates(sroot.Root, nil, func(k, v []byte) bool {
batch = append(batch, storage.KeyValue{Key: k, Value: v})
if len(batch) >= 3 {
err = module.AddContractStorageItems(batch, uint32(stateSyncPoint), sroot.Root)
err = module.AddContractStorageItems(batch, uint32(stateSyncPoint), sroot.Root, transaction.Witness{})
require.NoError(t, err)
batch = batch[:0]
}
return true
})
if len(batch) > 0 {
err = module.AddContractStorageItems(batch, uint32(stateSyncPoint), sroot.Root)
err = module.AddContractStorageItems(batch, uint32(stateSyncPoint), sroot.Root, transaction.Witness{})
require.NoError(t, err)
}
require.NoError(t, err)
Expand Down Expand Up @@ -707,7 +708,7 @@ func TestStateSyncModule_SetOnStageChanged(t *testing.T) {
all = append(all, storage.KeyValue{Key: k, Value: v})
return true
})
require.NoError(t, module.AddContractStorageItems(all, syncPoint, sroot.Root))
require.NoError(t, module.AddContractStorageItems(all, syncPoint, sroot.Root, transaction.Witness{}))
}
require.Equal(t, 3, calls)

Expand Down
5 changes: 4 additions & 1 deletion pkg/network/state_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ import (
"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/core/mpt"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/util"
)

// StateSync represents state sync module.
type StateSync interface {
blockHeaderQueuer
AddMPTNodes([][]byte) error
AddContractStorageItems(kvs []storage.KeyValue, syncHeight uint32, expectedRoot util.Uint256) error
AddContractStorageItems(kvs []storage.KeyValue, syncHeight uint32, expectedRoot util.Uint256, witness transaction.Witness) error
Init(currChainHeight uint32) error
IsActive() bool
IsInitialized() bool
Expand All @@ -24,4 +26,5 @@ type StateSync interface {
GetStateSyncPoint() uint32
SetOnStageChanged(func())
Traverse(root util.Uint256, process func(node mpt.Node, nodeBytes []byte) bool) error
VerifyWitness(h util.Uint160, c hash.Hashable, w *transaction.Witness, gas int64) (int64, error)
}
2 changes: 2 additions & 0 deletions pkg/services/helpers/neofs/blockstorage.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ const (
DefaultStateAttribute = "State"
// DefaultWitnessAttribute is the default attribute name for state object witness.
DefaultWitnessAttribute = "Witness"
// DefaultStateRootAttribute is the default attribute name for the stateroot field of a state object.
DefaultStateRootAttribute = "StateRoot"
// DefaultKVBatchSize is a number of contract storage key-value objects to
// flush to the node's DB in a batch.
DefaultKVBatchSize = 1000
Expand Down
Loading