diff --git a/subnet/subnet_contract.go b/subnet/subnet_contract.go index c3bb80d..54a1be9 100644 --- a/subnet/subnet_contract.go +++ b/subnet/subnet_contract.go @@ -17,12 +17,12 @@ const ( ErrInvalidOwner = "invalid owner" // ErrInvalidAdmin is thrown when admin has invalid format. ErrInvalidAdmin = "invalid administrator" - // ErrInvalidNode is thrown when node has invalid format. - ErrInvalidNode = "invalid node key" // ErrAlreadyExists is thrown when id already exists. ErrAlreadyExists = "subnet id already exists" // ErrSubNotExist is thrown when id doesn't exist. ErrSubNotExist = "subnet id doesn't exist" + // ErrInvalidNode is thrown when node has invalid format. + ErrInvalidNode = "invalid node key" // ErrNodeAdmNotExist is thrown when node admin is not found. ErrNodeAdmNotExist = "node admin not found" // ErrNodeNotExist is thrown when node is not found. @@ -176,7 +176,7 @@ func AddNodeAdmin(subnetID []byte, adminKey interop.PublicKey) { panic("addNodeAdmin: node admin has already been added") } - storage.Put(ctx, append(stKey, adminKey...), []byte{1}) + putKeyInList(ctx, adminKey, stKey) } // RemoveNodeAdmin removes node administrator from the specified subnetwork. @@ -188,9 +188,9 @@ func RemoveNodeAdmin(subnetID []byte, adminKey interop.PublicKey) { ctx := storage.GetContext() - stOwnerKey := append([]byte{ownerPrefix}, subnetID...) + stKey := append([]byte{ownerPrefix}, subnetID...) - rawOwner := storage.Get(ctx, stOwnerKey) + rawOwner := storage.Get(ctx, stKey) if rawOwner == nil { panic("removeNodeAdmin: " + ErrSubNotExist) } @@ -200,14 +200,13 @@ func RemoveNodeAdmin(subnetID []byte, adminKey interop.PublicKey) { panic("removeNodeAdmin: " + errCheckWitnessFailed) } - stOwnerKey[0] = nodeAdminPrefix - stNodeAdmKey := append(stOwnerKey, adminKey...) + stKey[0] = nodeAdminPrefix - if storage.Get(ctx, stNodeAdmKey) == nil { + if !keyInList(ctx, adminKey, stKey) { panic("removeNodeAdmin: " + ErrNodeAdmNotExist) } - storage.Delete(ctx, stNodeAdmKey) + deleteKeyFromList(ctx, adminKey, stKey) } // AddNode adds node to the specified subnetwork. @@ -254,7 +253,7 @@ func AddNode(subnetID []byte, node interop.PublicKey) { panic("addNode: node has already been added") } - storage.Put(ctx, append(stKey, node...), []byte{1}) + putKeyInList(ctx, node, stKey) } // RemoveNode removes node from the specified subnetwork. @@ -327,21 +326,50 @@ func NodeAllowed(subnetID []byte, node interop.PublicKey) bool { return storage.Get(ctx, append(stKey, node...)) != nil } +// AddClientAdmin adds new client administrator of the specified group in the specified subnetwork. +// Must be called by owner only. +func AddClientAdmin(subnetID []byte, groupID []byte, adminPublicKey interop.PublicKey) { + if len(adminPublicKey) != interop.PublicKeyCompressedLen { + panic("addClientAdmin: " + ErrInvalidAdmin) + } + + ctx := storage.GetContext() + + stKey := append([]byte{ownerPrefix}, subnetID...) + + rawOwner := storage.Get(ctx, stKey) + if rawOwner == nil { + panic("addClientAdmin: " + ErrSubNotExist) + } + + owner := rawOwner.([]byte) + if !runtime.CheckWitness(owner) { + panic("addClientAdmin: " + errCheckWitnessFailed) + } + + stKey[0] = clientAdminPrefix + stKey = append(stKey, groupID...) + + if keyInList(ctx, adminPublicKey, stKey) { + panic("addClientAdmin: client admin has already been added") + } + + putKeyInList(ctx, adminPublicKey, stKey) +} + // Version returns version of the contract. func Version() int { return common.Version } func keyInList(ctx storage.Context, searchedKey interop.PublicKey, prefix []byte) bool { - prefixLen := len(prefix) - - iter := storage.Find(ctx, prefix, storage.KeysOnly) - for iterator.Next(iter) { - key := iterator.Value(iter).([]byte) - if common.BytesEqual(key[prefixLen:], searchedKey) { - return true - } - } - - return false + return storage.Get(ctx, append(prefix, searchedKey...)) != nil +} + +func putKeyInList(ctx storage.Context, keyToPut interop.PublicKey, prefix []byte) { + storage.Put(ctx, append(prefix, keyToPut...), []byte{1}) +} + +func deleteKeyFromList(ctx storage.Context, keyToDelete interop.PublicKey, prefix []byte) { + storage.Delete(ctx, append(prefix, keyToDelete...)) } diff --git a/tests/subnet_test.go b/tests/subnet_test.go index c1b6535..7b0807c 100644 --- a/tests/subnet_test.go +++ b/tests/subnet_test.go @@ -137,6 +137,7 @@ func TestSubnet_AddNode(t *testing.T) { cOwn := e.WithSigners(owner) cOwn.InvokeFail(t, method+errSeparator+subnet.ErrInvalidNode, method, id, nodePub[1:]) + cOwn.InvokeFail(t, method+errSeparator+subnet.ErrSubNotExist, method, []byte{0, 0, 0, 0}, nodePub) cOwn.Invoke(t, stackitem.Null{}, method, id, nodePub) cOwn.InvokeFail(t, method+errSeparator+"node has already been added", method, id, nodePub) @@ -159,6 +160,7 @@ func TestSubnet_RemoveNode(t *testing.T) { cOwn := e.WithSigners(owner) cOwn.InvokeFail(t, method+errSeparator+subnet.ErrInvalidNode, method, id, nodePub[1:]) + cOwn.InvokeFail(t, method+errSeparator+subnet.ErrSubNotExist, method, []byte{0, 0, 0, 0}, nodePub) cOwn.InvokeFail(t, method+errSeparator+subnet.ErrNodeNotExist, method, id, nodePub) cOwn.Invoke(t, stackitem.Null{}, "addNode", id, nodePub) @@ -183,12 +185,33 @@ func TestSubnet_NodeAllowed(t *testing.T) { cOwn := e.WithSigners(owner) cOwn.InvokeFail(t, method+errSeparator+subnet.ErrInvalidNode, method, id, nodePub[1:]) + cOwn.InvokeFail(t, method+errSeparator+subnet.ErrSubNotExist, method, []byte{0, 0, 0, 0}, nodePub) cOwn.Invoke(t, stackitem.NewBool(false), method, id, nodePub) cOwn.Invoke(t, stackitem.Null{}, "addNode", id, nodePub) cOwn.Invoke(t, stackitem.NewBool(true), method, id, nodePub) } +func TestSubnet_AddClientAdmin(t *testing.T) { + e := newSubnetInvoker(t) + + id, owner := createSubnet(t, e) + + adm := e.NewAccount(t) + admPub, ok := vm.ParseSignatureContract(adm.Script()) + require.True(t, ok) + + const method = "addClientAdmin" + + groupId := randomBytes(8) + + cOwn := e.WithSigners(owner) + cOwn.InvokeFail(t, method+errSeparator+subnet.ErrInvalidAdmin, method, id, groupId, admPub[1:]) + cOwn.InvokeFail(t, method+errSeparator+subnet.ErrSubNotExist, method, []byte{0, 0, 0, 0}, groupId, admPub) + cOwn.Invoke(t, stackitem.Null{}, method, id, groupId, admPub) + cOwn.InvokeFail(t, method+errSeparator+"client admin has already been added", method, id, groupId, admPub) +} + func createSubnet(t *testing.T, e *neotest.ContractInvoker) (id []byte, owner neotest.Signer) { var ( ok bool