diff --git a/subnet/subnet_contract.go b/subnet/subnet_contract.go index 4e89ce6..dec7aad 100644 --- a/subnet/subnet_contract.go +++ b/subnet/subnet_contract.go @@ -3,9 +3,11 @@ package subnet import ( "github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/interop/contract" + "github.com/nspcc-dev/neo-go/pkg/interop/iterator" "github.com/nspcc-dev/neo-go/pkg/interop/native/management" "github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/storage" + "github.com/nspcc-dev/neo-go/pkg/interop/util" "github.com/nspcc-dev/neofs-contract/common" ) @@ -14,12 +16,18 @@ const ( ErrInvalidSubnetID = "invalid subnet ID" // ErrInvalidOwner is thrown when owner has invalid format. ErrInvalidOwner = "invalid owner" + // ErrInvalidAdmin is thrown when admin has invalid format. + ErrInvalidAdmin = "invalid administrator" // ErrAlreadyExists is thrown when id already exists. ErrAlreadyExists = "subnet id already exists" // ErrNotExist is thrown when id doesn't exist. ErrNotExist = "subnet id doesn't exist" ownerPrefix = 'o' + nodeAdminPrefix = 'a' + clientAdminPrefix = 'm' + nodePrefix = 'n' + user = 'u' infoPrefix = 'i' notaryDisabledKey = 'z' ) @@ -131,6 +139,40 @@ func Delete(id []byte) { storage.Delete(ctx, key) } +// AddNodeAdmin adds new node administrator to the specified subnetwork. +func AddNodeAdmin(subnetID []byte, adminKey interop.PublicKey) { + if len(adminKey) != interop.PublicKeyCompressedLen { + panic("addNodeAdmin: " + ErrInvalidAdmin) + } + + ctx := storage.GetContext() + + stKey := append([]byte{ownerPrefix}, subnetID...) + + rawOwner := storage.Get(ctx, stKey) + if rawOwner == nil { + panic("addNodeAdmin: " + ErrNotExist) + } + + owner := rawOwner.([]byte) + if !runtime.CheckWitness(owner) { + panic("addNodeAdmin: owner witness check failed") + } + + stKey[0] = nodeAdminPrefix + prefixLen := len(stKey) + + iter := storage.Find(ctx, stKey, storage.KeysOnly) + for iterator.Next(iter) { + key := iterator.Value(iter).([]byte) + if util.Equals(string(key[prefixLen:]), string(adminKey)) { + panic("addNodeAdmin: node admin has already been added") + } + } + + storage.Put(ctx, append(stKey, adminKey...), []byte{1}) +} + // 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 c42d5ac..4c60e59 100644 --- a/tests/subnet_test.go +++ b/tests/subnet_test.go @@ -80,3 +80,38 @@ func TestSubnet_Delete(t *testing.T) { cAcc.InvokeFail(t, subnet.ErrNotExist, "get", id) cAcc.InvokeFail(t, subnet.ErrNotExist, "delete", id) } + +func TestSubnet_AddNodeAdmin(t *testing.T) { + e := newSubnetInvoker(t) + + owner := e.NewAccount(t) + pub, ok := vm.ParseSignatureContract(owner.Script()) + require.True(t, ok) + + id := make([]byte, 4) + binary.LittleEndian.PutUint32(id, 123) + info := randomBytes(10) + + cBoth := e.WithSigners(e.Committee, owner) + cBoth.Invoke(t, stackitem.Null{}, "put", id, pub, info) + + adm := e.NewAccount(t) + admPub, ok := vm.ParseSignatureContract(adm.Script()) + require.True(t, ok) + + const ( + method = "addNodeAdmin" + errSeparator = ": " + ) + + e.InvokeFail(t, method+errSeparator+subnet.ErrInvalidAdmin, method, id, admPub[1:]) + e.InvokeFail(t, method+errSeparator+subnet.ErrNotExist, method, []byte{0, 0, 0, 0}, admPub) + + cAdm := e.WithSigners(adm) + cAdm.InvokeFail(t, method+errSeparator+"owner witness check failed", method, id, admPub) + + cOwner := e.WithSigners(owner) + cOwner.Invoke(t, stackitem.Null{}, method, id, admPub) + + cOwner.InvokeFail(t, method+errSeparator+"node admin has already been added", method, id, admPub) +}