forked from TrueCloudLab/neoneo-go
core: keep Designation cache always valid and up-to-date
Always use cache instead of DAO where possible. Update cache in-place each time new designated node is chosen.
This commit is contained in:
parent
c8bdd2ad1a
commit
11ab42d91c
4 changed files with 201 additions and 47 deletions
|
@ -594,7 +594,10 @@ func (bc *Blockchain) initializeNativeCache(d *dao.Simple) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't init cache for Management native contract: %w", err)
|
return fmt.Errorf("can't init cache for Management native contract: %w", err)
|
||||||
}
|
}
|
||||||
bc.contracts.Designate.InitializeCache(d)
|
err = bc.contracts.Designate.InitializeCache(d)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't init cache for Designation native contract: %w", err)
|
||||||
|
}
|
||||||
bc.contracts.Oracle.InitializeCache(d)
|
bc.contracts.Oracle.InitializeCache(d)
|
||||||
if bc.P2PSigExtensionsEnabled() {
|
if bc.P2PSigExtensionsEnabled() {
|
||||||
err = bc.contracts.Notary.InitializeCache(d)
|
err = bc.contracts.Notary.InitializeCache(d)
|
||||||
|
|
|
@ -3,6 +3,7 @@ package native
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -48,6 +49,8 @@ type roleData struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type DesignationCache struct {
|
type DesignationCache struct {
|
||||||
|
// rolesChangedFlag shows whether any of designated nodes were changed within the current block.
|
||||||
|
// It is used to notify dependant services about updated node roles during PostPersist.
|
||||||
rolesChangedFlag bool
|
rolesChangedFlag bool
|
||||||
oracles roleData
|
oracles roleData
|
||||||
stateVals roleData
|
stateVals roleData
|
||||||
|
@ -120,14 +123,33 @@ func newDesignate(p2pSigExtensionsEnabled bool) *Designate {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize initializes Oracle contract.
|
// Initialize initializes Designation contract. It is called once at native Management's OnPersist
|
||||||
|
// at the genesis block, and we can't properly fill the cache at this point, as there are no roles
|
||||||
|
// data in the storage.
|
||||||
func (s *Designate) Initialize(ic *interop.Context) error {
|
func (s *Designate) Initialize(ic *interop.Context) error {
|
||||||
cache := &DesignationCache{}
|
cache := &DesignationCache{}
|
||||||
cache.rolesChangedFlag = true
|
|
||||||
ic.DAO.Store.SetCache(s.ID, cache)
|
ic.DAO.Store.SetCache(s.ID, cache)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InitializeCache fills native Designate cache from DAO. It is called at non-zero height, thus
|
||||||
|
// we can fetch the roles data right from the storage.
|
||||||
|
func (s *Designate) InitializeCache(d *dao.Simple) error {
|
||||||
|
cache := &DesignationCache{}
|
||||||
|
roles := []noderoles.Role{noderoles.Oracle, noderoles.NeoFSAlphabet, noderoles.StateValidator}
|
||||||
|
if s.p2pSigExtensionsEnabled {
|
||||||
|
roles = append(roles, noderoles.P2PNotary)
|
||||||
|
}
|
||||||
|
for _, r := range roles {
|
||||||
|
err := s.updateCachedRoleData(cache, d, r)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get nodes from storage for %d role: %w", r, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.Store.SetCache(s.ID, cache)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// OnPersist implements Contract interface.
|
// OnPersist implements Contract interface.
|
||||||
func (s *Designate) OnPersist(ic *interop.Context) error {
|
func (s *Designate) OnPersist(ic *interop.Context) error {
|
||||||
return nil
|
return nil
|
||||||
|
@ -136,23 +158,15 @@ func (s *Designate) OnPersist(ic *interop.Context) error {
|
||||||
// PostPersist implements Contract interface.
|
// PostPersist implements Contract interface.
|
||||||
func (s *Designate) PostPersist(ic *interop.Context) error {
|
func (s *Designate) PostPersist(ic *interop.Context) error {
|
||||||
cache := ic.DAO.Store.GetRWCache(s.ID).(*DesignationCache)
|
cache := ic.DAO.Store.GetRWCache(s.ID).(*DesignationCache)
|
||||||
if !rolesChanged(cache) {
|
if !cache.rolesChangedFlag {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.updateCachedRoleData(&cache.oracles, ic.DAO, noderoles.Oracle); err != nil {
|
s.notifyRoleChanged(&cache.oracles, noderoles.Oracle)
|
||||||
return err
|
s.notifyRoleChanged(&cache.stateVals, noderoles.StateValidator)
|
||||||
}
|
s.notifyRoleChanged(&cache.neofsAlphabet, noderoles.NeoFSAlphabet)
|
||||||
if err := s.updateCachedRoleData(&cache.stateVals, ic.DAO, noderoles.StateValidator); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := s.updateCachedRoleData(&cache.neofsAlphabet, ic.DAO, noderoles.NeoFSAlphabet); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if s.p2pSigExtensionsEnabled {
|
if s.p2pSigExtensionsEnabled {
|
||||||
if err := s.updateCachedRoleData(&cache.notaries, ic.DAO, noderoles.P2PNotary); err != nil {
|
s.notifyRoleChanged(&cache.notaries, noderoles.P2PNotary)
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.rolesChangedFlag = false
|
cache.rolesChangedFlag = false
|
||||||
|
@ -184,10 +198,6 @@ func (s *Designate) getDesignatedByRole(ic *interop.Context, args []stackitem.It
|
||||||
return pubsToArray(pubs)
|
return pubsToArray(pubs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func rolesChanged(cache *DesignationCache) bool {
|
|
||||||
return cache.rolesChangedFlag
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Designate) hashFromNodes(r noderoles.Role, nodes keys.PublicKeys) util.Uint160 {
|
func (s *Designate) hashFromNodes(r noderoles.Role, nodes keys.PublicKeys) util.Uint160 {
|
||||||
if len(nodes) == 0 {
|
if len(nodes) == 0 {
|
||||||
return util.Uint160{}
|
return util.Uint160{}
|
||||||
|
@ -202,29 +212,46 @@ func (s *Designate) hashFromNodes(r noderoles.Role, nodes keys.PublicKeys) util.
|
||||||
return hash.Hash160(script)
|
return hash.Hash160(script)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Designate) updateCachedRoleData(v *roleData, d *dao.Simple, r noderoles.Role) error {
|
// updateCachedRoleData fetches the most recent role data from the storage and
|
||||||
nodeKeys, height, err := s.GetDesignatedByRole(d, r, math.MaxUint32)
|
// updates the given cache.
|
||||||
|
func (s *Designate) updateCachedRoleData(cache *DesignationCache, d *dao.Simple, r noderoles.Role) error {
|
||||||
|
var v *roleData
|
||||||
|
switch r {
|
||||||
|
case noderoles.Oracle:
|
||||||
|
v = &cache.oracles
|
||||||
|
case noderoles.StateValidator:
|
||||||
|
v = &cache.stateVals
|
||||||
|
case noderoles.NeoFSAlphabet:
|
||||||
|
v = &cache.neofsAlphabet
|
||||||
|
case noderoles.P2PNotary:
|
||||||
|
v = &cache.notaries
|
||||||
|
}
|
||||||
|
nodeKeys, height, err := s.getDesignatedByRoleFromStorage(d, r, math.MaxUint32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
v.nodes = nodeKeys
|
v.nodes = nodeKeys
|
||||||
v.addr = s.hashFromNodes(r, nodeKeys)
|
v.addr = s.hashFromNodes(r, nodeKeys)
|
||||||
v.height = height
|
v.height = height
|
||||||
|
cache.rolesChangedFlag = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Designate) notifyRoleChanged(v *roleData, r noderoles.Role) {
|
||||||
switch r {
|
switch r {
|
||||||
case noderoles.Oracle:
|
case noderoles.Oracle:
|
||||||
if orc, _ := s.OracleService.Load().(services.Oracle); orc != nil {
|
if orc, _ := s.OracleService.Load().(services.Oracle); orc != nil {
|
||||||
orc.UpdateOracleNodes(nodeKeys.Copy())
|
orc.UpdateOracleNodes(v.nodes.Copy())
|
||||||
}
|
}
|
||||||
case noderoles.P2PNotary:
|
case noderoles.P2PNotary:
|
||||||
if ntr, _ := s.NotaryService.Load().(services.Notary); ntr != nil {
|
if ntr, _ := s.NotaryService.Load().(services.Notary); ntr != nil {
|
||||||
ntr.UpdateNotaryNodes(nodeKeys.Copy())
|
ntr.UpdateNotaryNodes(v.nodes.Copy())
|
||||||
}
|
}
|
||||||
case noderoles.StateValidator:
|
case noderoles.StateValidator:
|
||||||
if s.StateRootService != nil {
|
if s.StateRootService != nil {
|
||||||
s.StateRootService.UpdateStateValidators(height, nodeKeys.Copy())
|
s.StateRootService.UpdateStateValidators(v.height, v.nodes.Copy())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCachedRoleData(cache *DesignationCache, r noderoles.Role) *roleData {
|
func getCachedRoleData(cache *DesignationCache, r noderoles.Role) *roleData {
|
||||||
|
@ -247,17 +274,10 @@ func (s *Designate) GetLastDesignatedHash(d *dao.Simple, r noderoles.Role) (util
|
||||||
return util.Uint160{}, ErrInvalidRole
|
return util.Uint160{}, ErrInvalidRole
|
||||||
}
|
}
|
||||||
cache := d.Store.GetROCache(s.ID).(*DesignationCache)
|
cache := d.Store.GetROCache(s.ID).(*DesignationCache)
|
||||||
if !rolesChanged(cache) {
|
if val := getCachedRoleData(cache, r); val != nil {
|
||||||
if val := getCachedRoleData(cache, r); val != nil {
|
return val.addr, nil
|
||||||
return val.addr, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
nodes, _, err := s.GetDesignatedByRole(d, r, math.MaxUint32)
|
return util.Uint160{}, nil
|
||||||
if err != nil {
|
|
||||||
return util.Uint160{}, err
|
|
||||||
}
|
|
||||||
// We only have hashing defined for oracles now.
|
|
||||||
return s.hashFromNodes(r, nodes), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDesignatedByRole returns nodes for role r.
|
// GetDesignatedByRole returns nodes for role r.
|
||||||
|
@ -266,11 +286,21 @@ func (s *Designate) GetDesignatedByRole(d *dao.Simple, r noderoles.Role, index u
|
||||||
return nil, 0, ErrInvalidRole
|
return nil, 0, ErrInvalidRole
|
||||||
}
|
}
|
||||||
cache := d.Store.GetROCache(s.ID).(*DesignationCache)
|
cache := d.Store.GetROCache(s.ID).(*DesignationCache)
|
||||||
if !rolesChanged(cache) {
|
if val := getCachedRoleData(cache, r); val != nil {
|
||||||
if val := getCachedRoleData(cache, r); val != nil && val.height <= index {
|
if val.height <= index {
|
||||||
return val.nodes.Copy(), val.height, nil
|
return val.nodes.Copy(), val.height, nil
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Cache is always valid, thus if there's no cache then there's no designated nodes for this role.
|
||||||
|
return nil, 0, nil
|
||||||
}
|
}
|
||||||
|
// Cache stores only latest designated nodes, so if the old info is requested, then we still need
|
||||||
|
// to search in the storage.
|
||||||
|
return s.getDesignatedByRoleFromStorage(d, r, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDesignatedByRoleFromStorage returns nodes for role r from the storage.
|
||||||
|
func (s *Designate) getDesignatedByRoleFromStorage(d *dao.Simple, r noderoles.Role, index uint32) (keys.PublicKeys, uint32, error) {
|
||||||
var (
|
var (
|
||||||
ns NodeList
|
ns NodeList
|
||||||
bestIndex uint32
|
bestIndex uint32
|
||||||
|
@ -344,12 +374,18 @@ func (s *Designate) DesignateAsRole(ic *interop.Context, r noderoles.Role, pubs
|
||||||
}
|
}
|
||||||
sort.Sort(pubs)
|
sort.Sort(pubs)
|
||||||
nl := NodeList(pubs)
|
nl := NodeList(pubs)
|
||||||
ic.DAO.Store.GetRWCache(s.ID).(*DesignationCache).rolesChangedFlag = true
|
|
||||||
err := putConvertibleToDAO(s.ID, ic.DAO, key, &nl)
|
err := putConvertibleToDAO(s.ID, ic.DAO, key, &nl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cache := ic.DAO.Store.GetRWCache(s.ID).(*DesignationCache)
|
||||||
|
err = s.updateCachedRoleData(cache, ic.DAO, r)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update Designation role data cache: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
ic.Notifications = append(ic.Notifications, state.NotificationEvent{
|
ic.Notifications = append(ic.Notifications, state.NotificationEvent{
|
||||||
ScriptHash: s.Hash,
|
ScriptHash: s.Hash,
|
||||||
Name: DesignationEventName,
|
Name: DesignationEventName,
|
||||||
|
@ -372,10 +408,3 @@ func (s *Designate) getRole(item stackitem.Item) (noderoles.Role, bool) {
|
||||||
u := bi.Uint64()
|
u := bi.Uint64()
|
||||||
return noderoles.Role(u), u <= math.MaxUint8 && s.isValidRole(noderoles.Role(u))
|
return noderoles.Role(u), u <= math.MaxUint8 && s.isValidRole(noderoles.Role(u))
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitializeCache invalidates native Designate cache.
|
|
||||||
func (s *Designate) InitializeCache(d *dao.Simple) {
|
|
||||||
cache := &DesignationCache{}
|
|
||||||
cache.rolesChangedFlag = true
|
|
||||||
d.Store.SetCache(s.ID, cache)
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,9 +8,12 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||||
"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/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -77,6 +80,33 @@ func testGetSet(t *testing.T, c *neotest.ContractInvoker, name string, defaultVa
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testGetSetCache(t *testing.T, c *neotest.ContractInvoker, name string, defaultValue int64) {
|
||||||
|
getName := "get" + name
|
||||||
|
setName := "set" + name
|
||||||
|
|
||||||
|
committeeInvoker := c.WithSigners(c.Committee)
|
||||||
|
|
||||||
|
newVal := defaultValue - 1
|
||||||
|
|
||||||
|
// Change fee, abort the transaction and check that contract cache wasn't persisted
|
||||||
|
// for FAULTed tx at the same block.
|
||||||
|
w := io.NewBufBinWriter()
|
||||||
|
emit.AppCall(w.BinWriter, committeeInvoker.Hash, setName, callflag.All, newVal)
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.ABORT)
|
||||||
|
tx1 := committeeInvoker.PrepareInvocation(t, w.Bytes(), committeeInvoker.Signers)
|
||||||
|
tx2 := committeeInvoker.PrepareInvoke(t, getName)
|
||||||
|
committeeInvoker.AddNewBlock(t, tx1, tx2)
|
||||||
|
committeeInvoker.CheckFault(t, tx1.Hash(), "ABORT")
|
||||||
|
committeeInvoker.CheckHalt(t, tx2.Hash(), stackitem.Make(defaultValue))
|
||||||
|
|
||||||
|
// Change fee and check that change is available for the next tx.
|
||||||
|
tx1 = committeeInvoker.PrepareInvoke(t, setName, newVal)
|
||||||
|
tx2 = committeeInvoker.PrepareInvoke(t, getName)
|
||||||
|
committeeInvoker.AddNewBlock(t, tx1, tx2)
|
||||||
|
committeeInvoker.CheckHalt(t, tx1.Hash())
|
||||||
|
committeeInvoker.CheckHalt(t, tx2.Hash(), stackitem.Make(newVal))
|
||||||
|
}
|
||||||
|
|
||||||
func setNodesByRole(t *testing.T, designateInvoker *neotest.ContractInvoker, ok bool, r noderoles.Role, nodes keys.PublicKeys) {
|
func setNodesByRole(t *testing.T, designateInvoker *neotest.ContractInvoker, ok bool, r noderoles.Role, nodes keys.PublicKeys) {
|
||||||
pubs := make([]interface{}, len(nodes))
|
pubs := make([]interface{}, len(nodes))
|
||||||
for i := range nodes {
|
for i := range nodes {
|
||||||
|
|
|
@ -5,8 +5,15 @@ import (
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||||
|
"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/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -45,3 +52,88 @@ func TestDesignate_DesignateAsRole(t *testing.T) {
|
||||||
checkNodeRoles(t, designateInvoker, true, noderoles.NeoFSAlphabet, e.Chain.BlockHeight()+1, pubs)
|
checkNodeRoles(t, designateInvoker, true, noderoles.NeoFSAlphabet, e.Chain.BlockHeight()+1, pubs)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dummyOracle struct {
|
||||||
|
updateNodes func(k keys.PublicKeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRequests processes new requests.
|
||||||
|
func (o *dummyOracle) AddRequests(map[uint64]*state.OracleRequest) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveRequests removes already processed requests.
|
||||||
|
func (o *dummyOracle) RemoveRequests([]uint64) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateOracleNodes updates oracle nodes.
|
||||||
|
func (o *dummyOracle) UpdateOracleNodes(k keys.PublicKeys) {
|
||||||
|
if o.updateNodes != nil {
|
||||||
|
o.updateNodes(k)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateNativeContract updates oracle contract native script and hash.
|
||||||
|
func (o *dummyOracle) UpdateNativeContract([]byte, []byte, util.Uint160, int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start runs oracle module.
|
||||||
|
func (o *dummyOracle) Start() {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown shutdowns oracle module.
|
||||||
|
func (o *dummyOracle) Shutdown() {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDesignate_Cache(t *testing.T) {
|
||||||
|
c := newDesignateClient(t)
|
||||||
|
e := c.Executor
|
||||||
|
designateInvoker := c.WithSigners(c.Committee)
|
||||||
|
r := int64(noderoles.Oracle)
|
||||||
|
var (
|
||||||
|
updatedNodes keys.PublicKeys
|
||||||
|
updateCalled bool
|
||||||
|
)
|
||||||
|
oracleServ := &dummyOracle{
|
||||||
|
updateNodes: func(k keys.PublicKeys) {
|
||||||
|
updatedNodes = k
|
||||||
|
updateCalled = true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
privGood, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
pubsGood := []interface{}{privGood.PublicKey().Bytes()}
|
||||||
|
|
||||||
|
privBad, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
pubsBad := []interface{}{privBad.PublicKey().Bytes()}
|
||||||
|
|
||||||
|
// Firstly, designate good Oracle node and check that OracleService callback was called during PostPersist.
|
||||||
|
e.Chain.SetOracle(oracleServ)
|
||||||
|
txDesignateGood := designateInvoker.PrepareInvoke(t, "designateAsRole", r, pubsGood)
|
||||||
|
e.AddNewBlock(t, txDesignateGood)
|
||||||
|
e.CheckHalt(t, txDesignateGood.Hash(), stackitem.Null{})
|
||||||
|
require.True(t, updateCalled)
|
||||||
|
require.Equal(t, keys.PublicKeys{privGood.PublicKey()}, updatedNodes)
|
||||||
|
updatedNodes = nil
|
||||||
|
updateCalled = false
|
||||||
|
|
||||||
|
// Check designated node in a separate block.
|
||||||
|
checkNodeRoles(t, designateInvoker, true, noderoles.Oracle, e.Chain.BlockHeight()+1, keys.PublicKeys{privGood.PublicKey()})
|
||||||
|
|
||||||
|
// Designate privBad as oracle node and abort the transaction. Designation cache changes
|
||||||
|
// shouldn't be persisted to the contract and no notification should be sent.
|
||||||
|
w := io.NewBufBinWriter()
|
||||||
|
emit.AppCall(w.BinWriter, designateInvoker.Hash, "designateAsRole", callflag.All, int64(r), pubsBad)
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.ABORT)
|
||||||
|
require.NoError(t, w.Err)
|
||||||
|
script := w.Bytes()
|
||||||
|
|
||||||
|
designateInvoker.InvokeScriptCheckFAULT(t, script, designateInvoker.Signers, "ABORT")
|
||||||
|
require.Nil(t, updatedNodes)
|
||||||
|
require.False(t, updateCalled)
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue