From 4b47bfadcd5b620b38aa22ebfb579544d5ddbe67 Mon Sep 17 00:00:00 2001 From: Pavel Karpy Date: Mon, 22 Nov 2021 21:35:07 +0300 Subject: [PATCH] [#174] subnet: Add `RemoveNode` method Signed-off-by: Pavel Karpy --- subnet/config.yml | 6 +++++ subnet/subnet_contract.go | 51 +++++++++++++++++++++++++++++++++++++++ tests/subnet_test.go | 28 +++++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/subnet/config.yml b/subnet/config.yml index 9ea4209..f4c0d6c 100644 --- a/subnet/config.yml +++ b/subnet/config.yml @@ -15,3 +15,9 @@ events: parameters: - name: id type: ByteArray + - name: NodeRemove + parameters: + - name: subnetID + type: ByteArray + - name: node + type: PublicKey diff --git a/subnet/subnet_contract.go b/subnet/subnet_contract.go index 663032d..0f67ee4 100644 --- a/subnet/subnet_contract.go +++ b/subnet/subnet_contract.go @@ -24,6 +24,8 @@ const ( ErrSubNotExist = "subnet id doesn't exist" // ErrNodeAdmNotExist is thrown when node admin is not found. ErrNodeAdmNotExist = "node admin not found" + // ErrNodeNotExist is thrown when node is not found. + ErrNodeNotExist = "node not found" // ErrAccessDenied is thrown when operation is denied for caller. ErrAccessDenied = "access denied" @@ -254,6 +256,55 @@ func AddNode(subnetID []byte, node interop.PublicKey) { storage.Put(ctx, append(stKey, node...), []byte{1}) } +// RemoveNode removes node from the specified subnetwork. +// Must be called by subnet's owner or node administrator +// only. +func RemoveNode(subnetID []byte, node interop.PublicKey) { + if len(node) != interop.PublicKeyCompressedLen { + panic("removeNode: " + ErrInvalidAdmin) + } + + ctx := storage.GetContext() + + stKey := append([]byte{ownerPrefix}, subnetID...) + prefixLen := len(stKey) + + rawOwner := storage.Get(ctx, stKey) + if rawOwner == nil { + panic("removeNode: " + ErrSubNotExist) + } + + owner := rawOwner.([]byte) + if !runtime.CheckWitness(owner) { + var hasAccess bool + + stKey[0] = nodeAdminPrefix + + iter := storage.Find(ctx, stKey, storage.KeysOnly) + for iterator.Next(iter) { + key := iterator.Value(iter).([]byte) + if runtime.CheckWitness(key[prefixLen:]) { + hasAccess = true + break + } + } + + if !hasAccess { + panic("removeNode: " + ErrAccessDenied) + } + } + + stKey[0] = nodePrefix + + if !keyInList(ctx, node, stKey) { + panic("removeNode: " + ErrNodeNotExist) + } + + storage.Delete(ctx, append(stKey, node...)) + + runtime.Notify("NodeRemove", subnetID, node) +} + // Version returns version of the contract. func Version() int { return common.Version diff --git a/tests/subnet_test.go b/tests/subnet_test.go index 49abed7..222b1d5 100644 --- a/tests/subnet_test.go +++ b/tests/subnet_test.go @@ -142,6 +142,34 @@ func TestSubnet_AddNode(t *testing.T) { cOwn.InvokeFail(t, method+errSeparator+"node has already been added", method, id, nodePub) } +func TestSubnet_RemoveNode(t *testing.T) { + e := newSubnetInvoker(t) + + id, owner := createSubnet(t, e) + + node := e.NewAccount(t) + nodePub, ok := vm.ParseSignatureContract(node.Script()) + require.True(t, ok) + + adm := e.NewAccount(t) + admPub, ok := vm.ParseSignatureContract(adm.Script()) + require.True(t, ok) + + const method = "removeNode" + + cOwn := e.WithSigners(owner) + cOwn.InvokeFail(t, method+errSeparator+subnet.ErrInvalidAdmin, method, id, nodePub[1:]) + cOwn.InvokeFail(t, method+errSeparator+subnet.ErrNodeNotExist, method, id, nodePub) + + cOwn.Invoke(t, stackitem.Null{}, "addNode", id, nodePub) + cOwn.Invoke(t, stackitem.Null{}, method, id, nodePub) + + cAdm := cOwn.WithSigners(adm) + + cOwn.Invoke(t, stackitem.Null{}, "addNodeAdmin", id, admPub) + cAdm.InvokeFail(t, method+errSeparator+subnet.ErrNodeNotExist, method, id, nodePub) +} + func createSubnet(t *testing.T, e *neotest.ContractInvoker) (id []byte, owner neotest.Signer) { var ( ok bool