mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-25 23:42:23 +00:00
stateroot: use RoleStateValidator for verification
This commit is contained in:
parent
bf20db09e0
commit
ac227a80fe
12 changed files with 405 additions and 11 deletions
|
@ -276,7 +276,7 @@ func (chain *FakeChain) GetEnrollments() ([]state.Validator, error) {
|
||||||
|
|
||||||
// GetStateModule implements Blockchainer interface.
|
// GetStateModule implements Blockchainer interface.
|
||||||
func (chain *FakeChain) GetStateModule() blockchainer.StateRoot {
|
func (chain *FakeChain) GetStateModule() blockchainer.StateRoot {
|
||||||
panic("TODO")
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStorageItem implements Blockchainer interface.
|
// GetStorageItem implements Blockchainer interface.
|
||||||
|
|
|
@ -196,6 +196,7 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration, log *zap.L
|
||||||
}
|
}
|
||||||
|
|
||||||
bc.stateRoot = stateroot.NewModule(bc, bc.log, bc.dao.Store)
|
bc.stateRoot = stateroot.NewModule(bc, bc.log, bc.dao.Store)
|
||||||
|
bc.contracts.Designate.StateRootService = bc.stateRoot
|
||||||
|
|
||||||
if err := bc.init(); err != nil {
|
if err := bc.init(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -2,12 +2,16 @@ package blockchainer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"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/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StateRoot represents local state root module.
|
// StateRoot represents local state root module.
|
||||||
type StateRoot interface {
|
type StateRoot interface {
|
||||||
|
AddStateRoot(root *state.MPTRoot) error
|
||||||
CurrentLocalStateRoot() util.Uint256
|
CurrentLocalStateRoot() util.Uint256
|
||||||
|
CurrentValidatedHeight() uint32
|
||||||
GetStateProof(root util.Uint256, key []byte) ([][]byte, error)
|
GetStateProof(root util.Uint256, key []byte) ([][]byte, error)
|
||||||
GetStateRoot(height uint32) (*state.MPTRoot, error)
|
GetStateRoot(height uint32) (*state.MPTRoot, error)
|
||||||
|
UpdateStateValidators(height uint32, pubs keys.PublicKeys)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer/services"
|
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer/services"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||||
|
@ -40,6 +41,8 @@ type Designate struct {
|
||||||
OracleService atomic.Value
|
OracleService atomic.Value
|
||||||
// NotaryService represents Notary node module.
|
// NotaryService represents Notary node module.
|
||||||
NotaryService atomic.Value
|
NotaryService atomic.Value
|
||||||
|
// StateRootService represents StateRoot node module.
|
||||||
|
StateRootService blockchainer.StateRoot
|
||||||
}
|
}
|
||||||
|
|
||||||
type roleData struct {
|
type roleData struct {
|
||||||
|
@ -172,12 +175,10 @@ func (s *Designate) hashFromNodes(r Role, nodes keys.PublicKeys) util.Uint160 {
|
||||||
}
|
}
|
||||||
var script []byte
|
var script []byte
|
||||||
switch r {
|
switch r {
|
||||||
case RoleOracle, RoleNeoFSAlphabet:
|
|
||||||
script, _ = smartcontract.CreateDefaultMultiSigRedeemScript(nodes.Copy())
|
|
||||||
case RoleP2PNotary:
|
case RoleP2PNotary:
|
||||||
script, _ = smartcontract.CreateMultiSigRedeemScript(1, nodes.Copy())
|
script, _ = smartcontract.CreateMultiSigRedeemScript(1, nodes.Copy())
|
||||||
default:
|
default:
|
||||||
script, _ = smartcontract.CreateMajorityMultiSigRedeemScript(nodes.Copy())
|
script, _ = smartcontract.CreateDefaultMultiSigRedeemScript(nodes.Copy())
|
||||||
}
|
}
|
||||||
return hash.Hash160(script)
|
return hash.Hash160(script)
|
||||||
}
|
}
|
||||||
|
@ -201,6 +202,10 @@ func (s *Designate) updateCachedRoleData(v *atomic.Value, d dao.DAO, r Role) err
|
||||||
if ntr, _ := s.NotaryService.Load().(services.Notary); ntr != nil {
|
if ntr, _ := s.NotaryService.Load().(services.Notary); ntr != nil {
|
||||||
ntr.UpdateNotaryNodes(nodeKeys.Copy())
|
ntr.UpdateNotaryNodes(nodeKeys.Copy())
|
||||||
}
|
}
|
||||||
|
case RoleStateValidator:
|
||||||
|
if s.StateRootService != nil {
|
||||||
|
s.StateRootService.UpdateStateValidators(height, nodeKeys.Copy())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
package stateroot
|
package stateroot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"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/mpt"
|
"github.com/nspcc-dev/neo-go/pkg/core/mpt"
|
||||||
"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/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
@ -20,9 +24,19 @@ type (
|
||||||
bc blockchainer.Blockchainer
|
bc blockchainer.Blockchainer
|
||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
|
|
||||||
|
currentLocal atomic.Value
|
||||||
localHeight atomic.Uint32
|
localHeight atomic.Uint32
|
||||||
validatedHeight atomic.Uint32
|
validatedHeight atomic.Uint32
|
||||||
currentLocal atomic.Value
|
|
||||||
|
mtx sync.RWMutex
|
||||||
|
keys []keyCache
|
||||||
|
}
|
||||||
|
|
||||||
|
keyCache struct {
|
||||||
|
height uint32
|
||||||
|
validatorsKeys keys.PublicKeys
|
||||||
|
validatorsHash util.Uint160
|
||||||
|
validatorsScript []byte
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -51,8 +65,18 @@ func (s *Module) CurrentLocalStateRoot() util.Uint256 {
|
||||||
return s.currentLocal.Load().(util.Uint256)
|
return s.currentLocal.Load().(util.Uint256)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CurrentValidatedHeight returns current state root validated height.
|
||||||
|
func (s *Module) CurrentValidatedHeight() uint32 {
|
||||||
|
return s.validatedHeight.Load()
|
||||||
|
}
|
||||||
|
|
||||||
// Init initializes state root module at the given height.
|
// Init initializes state root module at the given height.
|
||||||
func (s *Module) Init(height uint32, enableRefCount bool) error {
|
func (s *Module) Init(height uint32, enableRefCount bool) error {
|
||||||
|
data, err := s.Store.Get([]byte{byte(storage.DataMPT), prefixValidated})
|
||||||
|
if err == nil {
|
||||||
|
s.validatedHeight.Store(binary.LittleEndian.Uint32(data))
|
||||||
|
}
|
||||||
|
|
||||||
var gcKey = []byte{byte(storage.DataMPT), prefixGC}
|
var gcKey = []byte{byte(storage.DataMPT), prefixGC}
|
||||||
if height == 0 {
|
if height == 0 {
|
||||||
s.mpt = mpt.NewTrie(nil, enableRefCount, s.Store)
|
s.mpt = mpt.NewTrie(nil, enableRefCount, s.Store)
|
||||||
|
@ -96,3 +120,22 @@ func (s *Module) AddMPTBatch(index uint32, b mpt.Batch) error {
|
||||||
_, err = s.Store.Persist()
|
_, err = s.Store.Persist()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VerifyStateRoot checks if state root is valid.
|
||||||
|
func (s *Module) VerifyStateRoot(r *state.MPTRoot) error {
|
||||||
|
_, err := s.getStateRoot(makeStateRootKey(r.Index - 1))
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("can't get previous state root")
|
||||||
|
}
|
||||||
|
return s.verifyWitness(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxVerificationGAS = 1_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()
|
||||||
|
return s.bc.VerifyWitness(h, r, r.Witness, maxVerificationGAS)
|
||||||
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
const (
|
const (
|
||||||
prefixGC = 0x01
|
prefixGC = 0x01
|
||||||
prefixLocal = 0x02
|
prefixLocal = 0x02
|
||||||
|
prefixValidated = 0x03
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Module) addLocalStateRoot(sr *state.MPTRoot) error {
|
func (s *Module) addLocalStateRoot(sr *state.MPTRoot) error {
|
||||||
|
@ -26,7 +27,10 @@ func (s *Module) addLocalStateRoot(sr *state.MPTRoot) error {
|
||||||
}
|
}
|
||||||
s.currentLocal.Store(sr.Root)
|
s.currentLocal.Store(sr.Root)
|
||||||
s.localHeight.Store(sr.Index)
|
s.localHeight.Store(sr.Index)
|
||||||
|
if s.bc.GetConfig().StateRootInHeader {
|
||||||
|
s.validatedHeight.Store(sr.Index)
|
||||||
updateStateHeightMetric(sr.Index)
|
updateStateHeightMetric(sr.Index)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,3 +58,32 @@ func makeStateRootKey(index uint32) []byte {
|
||||||
binary.BigEndian.PutUint32(key, index)
|
binary.BigEndian.PutUint32(key, index)
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddStateRoot adds validated state root provided by network.
|
||||||
|
func (s *Module) AddStateRoot(sr *state.MPTRoot) error {
|
||||||
|
if err := s.VerifyStateRoot(sr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
key := makeStateRootKey(sr.Index)
|
||||||
|
local, err := s.getStateRoot(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if local.Witness != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := s.putStateRoot(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), prefixValidated}, data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.validatedHeight.Store(sr.Index)
|
||||||
|
if !s.bc.GetConfig().StateRootInHeader {
|
||||||
|
updateStateHeightMetric(sr.Index)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
37
pkg/core/stateroot/validators.go
Normal file
37
pkg/core/stateroot/validators.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package stateroot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UpdateStateValidators updates list of state validator keys.
|
||||||
|
func (s *Module) UpdateStateValidators(height uint32, pubs keys.PublicKeys) {
|
||||||
|
script, _ := smartcontract.CreateDefaultMultiSigRedeemScript(pubs)
|
||||||
|
h := hash.Hash160(script)
|
||||||
|
|
||||||
|
s.mtx.Lock()
|
||||||
|
kc := s.getKeyCacheForHeight(height)
|
||||||
|
if kc.validatorsHash != h {
|
||||||
|
s.keys = append(s.keys, keyCache{
|
||||||
|
height: height,
|
||||||
|
validatorsKeys: pubs,
|
||||||
|
validatorsHash: h,
|
||||||
|
validatorsScript: script,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
s.mtx.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Module) getKeyCacheForHeight(h uint32) keyCache {
|
||||||
|
index := sort.Search(len(s.keys), func(i int) bool {
|
||||||
|
return s.keys[i].height >= h
|
||||||
|
})
|
||||||
|
if index == len(s.keys) {
|
||||||
|
return keyCache{}
|
||||||
|
}
|
||||||
|
return s.keys[index]
|
||||||
|
}
|
153
pkg/core/stateroot_test.go
Normal file
153
pkg/core/stateroot_test.go
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/internal/testserdes"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||||
|
"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/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||||
|
"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"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/services/stateroot"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testSignStateRoot(t *testing.T, r *state.MPTRoot, pubs keys.PublicKeys, accs ...*wallet.Account) []byte {
|
||||||
|
n := smartcontract.GetMajorityHonestNodeCount(len(accs))
|
||||||
|
w := io.NewBufBinWriter()
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
sig := accs[i].PrivateKey().SignHash(r.GetSignedHash())
|
||||||
|
emit.Bytes(w.BinWriter, sig)
|
||||||
|
}
|
||||||
|
require.NoError(t, w.Err)
|
||||||
|
|
||||||
|
script, err := smartcontract.CreateMajorityMultiSigRedeemScript(pubs.Copy())
|
||||||
|
require.NoError(t, err)
|
||||||
|
r.Witness = &transaction.Witness{
|
||||||
|
VerificationScript: script,
|
||||||
|
InvocationScript: w.Bytes(),
|
||||||
|
}
|
||||||
|
data, err := testserdes.EncodeBinary(stateroot.NewMessage(stateroot.RootT, r))
|
||||||
|
require.NoError(t, err)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMajorityMultisigWithGAS(t *testing.T, n int) (util.Uint160, keys.PublicKeys, []*wallet.Account) {
|
||||||
|
accs := make([]*wallet.Account, n)
|
||||||
|
for i := range accs {
|
||||||
|
acc, err := wallet.NewAccount()
|
||||||
|
require.NoError(t, err)
|
||||||
|
accs[i] = acc
|
||||||
|
}
|
||||||
|
sort.Slice(accs, func(i, j int) bool {
|
||||||
|
pi := accs[i].PrivateKey().PublicKey()
|
||||||
|
pj := accs[j].PrivateKey().PublicKey()
|
||||||
|
return pi.Cmp(pj) == -1
|
||||||
|
})
|
||||||
|
pubs := make(keys.PublicKeys, n)
|
||||||
|
for i := range pubs {
|
||||||
|
pubs[i] = accs[i].PrivateKey().PublicKey()
|
||||||
|
}
|
||||||
|
script, err := smartcontract.CreateMajorityMultiSigRedeemScript(pubs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return hash.Hash160(script), pubs, accs
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateRoot(t *testing.T) {
|
||||||
|
bc := newTestChain(t)
|
||||||
|
|
||||||
|
h, pubs, accs := newMajorityMultisigWithGAS(t, 2)
|
||||||
|
bc.setNodesByRole(t, true, native.RoleStateValidator, pubs)
|
||||||
|
updateIndex := bc.BlockHeight()
|
||||||
|
transferTokenFromMultisigAccount(t, bc, h, bc.contracts.GAS.Hash, 1_0000_0000)
|
||||||
|
|
||||||
|
srv, err := stateroot.New(bc.GetStateModule())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, 0, srv.CurrentValidatedHeight())
|
||||||
|
r, err := srv.GetStateRoot(bc.BlockHeight())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, r.Root, srv.CurrentLocalStateRoot())
|
||||||
|
|
||||||
|
t.Run("invalid message", func(t *testing.T) {
|
||||||
|
require.Error(t, srv.OnPayload(&payload.Extensible{Data: []byte{42}}))
|
||||||
|
require.EqualValues(t, 0, srv.CurrentValidatedHeight())
|
||||||
|
})
|
||||||
|
t.Run("drop zero index", func(t *testing.T) {
|
||||||
|
r, err := srv.GetStateRoot(0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
data, err := testserdes.EncodeBinary(stateroot.NewMessage(stateroot.RootT, r))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, srv.OnPayload(&payload.Extensible{Data: data}))
|
||||||
|
require.EqualValues(t, 0, srv.CurrentValidatedHeight())
|
||||||
|
})
|
||||||
|
t.Run("invalid height", func(t *testing.T) {
|
||||||
|
r, err := srv.GetStateRoot(1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
r.Index = 10
|
||||||
|
data := testSignStateRoot(t, r, pubs, accs...)
|
||||||
|
require.Error(t, srv.OnPayload(&payload.Extensible{Data: data}))
|
||||||
|
require.EqualValues(t, 0, srv.CurrentValidatedHeight())
|
||||||
|
})
|
||||||
|
t.Run("invalid signer", func(t *testing.T) {
|
||||||
|
accInv, err := wallet.NewAccount()
|
||||||
|
require.NoError(t, err)
|
||||||
|
pubs := keys.PublicKeys{accInv.PrivateKey().PublicKey()}
|
||||||
|
require.NoError(t, accInv.ConvertMultisig(1, pubs))
|
||||||
|
transferTokenFromMultisigAccount(t, bc, accInv.Contract.ScriptHash(), bc.contracts.GAS.Hash, 1_0000_0000)
|
||||||
|
r, err := srv.GetStateRoot(1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
data := testSignStateRoot(t, r, pubs, accInv)
|
||||||
|
err = srv.OnPayload(&payload.Extensible{Data: data})
|
||||||
|
require.True(t, errors.Is(err, ErrWitnessHashMismatch), "got: %v", err)
|
||||||
|
require.EqualValues(t, 0, srv.CurrentValidatedHeight())
|
||||||
|
})
|
||||||
|
|
||||||
|
r, err = srv.GetStateRoot(updateIndex + 1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
data := testSignStateRoot(t, r, pubs, accs...)
|
||||||
|
require.NoError(t, srv.OnPayload(&payload.Extensible{Data: data}))
|
||||||
|
require.EqualValues(t, 2, srv.CurrentValidatedHeight())
|
||||||
|
|
||||||
|
r, err = srv.GetStateRoot(updateIndex + 1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, r.Witness)
|
||||||
|
require.Equal(t, h, r.Witness.ScriptHash())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateRootInitNonZeroHeight(t *testing.T) {
|
||||||
|
st := memoryStore{storage.NewMemoryStore()}
|
||||||
|
h, pubs, accs := newMajorityMultisigWithGAS(t, 2)
|
||||||
|
|
||||||
|
var root util.Uint256
|
||||||
|
t.Run("init", func(t *testing.T) { // this is in a separate test to do proper cleanup
|
||||||
|
bc := newTestChainWithCustomCfgAndStore(t, st, nil)
|
||||||
|
bc.setNodesByRole(t, true, native.RoleStateValidator, pubs)
|
||||||
|
transferTokenFromMultisigAccount(t, bc, h, bc.contracts.GAS.Hash, 1_0000_0000)
|
||||||
|
|
||||||
|
_, err := persistBlock(bc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
srv, err := stateroot.New(bc.GetStateModule())
|
||||||
|
require.NoError(t, err)
|
||||||
|
r, err := srv.GetStateRoot(2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
data := testSignStateRoot(t, r, pubs, accs...)
|
||||||
|
require.NoError(t, srv.OnPayload(&payload.Extensible{Data: data}))
|
||||||
|
require.EqualValues(t, 2, srv.CurrentValidatedHeight())
|
||||||
|
root = srv.CurrentLocalStateRoot()
|
||||||
|
})
|
||||||
|
|
||||||
|
bc2 := newTestChainWithCustomCfgAndStore(t, st, nil)
|
||||||
|
srv := bc2.GetStateModule()
|
||||||
|
require.EqualValues(t, 2, srv.CurrentValidatedHeight())
|
||||||
|
require.Equal(t, root, srv.CurrentLocalStateRoot())
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/services/notary"
|
"github.com/nspcc-dev/neo-go/pkg/services/notary"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/services/oracle"
|
"github.com/nspcc-dev/neo-go/pkg/services/oracle"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/services/stateroot"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
@ -88,6 +89,7 @@ type (
|
||||||
canHandleExtens *atomic.Bool
|
canHandleExtens *atomic.Bool
|
||||||
|
|
||||||
oracle *oracle.Oracle
|
oracle *oracle.Oracle
|
||||||
|
stateRoot stateroot.Service
|
||||||
|
|
||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
}
|
}
|
||||||
|
@ -171,6 +173,12 @@ func newServerFromConstructors(config ServerConfig, chain blockchainer.Blockchai
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
sr, err := stateroot.New(chain.GetStateModule())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't initialize StateRoot service: %w", err)
|
||||||
|
}
|
||||||
|
s.stateRoot = sr
|
||||||
|
|
||||||
if config.OracleCfg.Enabled {
|
if config.OracleCfg.Enabled {
|
||||||
orcCfg := oracle.Config{
|
orcCfg := oracle.Config{
|
||||||
Log: log,
|
Log: log,
|
||||||
|
@ -295,6 +303,11 @@ func (s *Server) GetOracle() *oracle.Oracle {
|
||||||
return s.oracle
|
return s.oracle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetStateRoot returns state root service instance.
|
||||||
|
func (s *Server) GetStateRoot() stateroot.Service {
|
||||||
|
return s.stateRoot
|
||||||
|
}
|
||||||
|
|
||||||
// UnconnectedPeers returns a list of peers that are in the discovery peer list
|
// UnconnectedPeers returns a list of peers that are in the discovery peer list
|
||||||
// but are not connected to the server.
|
// but are not connected to the server.
|
||||||
func (s *Server) UnconnectedPeers() []string {
|
func (s *Server) UnconnectedPeers() []string {
|
||||||
|
@ -803,7 +816,11 @@ func (s *Server) handleExtensibleCmd(e *payload.Extensible) error {
|
||||||
switch e.Category {
|
switch e.Category {
|
||||||
case consensus.Category:
|
case consensus.Category:
|
||||||
s.consensus.OnPayload(e)
|
s.consensus.OnPayload(e)
|
||||||
case "StateService": // no-op for now
|
case stateroot.Category:
|
||||||
|
err := s.stateRoot.OnPayload(e)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return errors.New("invalid category")
|
return errors.New("invalid category")
|
||||||
}
|
}
|
||||||
|
|
|
@ -869,7 +869,7 @@ func (s *Server) verifyProof(ps request.Params) (interface{}, *response.Error) {
|
||||||
|
|
||||||
func (s *Server) getStateHeight(_ request.Params) (interface{}, *response.Error) {
|
func (s *Server) getStateHeight(_ request.Params) (interface{}, *response.Error) {
|
||||||
var height = s.chain.BlockHeight()
|
var height = s.chain.BlockHeight()
|
||||||
var stateHeight uint32
|
var stateHeight = s.chain.GetStateModule().CurrentValidatedHeight()
|
||||||
if s.chain.GetConfig().StateRootInHeader {
|
if s.chain.GetConfig().StateRootInHeader {
|
||||||
stateHeight = height - 1
|
stateHeight = height - 1
|
||||||
}
|
}
|
||||||
|
|
50
pkg/services/stateroot/message.go
Normal file
50
pkg/services/stateroot/message.go
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package stateroot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// MessageType represents message type.
|
||||||
|
MessageType byte
|
||||||
|
|
||||||
|
// Message represents state-root related message.
|
||||||
|
Message struct {
|
||||||
|
Type MessageType
|
||||||
|
Payload io.Serializable
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Various message types.
|
||||||
|
const (
|
||||||
|
RootT MessageType = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewMessage creates new message of specified type.
|
||||||
|
func NewMessage(typ MessageType, p io.Serializable) *Message {
|
||||||
|
return &Message{
|
||||||
|
Type: typ,
|
||||||
|
Payload: p,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeBinary implements io.Serializable interface.
|
||||||
|
func (m *Message) EncodeBinary(w *io.BinWriter) {
|
||||||
|
w.WriteB(byte(m.Type))
|
||||||
|
m.Payload.EncodeBinary(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeBinary implements io.Serializable interface.
|
||||||
|
func (m *Message) DecodeBinary(r *io.BinReader) {
|
||||||
|
switch m.Type = MessageType(r.ReadB()); m.Type {
|
||||||
|
case RootT:
|
||||||
|
m.Payload = new(state.MPTRoot)
|
||||||
|
default:
|
||||||
|
r.Err = fmt.Errorf("invalid type: %x", m.Type)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.Payload.DecodeBinary(r)
|
||||||
|
}
|
51
pkg/services/stateroot/service.go
Normal file
51
pkg/services/stateroot/service.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package stateroot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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/io"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Service represents state root service.
|
||||||
|
Service interface {
|
||||||
|
blockchainer.StateRoot
|
||||||
|
OnPayload(p *payload.Extensible) error
|
||||||
|
}
|
||||||
|
|
||||||
|
service struct {
|
||||||
|
blockchainer.StateRoot
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Category is message category for extensible payloads.
|
||||||
|
Category = "StateService"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New returns new state root service instance using underlying module.
|
||||||
|
func New(mod blockchainer.StateRoot) (Service, error) {
|
||||||
|
return &service{
|
||||||
|
StateRoot: mod,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnPayload implements Service interface.
|
||||||
|
func (s *service) OnPayload(ep *payload.Extensible) error {
|
||||||
|
m := new(Message)
|
||||||
|
r := io.NewBinReaderFromBuf(ep.Data)
|
||||||
|
m.DecodeBinary(r)
|
||||||
|
if r.Err != nil {
|
||||||
|
return r.Err
|
||||||
|
}
|
||||||
|
switch m.Type {
|
||||||
|
case RootT:
|
||||||
|
sr := m.Payload.(*state.MPTRoot)
|
||||||
|
if sr.Index == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return s.AddStateRoot(sr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in a new issue