[#269] netmap: Return same type from reading methods

There is a need to return similar structure of information about the
storage nodes from the contract storage readers. In previous
implementation some methods didn't return node state which can differ
with the one encoded in the node's BLOB.

Define `Node` structure of the information about the storage nodes
recorded in the contract storage. Return `[]Node` from all related
methods.

Also improve docs of touched contract methods.

Signed-off-by: Leonard Lyubich <ctulhurider@gmail.com>
This commit is contained in:
Leonard Lyubich 2022-09-28 11:41:51 +04:00 committed by LeL
parent 4d487a069e
commit d95bc53589
3 changed files with 215 additions and 140 deletions

View file

@ -3,12 +3,22 @@ Changelog for NeoFS Contract
## Unrelease ## Unrelease
### Added
- Support `MAINTENANCE` state of storage nodes (#269)
### Changed
- `netmap.Snapshot` and all similar methods return (#269)
### Updated ### Updated
- NNS contract now sets domain expiration based on `register` arguments (#262) - NNS contract now sets domain expiration based on `register` arguments (#262)
### Fixed ### Fixed
- NNS `renew` now can only be done by the domain owner - NNS `renew` now can only be done by the domain owner
### Updating from v0.15.x
Update deployed `Netmap` contract using `Update` method: storage of the contract
has been incompatibly changed.
## [0.15.5] - 2022-08-23 ## [0.15.5] - 2022-08-23
### Updated ### Updated

View file

@ -13,25 +13,36 @@ import (
"github.com/nspcc-dev/neofs-contract/common" "github.com/nspcc-dev/neofs-contract/common"
) )
type (
storageNode struct {
info []byte
}
netmapNode struct {
node storageNode
state NodeState
}
// NodeState is an enumeration for node states. // NodeState is an enumeration for node states.
NodeState int type NodeState int
record struct { // Various Node states
key []byte const (
val []byte _ NodeState = iota
}
// NodeStateOnline stands for nodes that are in full network and
// operational availability.
NodeStateOnline
// NodeStateOffline stands for nodes that are in network unavailability.
NodeStateOffline
// NodeStateMaintenance stands for nodes under maintenance with partial
// network availability.
NodeStateMaintenance
) )
// Node groups data related to NeoFS storage nodes registered in the NeoFS
// network. The information is stored in the current contract.
type Node struct {
// Information about the node encoded according to the NeoFS binary
// protocol.
BLOB []byte
// Current node state.
State NodeState
}
const ( const (
notaryDisabledKey = "notary" notaryDisabledKey = "notary"
innerRingKey = "innerring" innerRingKey = "innerring"
@ -51,14 +62,6 @@ const (
cleanupEpochMethod = "newEpoch" cleanupEpochMethod = "newEpoch"
) )
const (
// V2 format
_ NodeState = iota
OnlineState
OfflineState
MaintenanceState
)
var ( var (
configPrefix = []byte("config") configPrefix = []byte("config")
candidatePrefix = []byte("candidate") candidatePrefix = []byte("candidate")
@ -105,7 +108,7 @@ func _deploy(data interface{}, isUpdate bool) {
prefix := []byte(snapshotKeyPrefix) prefix := []byte(snapshotKeyPrefix)
for i := 0; i < DefaultSnapshotCount; i++ { for i := 0; i < DefaultSnapshotCount; i++ {
common.SetSerialized(ctx, append(prefix, byte(i)), []storageNode{}) common.SetSerialized(ctx, append(prefix, byte(i)), []Node{})
} }
storage.Put(ctx, snapshotCurrentIDKey, 0) storage.Put(ctx, snapshotCurrentIDKey, 0)
@ -191,8 +194,11 @@ func UpdateInnerRing(keys []interop.PublicKey) {
common.SetSerialized(ctx, innerRingKey, keys) common.SetSerialized(ctx, innerRingKey, keys)
} }
// AddPeerIR method tries to add a new candidate to the network map. // AddPeerIR accepts Alphabet calls in the notary-enabled contract setting and
// It should only be invoked in notary-enabled environment by the alphabet. // behaves similar to AddPeer in the notary-disabled one.
//
// AddPeerIR MUST NOT be called in notary-disabled contract setting.
// AddPeerIR MUST be called by the Alphabet member only.
func AddPeerIR(nodeInfo []byte) { func AddPeerIR(nodeInfo []byte) {
ctx := storage.GetContext() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool) notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
@ -202,19 +208,40 @@ func AddPeerIR(nodeInfo []byte) {
common.CheckAlphabetWitness(common.AlphabetAddress()) common.CheckAlphabetWitness(common.AlphabetAddress())
addToNetmap(ctx, storageNode{info: nodeInfo})
publicKey := nodeInfo[2:35] // V2 format: offset:2, len:33 publicKey := nodeInfo[2:35] // V2 format: offset:2, len:33
runtime.Notify("AddPeerSuccess", interop.PublicKey(publicKey))
addToNetmap(ctx, publicKey, Node{
BLOB: nodeInfo,
State: NodeStateOnline,
})
} }
// AddPeer method adds a new candidate to the next network map if it was invoked // AddPeer accepts information about the network map candidate in the NeoFS
// by Alphabet node. If it was invoked by a node candidate, it produces AddPeer // binary protocol format, identifies the caller and behaves depending on different
// notification. Otherwise, the method throws panic. // conditions listed below.
// //
// If the candidate already exists, its info is updated. // Contract settings:
// NodeInfo argument contains a stable marshaled version of netmap.NodeInfo //
// structure. // (1) notary-enabled
// (2) notary-disabled
//
// Callers:
//
// (a) candidate himself, if node's public key corresponds to the signer
// (b) Alphabet member
// (c) others
//
// AddPeer case-by-case behavior:
//
// (1a) does nothing
// (1b) panics. Notice that AddPeerIR MUST be used for this purpose.
// (2a) throws AddPeer notification with the provided BLOB
// (2b) accepts Alphabet vote. If the threshold of votes is reached, adds
// new element to the candidate set, and throws AddPeerSuccess notification.
// (c) panics
//
// Candidate MUST call AddPeer with "online" state in its descriptor. Alphabet
// members MUST NOT call AddPeer with any other states.
func AddPeer(nodeInfo []byte) { func AddPeer(nodeInfo []byte) {
ctx := storage.GetContext() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool) notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
@ -242,8 +269,9 @@ func AddPeer(nodeInfo []byte) {
return return
} }
candidate := storageNode{ candidate := Node{
info: nodeInfo, BLOB: nodeInfo,
State: NodeStateOnline,
} }
if notaryDisabled { if notaryDisabled {
@ -259,28 +287,63 @@ func AddPeer(nodeInfo []byte) {
common.RemoveVotes(ctx, id) common.RemoveVotes(ctx, id)
} }
addToNetmap(ctx, candidate) addToNetmap(ctx, publicKey, candidate)
runtime.Notify("AddPeerSuccess", interop.PublicKey(publicKey))
} }
// UpdateState method updates the state of a node from the network map candidate list. // updates state of the network map candidate by its public key in the contract
// For notary-ENABLED environment, tx must be signed by both storage node and alphabet. // storage, and throws UpdateStateSuccess notification after this.
// To force update without storage node signature, see `UpdateStateIR`.
// //
// For notary-DISABLED environment, the behaviour depends on who signed the transaction: // State MUST be from the NodeState enum.
// 1. If it was signed by alphabet, go into voting. func updateCandidateState(ctx storage.Context, publicKey interop.PublicKey, state NodeState) {
// 2. If it was signed by a storage node, emit `UpdateState` notification. switch state {
// 2. Fail in any other case. case NodeStateOffline:
removeFromNetmap(ctx, publicKey)
runtime.Log("remove storage node from the network map")
case NodeStateOnline, NodeStateMaintenance:
updateNetmapState(ctx, publicKey, state)
runtime.Log("update state of the network map candidate")
default:
panic("unsupported state")
}
runtime.Notify("UpdateStateSuccess", publicKey, state)
}
// UpdateState accepts new state to be assigned to network map candidate
// identified by the given public key, identifies the signer and behaves
// depending on different conditions listed below.
// //
// The behaviour can be summarized in the following table: // Contract settings:
// | notary \ Signer | Storage node | Alphabet | Both |
// | ENABLED | FAIL | FAIL | OK |
// | DISABLED | NOTIFICATION | OK | OK (same as alphabet) |
// State argument defines node state. The only supported state now is (2) --
// offline state. Node is removed from the network map candidate list.
// //
// Method panics when invoked with unsupported states. // (1) notary-enabled
func UpdateState(state int, publicKey interop.PublicKey) { // (2) notary-disabled
//
// Signers:
//
// (a) candidate himself only, if provided public key corresponds to the signer
// (b) Alphabet member only
// (ab) both candidate and Alphabet member
// (c) others
//
// UpdateState case-by-case behavior:
//
// (1a) panics
// (1b) like (1a)
// (1ab) updates candidate's state in the contract storage (*), and throws
// UpdateStateSuccess with the provided key and new state
// (2a) throws UpdateState notification with the provided key and new state
// (2b) accepts Alphabet vote. If the threshold of votes is reached, behaves
// like (1ab).
// (c) panics
//
// (*) Candidate is removed from the candidate set if state is NodeStateOffline.
// Any other state is written into candidate's descriptor in the contract storage.
// If requested candidate is missing, panic occurs. Throws UpdateStateSuccess
// notification on success.
//
// State MUST be from the NodeState enum. Public key MUST be
// interop.PublicKeyCompressedLen bytes.
func UpdateState(state NodeState, publicKey interop.PublicKey) {
if len(publicKey) != interop.PublicKeyCompressedLen { if len(publicKey) != interop.PublicKeyCompressedLen {
panic("incorrect public key") panic("incorrect public key")
} }
@ -314,23 +377,15 @@ func UpdateState(state int, publicKey interop.PublicKey) {
common.CheckAlphabetWitness(common.AlphabetAddress()) common.CheckAlphabetWitness(common.AlphabetAddress())
} }
st := NodeState(state) updateCandidateState(ctx, publicKey, state)
switch st {
case OfflineState:
removeFromNetmap(ctx, publicKey)
runtime.Log("remove storage node from the network map")
case MaintenanceState, OnlineState:
updateNetmapState(ctx, publicKey, st)
runtime.Log("move storage node to a maintenance state")
default:
panic("unsupported state")
} }
runtime.Notify("UpdateStateSuccess", publicKey, state) // UpdateStateIR accepts Alphabet calls in the notary-enabled contract setting
} // and behaves similar to UpdateState, but does not require candidate's
// signature presence.
// UpdateStateIR method tries to change the node state in the network map. //
// Should only be invoked in notary-enabled environment by alphabet. // UpdateStateIR MUST NOT be called in notary-disabled contract setting.
// UpdateStateIR MUST be called by the Alphabet member only.
func UpdateStateIR(state NodeState, publicKey interop.PublicKey) { func UpdateStateIR(state NodeState, publicKey interop.PublicKey) {
ctx := storage.GetContext() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool) notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
@ -340,15 +395,7 @@ func UpdateStateIR(state NodeState, publicKey interop.PublicKey) {
common.CheckAlphabetWitness(common.AlphabetAddress()) common.CheckAlphabetWitness(common.AlphabetAddress())
switch state { updateCandidateState(ctx, publicKey, state)
case OfflineState:
removeFromNetmap(ctx, publicKey)
case MaintenanceState, OnlineState:
updateNetmapState(ctx, publicKey, state)
default:
panic("unsupported state")
}
runtime.Notify("UpdateStateSuccess", publicKey, state)
} }
// NewEpoch method changes the epoch number up to the provided epochNum argument. It can // NewEpoch method changes the epoch number up to the provided epochNum argument. It can
@ -397,7 +444,7 @@ func NewEpoch(epochNum int) {
panic("invalid epoch") // ignore invocations with invalid epoch panic("invalid epoch") // ignore invocations with invalid epoch
} }
dataOnlineState := filterNetmap(ctx, OnlineState) dataOnlineState := filterNetmap(ctx)
runtime.Log("process new epoch") runtime.Log("process new epoch")
@ -430,30 +477,40 @@ func LastEpochBlock() int {
return storage.Get(ctx, snapshotBlockKey).(int) return storage.Get(ctx, snapshotBlockKey).(int)
} }
// Netmap method returns a list of structures that contain a byte array of a stable // Netmap returns set of information about the storage nodes representing a network
// marshalled netmap.NodeInfo structure. These structures contain Storage nodes // map in the current epoch.
// of the current epoch. //
func Netmap() []storageNode { // Current state of each node is represented in the State field. It MAY differ
// with the state encoded into BLOB field, in this case binary encoded state
// MUST NOT be processed.
func Netmap() []Node {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
id := storage.Get(ctx, snapshotCurrentIDKey).(int) id := storage.Get(ctx, snapshotCurrentIDKey).(int)
return getSnapshot(ctx, snapshotKeyPrefix+string([]byte{byte(id)})) return getSnapshot(ctx, snapshotKeyPrefix+string([]byte{byte(id)}))
} }
// NetmapCandidates method returns a list of structures that contain the node state // NetmapCandidates returns set of information about the storage nodes
// and a byte array of a stable marshalled netmap.NodeInfo structure. // representing candidates for the network map in the coming epoch.
// These structures contain Storage node candidates for the next epoch. //
func NetmapCandidates() []netmapNode { // Current state of each node is represented in the State field. It MAY differ
// with the state encoded into BLOB field, in this case binary encoded state
// MUST NOT be processed.
func NetmapCandidates() []Node {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
return getNetmapNodes(ctx) return getNetmapNodes(ctx)
} }
// Snapshot method returns a list of structures that contain the node state // Snapshot returns set of information about the storage nodes representing a network
// (online: 1) and a byte array of a stable marshalled netmap.NodeInfo structure. // map in (current-diff)-th epoch.
// These structures contain Storage nodes of the specified epoch.
// //
// Netmap contract contains only two recent network map snapshots: current and // Diff MUST NOT be negative. Diff MUST be less than maximum number of network
// previous epoch. For diff bigger than 1 or less than 0, the method throws panic. // map snapshots stored in the contract. The limit is a contract setting,
func Snapshot(diff int) []storageNode { // DefaultSnapshotCount by default. See UpdateSnapshotCount for details.
//
// Current state of each node is represented in the State field. It MAY differ
// with the state encoded into BLOB field, in this case binary encoded state
// MUST NOT be processed.
func Snapshot(diff int) []Node {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
count := getSnapshotCount(ctx) count := getSnapshotCount(ctx)
if diff < 0 || count <= diff { if diff < 0 || count <= diff {
@ -475,6 +532,8 @@ func getSnapshotCount(ctx storage.Context) int {
// Otherwise, history is extended with empty snapshots, so // Otherwise, history is extended with empty snapshots, so
// `Snapshot` method can return invalid results for `diff = new-old` epochs // `Snapshot` method can return invalid results for `diff = new-old` epochs
// until `diff` epochs have passed. // until `diff` epochs have passed.
//
// Count MUST NOT be negative.
func UpdateSnapshotCount(count int) { func UpdateSnapshotCount(count int) {
common.CheckAlphabetWitness(common.AlphabetAddress()) common.CheckAlphabetWitness(common.AlphabetAddress())
if count < 0 { if count < 0 {
@ -552,13 +611,12 @@ func moveSnapshot(ctx storage.Context, from, to int) {
storage.Put(ctx, keyTo, data) storage.Put(ctx, keyTo, data)
} }
// SnapshotByEpoch method returns a list of structures that contain the node state // SnapshotByEpoch returns set of information about the storage nodes representing
// (online: 1) and a byte array of a stable marshalled netmap.NodeInfo structure. // a network map in the given epoch.
// These structures contain Storage nodes of the specified epoch.
// //
// Netmap contract contains only two recent network map snapshot: current and // Behaves like Snapshot: it is called after difference with the current epoch is
// previous epoch. For all others epoch method throws panic. // calculated.
func SnapshotByEpoch(epoch int) []storageNode { func SnapshotByEpoch(epoch int) []Node {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
currentEpoch := storage.Get(ctx, snapshotEpoch).(int) currentEpoch := storage.Get(ctx, snapshotEpoch).(int)
@ -610,6 +668,11 @@ func SetConfig(id, key, val []byte) {
runtime.Log("configuration has been updated") runtime.Log("configuration has been updated")
} }
type record struct {
key []byte
val []byte
}
// ListConfig returns an array of structures that contain key and value of all // ListConfig returns an array of structures that contain key and value of all
// NeoFS configuration records. Key and value are both byte arrays. // NeoFS configuration records. Key and value are both byte arrays.
func ListConfig() []record { func ListConfig() []record {
@ -636,19 +699,15 @@ func Version() int {
return common.Version return common.Version
} }
func addToNetmap(ctx storage.Context, n storageNode) { // serializes and stores the given Node by its public key in the contract storage,
var ( // and throws AddPeerSuccess notification after this.
newNode = n.info //
newNodeKey = newNode[2:35] // Public key MUST match the one encoded in BLOB field.
storageKey = append(candidatePrefix, newNodeKey...) func addToNetmap(ctx storage.Context, publicKey []byte, node Node) {
storageKey := append(candidatePrefix, publicKey...)
node = netmapNode{
node: n,
state: OnlineState,
}
)
storage.Put(ctx, storageKey, std.Serialize(node)) storage.Put(ctx, storageKey, std.Serialize(node))
runtime.Notify("AddPeerSuccess", interop.PublicKey(publicKey))
} }
func removeFromNetmap(ctx storage.Context, key interop.PublicKey) { func removeFromNetmap(ctx storage.Context, key interop.PublicKey) {
@ -662,46 +721,46 @@ func updateNetmapState(ctx storage.Context, key interop.PublicKey, state NodeSta
if raw == nil { if raw == nil {
panic("peer is missing") panic("peer is missing")
} }
node := std.Deserialize(raw).(netmapNode) node := std.Deserialize(raw).(Node)
node.state = state node.State = state
storage.Put(ctx, storageKey, std.Serialize(node)) storage.Put(ctx, storageKey, std.Serialize(node))
} }
func filterNetmap(ctx storage.Context, st NodeState) []storageNode { func filterNetmap(ctx storage.Context) []Node {
var ( var (
netmap = getNetmapNodes(ctx) netmap = getNetmapNodes(ctx)
result = []storageNode{} result = []Node{}
) )
for i := 0; i < len(netmap); i++ { for i := 0; i < len(netmap); i++ {
item := netmap[i] item := netmap[i]
if item.state == st { if item.State != NodeStateOffline {
result = append(result, item.node) result = append(result, item)
} }
} }
return result return result
} }
func getNetmapNodes(ctx storage.Context) []netmapNode { func getNetmapNodes(ctx storage.Context) []Node {
result := []netmapNode{} result := []Node{}
it := storage.Find(ctx, candidatePrefix, storage.ValuesOnly|storage.DeserializeValues) it := storage.Find(ctx, candidatePrefix, storage.ValuesOnly|storage.DeserializeValues)
for iterator.Next(it) { for iterator.Next(it) {
node := iterator.Value(it).(netmapNode) node := iterator.Value(it).(Node)
result = append(result, node) result = append(result, node)
} }
return result return result
} }
func getSnapshot(ctx storage.Context, key string) []storageNode { func getSnapshot(ctx storage.Context, key string) []Node {
data := storage.Get(ctx, key) data := storage.Get(ctx, key)
if data != nil { if data != nil {
return std.Deserialize(data.([]byte)).([]storageNode) return std.Deserialize(data.([]byte)).([]Node)
} }
return []storageNode{} return []Node{}
} }
func getConfig(ctx storage.Context, key interface{}) interface{} { func getConfig(ctx storage.Context, key interface{}) interface{} {

View file

@ -62,6 +62,7 @@ type testNodeInfo struct {
signer neotest.SingleSigner signer neotest.SingleSigner
pub []byte pub []byte
raw []byte raw []byte
state netmap.NodeState
} }
func dummyNodeInfo(acc neotest.Signer) testNodeInfo { func dummyNodeInfo(acc neotest.Signer) testNodeInfo {
@ -75,6 +76,7 @@ func dummyNodeInfo(acc neotest.Signer) testNodeInfo {
signer: s, signer: s,
pub: pub, pub: pub,
raw: ni, raw: ni,
state: netmap.NodeStateOnline,
} }
} }
@ -136,7 +138,7 @@ func TestNewEpoch(t *testing.T) {
for j := range nodes[i-1] { for j := range nodes[i-1] {
if rand.Int()%3 == 0 { if rand.Int()%3 == 0 {
cNm.Invoke(t, stackitem.Null{}, "updateStateIR", cNm.Invoke(t, stackitem.Null{}, "updateStateIR",
int64(netmap.OfflineState), nodes[i-1][j].pub) int64(netmap.NodeStateOffline), nodes[i-1][j].pub)
} else { } else {
current = append(current, nodes[i-1][j]) current = append(current, nodes[i-1][j])
} }
@ -289,18 +291,22 @@ func checkSnapshot(t *testing.T, s *vm.Stack, nodes []testNodeInfo) {
require.True(t, ok, "expected array") require.True(t, ok, "expected array")
require.Equal(t, len(nodes), len(arr), "expected %d nodes", len(nodes)) require.Equal(t, len(nodes), len(arr), "expected %d nodes", len(nodes))
actual := make([][]byte, len(nodes)) actual := make([]netmap.Node, len(nodes))
expected := make([][]byte, len(nodes)) expected := make([]netmap.Node, len(nodes))
for i := range nodes { for i := range nodes {
n, ok := arr[i].Value().([]stackitem.Item) n, ok := arr[i].Value().([]stackitem.Item)
require.True(t, ok, "expected node struct") require.True(t, ok, "expected node struct")
require.Equal(t, 1, len(n), "expected single field") require.Equalf(t, 2, len(n), "expected %d field(s)", 2)
raw, ok := n[0].Value().([]byte) require.IsType(t, []byte{}, n[0].Value())
require.True(t, ok, "expected bytes")
actual[i] = raw state, err := n[1].TryInteger()
expected[i] = nodes[i].raw require.NoError(t, err)
actual[i].BLOB = n[0].Value().([]byte)
actual[i].State = netmap.NodeState(state.Int64())
expected[i].BLOB = nodes[i].raw
expected[i].State = nodes[i].state
} }
require.ElementsMatch(t, expected, actual, "snapshot is different") require.ElementsMatch(t, expected, actual, "snapshot is different")
@ -313,7 +319,7 @@ func TestUpdateStateIR(t *testing.T) {
pub := acc.(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes() pub := acc.(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes()
t.Run("can't move online, need addPeerIR", func(t *testing.T) { t.Run("can't move online, need addPeerIR", func(t *testing.T) {
cNm.InvokeFail(t, "peer is missing", "updateStateIR", int64(netmap.OnlineState), pub) cNm.InvokeFail(t, "peer is missing", "updateStateIR", int64(netmap.NodeStateOnline), pub)
}) })
dummyInfo := dummyNodeInfo(acc) dummyInfo := dummyNodeInfo(acc)
@ -325,7 +331,7 @@ func TestUpdateStateIR(t *testing.T) {
t.Run("must be signed by the alphabet", func(t *testing.T) { t.Run("must be signed by the alphabet", func(t *testing.T) {
cAcc := cNm.WithSigners(acc) cAcc := cNm.WithSigners(acc)
cAcc.InvokeFail(t, common.ErrAlphabetWitnessFailed, "updateStateIR", int64(netmap.OfflineState), pub) cAcc.InvokeFail(t, common.ErrAlphabetWitnessFailed, "updateStateIR", int64(netmap.NodeStateOffline), pub)
}) })
t.Run("invalid state", func(t *testing.T) { t.Run("invalid state", func(t *testing.T) {
cNm.InvokeFail(t, "unsupported state", "updateStateIR", int64(42), pub) cNm.InvokeFail(t, "unsupported state", "updateStateIR", int64(42), pub)
@ -334,7 +340,7 @@ func TestUpdateStateIR(t *testing.T) {
checkNetmapCandidates(t, cNm, 2) checkNetmapCandidates(t, cNm, 2)
// Move the first node offline. // Move the first node offline.
cNm.Invoke(t, stackitem.Null{}, "updateStateIR", int64(netmap.OfflineState), pub) cNm.Invoke(t, stackitem.Null{}, "updateStateIR", int64(netmap.NodeStateOffline), pub)
checkNetmapCandidates(t, cNm, 1) checkNetmapCandidates(t, cNm, 1)
checkState := func(expected netmap.NodeState) { checkState := func(expected netmap.NodeState) {
@ -348,16 +354,16 @@ func TestUpdateStateIR(t *testing.T) {
// Move the second node in the maintenance state. // Move the second node in the maintenance state.
pub1 := acc1.(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes() pub1 := acc1.(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes()
t.Run("maintenance -> add peer", func(t *testing.T) { t.Run("maintenance -> add peer", func(t *testing.T) {
cNm.Invoke(t, stackitem.Null{}, "updateStateIR", int64(netmap.MaintenanceState), pub1) cNm.Invoke(t, stackitem.Null{}, "updateStateIR", int64(netmap.NodeStateMaintenance), pub1)
checkState(netmap.MaintenanceState) checkState(netmap.NodeStateMaintenance)
cNm.Invoke(t, stackitem.Null{}, "addPeerIR", dummyInfo1.raw) cNm.Invoke(t, stackitem.Null{}, "addPeerIR", dummyInfo1.raw)
checkState(netmap.OnlineState) checkState(netmap.NodeStateOnline)
}) })
t.Run("maintenance -> online", func(t *testing.T) { t.Run("maintenance -> online", func(t *testing.T) {
cNm.Invoke(t, stackitem.Null{}, "updateStateIR", int64(netmap.MaintenanceState), pub1) cNm.Invoke(t, stackitem.Null{}, "updateStateIR", int64(netmap.NodeStateMaintenance), pub1)
checkState(netmap.MaintenanceState) checkState(netmap.NodeStateMaintenance)
cNm.Invoke(t, stackitem.Null{}, "updateStateIR", int64(netmap.OnlineState), pub1) cNm.Invoke(t, stackitem.Null{}, "updateStateIR", int64(netmap.NodeStateOnline), pub1)
checkState(netmap.OnlineState) checkState(netmap.NodeStateOnline)
}) })
} }