From 3fb511ac15d2eebada28a7fbf08f5120dbb428b8 Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Thu, 7 Dec 2023 12:23:18 +0300 Subject: [PATCH] [#48] frostfsid: Support empty namespaces Require ns to create subject. Since we don't allow move subject from one ns to another - drop add/remove subject to/from namespace Signed-off-by: Denis Kirillov --- frostfsid/client/client.go | 46 ++------ frostfsid/frostfsid_contract.go | 157 +++++++------------------ rpcclient/frostfsid/client.go | 56 +-------- tests/frostfsid_client_test.go | 198 ++++++++++++++++---------------- tests/frostfsid_test.go | 109 ++++++++---------- 5 files changed, 202 insertions(+), 364 deletions(-) diff --git a/frostfsid/client/client.go b/frostfsid/client/client.go index 07ef08b..6e78b99 100644 --- a/frostfsid/client/client.go +++ b/frostfsid/client/client.go @@ -101,13 +101,11 @@ const ( deleteSubjectKVMethod = "deleteSubjectKV" deleteSubjectMethod = "deleteSubject" - createNamespaceMethod = "createNamespace" - getNamespaceMethod = "getNamespace" - getNamespaceExtendedMethod = "getNamespaceExtended" - listNamespacesMethod = "listNamespaces" - addSubjectToNamespaceMethod = "addSubjectToNamespace" - removeSubjectFromNamespaceMethod = "removeSubjectFromNamespace" - listNamespaceSubjectsMethod = "listNamespaceSubjects" + createNamespaceMethod = "createNamespace" + getNamespaceMethod = "getNamespace" + getNamespaceExtendedMethod = "getNamespaceExtended" + listNamespacesMethod = "listNamespaces" + listNamespaceSubjectsMethod = "listNamespaceSubjects" createGroupMethod = "createGroup" getGroupMethod = "getGroup" @@ -199,16 +197,16 @@ func (c Client) GetAdmin() (util.Uint160, bool, error) { return u, true, err } -// CreateSubject creates new subject using public key. +// CreateSubject creates new subject using public key and namespace. // Must be invoked by contract owner. -func (c Client) CreateSubject(key *keys.PublicKey) (tx util.Uint256, vub uint32, err error) { - method, args := c.CreateSubjectCall(key) +func (c Client) CreateSubject(ns string, key *keys.PublicKey) (tx util.Uint256, vub uint32, err error) { + method, args := c.CreateSubjectCall(ns, key) return c.act.SendCall(c.contract, method, args...) } // CreateSubjectCall provides args for CreateSubject to use in commonclient.Transaction. -func (c Client) CreateSubjectCall(key *keys.PublicKey) (method string, args []any) { - return createSubjectMethod, []any{key.Bytes()} +func (c Client) CreateSubjectCall(ns string, key *keys.PublicKey) (method string, args []any) { + return createSubjectMethod, []any{ns, key.Bytes()} } // GetSubject gets subject by address. @@ -366,30 +364,6 @@ func (c Client) ListNamespaces() ([]*Namespace, error) { return parseNamespaces(items) } -// AddSubjectToNamespace adds a subject to namespace. -// Must be invoked by contract owner. -func (c Client) AddSubjectToNamespace(addr util.Uint160, namespace string) (tx util.Uint256, vub uint32, err error) { - method, args := c.AddSubjectToNamespaceCall(addr, namespace) - return c.act.SendCall(c.contract, method, args...) -} - -// AddSubjectToNamespaceCall provides args for AddSubjectToNamespace to use in commonclient.Transaction. -func (c Client) AddSubjectToNamespaceCall(addr util.Uint160, namespace string) (method string, args []any) { - return addSubjectToNamespaceMethod, []any{addr, namespace} -} - -// RemoveSubjectFromNamespace removes a subject from namespace. -// Must be invoked by contract owner. -func (c Client) RemoveSubjectFromNamespace(addr util.Uint160) (tx util.Uint256, vub uint32, err error) { - method, args := c.RemoveSubjectFromNamespaceCall(addr) - return c.act.SendCall(c.contract, method, args...) -} - -// RemoveSubjectFromNamespaceCall provides args for RemoveSubjectFromNamespace to use in commonclient.Transaction. -func (c Client) RemoveSubjectFromNamespaceCall(addr util.Uint160) (method string, args []any) { - return removeSubjectFromNamespaceMethod, []any{addr} -} - // ListNamespaceSubjects gets all subjects from namespace. func (c Client) ListNamespaceSubjects(namespace string) ([]util.Uint160, error) { return unwrapArrayOfUint160(commonclient.ReadIteratorItems(c.act, iteratorBatchSize, c.contract, listNamespaceSubjectsMethod, namespace)) diff --git a/frostfsid/frostfsid_contract.go b/frostfsid/frostfsid_contract.go index 0f391ad..2afcba0 100644 --- a/frostfsid/frostfsid_contract.go +++ b/frostfsid/frostfsid_contract.go @@ -85,6 +85,7 @@ func _deploy(data any, isUpdate bool) { } storage.Put(ctx, groupCounterKey, 0) + storage.Put(ctx, namespaceKey(""), std.Serialize(Namespace{})) runtime.Log("frostfsid contract initialized") } @@ -128,7 +129,7 @@ func Version() int { return common.Version } -func CreateSubject(key interop.PublicKey) { +func CreateSubject(ns string, key interop.PublicKey) { ctx := storage.GetContext() checkContractOwner(ctx) @@ -149,11 +150,22 @@ func CreateSubject(key interop.PublicKey) { panic("key is occupied") } + nsKey := namespaceKey(ns) + data = storage.Get(ctx, nsKey).([]byte) + if data == nil { + panic("namespace not found") + } + subj := Subject{ PrimaryKey: key, + Namespace: ns, } storage.Put(ctx, sKey, std.Serialize(subj)) + + nsSubjKey := namespaceSubjectKey(ns, addr) + storage.Put(ctx, nsSubjKey, []byte{1}) + runtime.Notify("CreateSubject", interop.Hash160(addr)) } @@ -315,9 +327,7 @@ func DeleteSubject(addr interop.Hash160) { } storage.Delete(ctx, sKey) - if subj.Namespace != "" { - removeSubjectFromNamespace(ctx, subj.Namespace, addr) - } + removeSubjectFromNamespace(ctx, subj.Namespace, addr) deleteNamespaceSubjectName(ctx, subj.Namespace, subj.Name) runtime.Notify("DeleteSubject", addr) @@ -339,33 +349,21 @@ func GetSubject(addr interop.Hash160) Subject { } func GetSubjectExtended(addr interop.Hash160) SubjectExtended { - if len(addr) != interop.Hash160Len { - panic("incorrect address length") - } - + subj := GetSubject(addr) ctx := storage.GetReadOnlyContext() - sKey := subjectKeyFromAddr(addr) - data := storage.Get(ctx, sKey).([]byte) - if data == nil { - panic("subject not found") - } - - subj := std.Deserialize(data).(Subject) var groups []Group - if subj.Namespace != "" { - nsHash := ripemd160Hash(subj.Namespace) - it := storage.Find(ctx, groupPrefixFromHash(nsHash), storage.KeysOnly|storage.RemovePrefix) - for iterator.Next(it) { - groupHash := iterator.Value(it).([]byte) - data = storage.Get(ctx, groupSubjectKeyFromHashes(nsHash, groupHash, addr)).([]byte) - if data != nil { - data = storage.Get(ctx, groupKeyFromHashes(nsHash, groupHash)).([]byte) - if data == nil { - panic("group not found") - } - groups = append(groups, std.Deserialize(data).(Group)) + nsHash := ripemd160Hash(subj.Namespace) + it := storage.Find(ctx, groupPrefixFromHash(nsHash), storage.KeysOnly|storage.RemovePrefix) + for iterator.Next(it) { + groupHash := iterator.Value(it).([]byte) + data := storage.Get(ctx, groupSubjectKeyFromHashes(nsHash, groupHash, addr)).([]byte) + if data != nil { + data = storage.Get(ctx, groupKeyFromHashes(nsHash, groupHash)).([]byte) + if data == nil { + panic("group not found") } + groups = append(groups, std.Deserialize(data).(Group)) } } @@ -410,8 +408,8 @@ func GetSubjectByKey(key interop.PublicKey) Subject { } func GetSubjectKeyByName(ns, name string) interop.PublicKey { - if ns == "" || name == "" { - panic("invalid namespace or name") + if name == "" { + panic("invalid or name") } ctx := storage.GetReadOnlyContext() @@ -434,10 +432,6 @@ func CreateNamespace(ns string) { ctx := storage.GetContext() checkContractOwner(ctx) - if ns == "" { - panic("invalid namespace name") - } - nsKey := namespaceKey(ns) data := storage.Get(ctx, nsKey).([]byte) if data != nil { @@ -494,76 +488,6 @@ func ListNamespaces() iterator.Iterator { return storage.Find(ctx, []byte{namespaceKeysPrefix}, storage.ValuesOnly|storage.DeserializeValues) } -func AddSubjectToNamespace(addr interop.Hash160, ns string) { - ctx := storage.GetContext() - checkContractOwner(ctx) - - if ns == "" { - panic("invalid namespace name") - } - - if len(addr) != interop.Hash160Len { - panic("invalid address length") - } - - sKey := subjectKeyFromAddr(addr) - data := storage.Get(ctx, sKey).([]byte) - if data == nil { - panic("subject not found") - } - subject := std.Deserialize(data).(Subject) - - if subject.Namespace == ns { - panic("subject already added") - } - if subject.Namespace != "" { - panic("subject cannot be moved to another namespace") - } - - subject.Namespace = ns - storage.Put(ctx, sKey, std.Serialize(subject)) - - data = storage.Get(ctx, namespaceKey(ns)).([]byte) - if data == nil { - panic("namespace not found") - } - - nsSubjKey := namespaceSubjectKey(ns, addr) - storage.Put(ctx, nsSubjKey, []byte{1}) - - setNamespaceSubjectName(ctx, subject) - - runtime.Notify("AddSubjectToNamespace", addr, ns) -} - -func RemoveSubjectFromNamespace(addr interop.Hash160) { - ctx := storage.GetContext() - checkContractOwner(ctx) - - if len(addr) != interop.Hash160Len { - panic("invalid address length") - } - - sKey := subjectKeyFromAddr(addr) - data := storage.Get(ctx, sKey).([]byte) - if data == nil { - panic("subject not found") - } - subject := std.Deserialize(data).(Subject) - - if subject.Namespace == "" { - panic("subject does not belong to any namespace") - } - - removeSubjectFromNamespace(ctx, subject.Namespace, addr) - deleteNamespaceSubjectName(ctx, subject.Namespace, subject.Name) - - subject.Namespace = "" - storage.Put(ctx, sKey, std.Serialize(subject)) - - runtime.Notify("RemoveSubjectFromNamespace", addr, subject.Namespace) -} - func ListNamespaceSubjects(ns string) iterator.Iterator { ctx := storage.GetReadOnlyContext() return storage.Find(ctx, namespaceSubjectPrefix(ns), storage.KeysOnly|storage.RemovePrefix) @@ -573,9 +497,6 @@ func CreateGroup(ns, group string) int { ctx := storage.GetContext() checkContractOwner(ctx) - if ns == "" { - panic("invalid namespace name") - } if group == "" { panic("invalid group name") } @@ -643,8 +564,8 @@ func GetGroupExtended(ns string, groupID int) GroupExtended { } func GetGroupIDByName(ns, name string) int { - if ns == "" || name == "" { - panic("invalid namespace or name") + if name == "" { + panic("invalid name") } ctx := storage.GetReadOnlyContext() @@ -735,18 +656,16 @@ func AddSubjectToGroup(addr interop.Hash160, groupID int) { } subject := std.Deserialize(data).(Subject) - if subject.Namespace == "" { - panic("subject does not belong to any namespace") - } - - nSubjKey := namespaceSubjectKey(subject.Namespace, addr) - storage.Put(ctx, nSubjKey, []byte{1}) - gKey := groupKey(subject.Namespace, groupID) data = storage.Get(ctx, gKey).([]byte) if data == nil { panic("group not found") } + group := std.Deserialize(data).(Group) + + if group.Namespace != subject.Namespace { + panic("subject and group must be in the same namespace") + } gsKey := groupSubjectKey(subject.Namespace, groupID, addr) storage.Put(ctx, gsKey, []byte{1}) @@ -769,15 +688,17 @@ func RemoveSubjectFromGroup(addr interop.Hash160, groupID int) { } subject := std.Deserialize(data).(Subject) - if subject.Namespace == "" { - panic("subject doesn't belong to any namespace") - } gKey := groupKey(subject.Namespace, groupID) data = storage.Get(ctx, gKey).([]byte) if data == nil { panic("group not found") } + group := std.Deserialize(data).(Group) + + if group.Namespace != subject.Namespace { + panic("subject and group must be in the same namespace") + } gsKey := groupSubjectKey(subject.Namespace, groupID, addr) storage.Delete(ctx, gsKey) diff --git a/rpcclient/frostfsid/client.go b/rpcclient/frostfsid/client.go index 39bf6dc..4c4a999 100644 --- a/rpcclient/frostfsid/client.go +++ b/rpcclient/frostfsid/client.go @@ -336,28 +336,6 @@ func (c *Contract) AddSubjectToGroupUnsigned(addr util.Uint160, groupID *big.Int return c.actor.MakeUnsignedCall(c.hash, "addSubjectToGroup", nil, addr, groupID) } -// AddSubjectToNamespace creates a transaction invoking `addSubjectToNamespace` method of the contract. -// This transaction is signed and immediately sent to the network. -// The values returned are its hash, ValidUntilBlock value and error if any. -func (c *Contract) AddSubjectToNamespace(addr util.Uint160, ns string) (util.Uint256, uint32, error) { - return c.actor.SendCall(c.hash, "addSubjectToNamespace", addr, ns) -} - -// AddSubjectToNamespaceTransaction creates a transaction invoking `addSubjectToNamespace` method of the contract. -// This transaction is signed, but not sent to the network, instead it's -// returned to the caller. -func (c *Contract) AddSubjectToNamespaceTransaction(addr util.Uint160, ns string) (*transaction.Transaction, error) { - return c.actor.MakeCall(c.hash, "addSubjectToNamespace", addr, ns) -} - -// AddSubjectToNamespaceUnsigned creates a transaction invoking `addSubjectToNamespace` method of the contract. -// This transaction is not signed, it's simply returned to the caller. -// Any fields of it that do not affect fees can be changed (ValidUntilBlock, -// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. -func (c *Contract) AddSubjectToNamespaceUnsigned(addr util.Uint160, ns string) (*transaction.Transaction, error) { - return c.actor.MakeUnsignedCall(c.hash, "addSubjectToNamespace", nil, addr, ns) -} - // ClearAdmin creates a transaction invoking `clearAdmin` method of the contract. // This transaction is signed and immediately sent to the network. // The values returned are its hash, ValidUntilBlock value and error if any. @@ -427,23 +405,23 @@ func (c *Contract) CreateNamespaceUnsigned(ns string) (*transaction.Transaction, // CreateSubject creates a transaction invoking `createSubject` method of the contract. // This transaction is signed and immediately sent to the network. // The values returned are its hash, ValidUntilBlock value and error if any. -func (c *Contract) CreateSubject(key *keys.PublicKey) (util.Uint256, uint32, error) { - return c.actor.SendCall(c.hash, "createSubject", key) +func (c *Contract) CreateSubject(ns string, key *keys.PublicKey) (util.Uint256, uint32, error) { + return c.actor.SendCall(c.hash, "createSubject", ns, key) } // CreateSubjectTransaction creates a transaction invoking `createSubject` method of the contract. // This transaction is signed, but not sent to the network, instead it's // returned to the caller. -func (c *Contract) CreateSubjectTransaction(key *keys.PublicKey) (*transaction.Transaction, error) { - return c.actor.MakeCall(c.hash, "createSubject", key) +func (c *Contract) CreateSubjectTransaction(ns string, key *keys.PublicKey) (*transaction.Transaction, error) { + return c.actor.MakeCall(c.hash, "createSubject", ns, key) } // CreateSubjectUnsigned creates a transaction invoking `createSubject` method of the contract. // This transaction is not signed, it's simply returned to the caller. // Any fields of it that do not affect fees can be changed (ValidUntilBlock, // Nonce), fee values (NetworkFee, SystemFee) can be increased as well. -func (c *Contract) CreateSubjectUnsigned(key *keys.PublicKey) (*transaction.Transaction, error) { - return c.actor.MakeUnsignedCall(c.hash, "createSubject", nil, key) +func (c *Contract) CreateSubjectUnsigned(ns string, key *keys.PublicKey) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(c.hash, "createSubject", nil, ns, key) } // DeleteGroup creates a transaction invoking `deleteGroup` method of the contract. @@ -556,28 +534,6 @@ func (c *Contract) RemoveSubjectFromGroupUnsigned(addr util.Uint160, groupID *bi return c.actor.MakeUnsignedCall(c.hash, "removeSubjectFromGroup", nil, addr, groupID) } -// RemoveSubjectFromNamespace creates a transaction invoking `removeSubjectFromNamespace` method of the contract. -// This transaction is signed and immediately sent to the network. -// The values returned are its hash, ValidUntilBlock value and error if any. -func (c *Contract) RemoveSubjectFromNamespace(addr util.Uint160) (util.Uint256, uint32, error) { - return c.actor.SendCall(c.hash, "removeSubjectFromNamespace", addr) -} - -// RemoveSubjectFromNamespaceTransaction creates a transaction invoking `removeSubjectFromNamespace` method of the contract. -// This transaction is signed, but not sent to the network, instead it's -// returned to the caller. -func (c *Contract) RemoveSubjectFromNamespaceTransaction(addr util.Uint160) (*transaction.Transaction, error) { - return c.actor.MakeCall(c.hash, "removeSubjectFromNamespace", addr) -} - -// RemoveSubjectFromNamespaceUnsigned creates a transaction invoking `removeSubjectFromNamespace` method of the contract. -// This transaction is not signed, it's simply returned to the caller. -// Any fields of it that do not affect fees can be changed (ValidUntilBlock, -// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. -func (c *Contract) RemoveSubjectFromNamespaceUnsigned(addr util.Uint160) (*transaction.Transaction, error) { - return c.actor.MakeUnsignedCall(c.hash, "removeSubjectFromNamespace", nil, addr) -} - // RemoveSubjectKey creates a transaction invoking `removeSubjectKey` method of the contract. // This transaction is signed and immediately sent to the network. // The values returned are its hash, ValidUntilBlock value and error if any. diff --git a/tests/frostfsid_client_test.go b/tests/frostfsid_client_test.go index c29c689..952ba54 100644 --- a/tests/frostfsid_client_test.go +++ b/tests/frostfsid_client_test.go @@ -11,6 +11,7 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/rpcclient" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/wallet" @@ -115,7 +116,7 @@ func TestFrostFSID_Client_SubjectManagement(t *testing.T) { subjLogin := "subj-login" iamPathKV := "iam/path" - ffsid.a.await(ffsid.cli.CreateSubject(subjKey.PublicKey())) + ffsid.a.await(ffsid.cli.CreateSubject(defaultNamespace, subjKey.PublicKey())) ffsid.a.await(ffsid.cli.SetSubjectName(subjAddr, subjLogin)) ffsid.a.await(ffsid.cli.SetSubjectKV(subjAddr, client.IAMPathKey, iamPathKV)) ffsid.a.await(ffsid.cli.AddSubjectKey(subjAddr, extraKey.PublicKey())) @@ -157,25 +158,22 @@ func TestFrostFSID_Client_NamespaceManagement(t *testing.T) { ffsid, cancel := initFrostfsIFClientTest(t) defer cancel() - subjKey, subjAddr := newKey(t) - ffsid.a.await(ffsid.cli.CreateSubject(subjKey.PublicKey())) - namespace := "namespace" + subjKey, subjAddr := newKey(t) + ffsid.a.await(ffsid.cli.CreateNamespace(namespace)) _, _, err := ffsid.cli.CreateNamespace(namespace) require.ErrorContains(t, err, "already exists") + ffsid.a.await(ffsid.cli.CreateSubject(namespace, subjKey.PublicKey())) + ns, err := ffsid.cli.GetNamespace(namespace) require.NoError(t, err) require.Equal(t, namespace, ns.Name) namespaces, err := ffsid.cli.ListNamespaces() require.NoError(t, err) - require.ElementsMatch(t, []*client.Namespace{ns}, namespaces) - - ffsid.a.await(ffsid.cli.AddSubjectToNamespace(subjAddr, namespace)) - _, _, err = ffsid.cli.AddSubjectToNamespace(subjAddr, namespace) - require.ErrorContains(t, err, "already added") + require.ElementsMatch(t, []*client.Namespace{{}, ns}, namespaces) subj, err := ffsid.cli.GetSubject(subjAddr) require.NoError(t, err) @@ -185,26 +183,57 @@ func TestFrostFSID_Client_NamespaceManagement(t *testing.T) { require.NoError(t, err) require.ElementsMatch(t, []util.Uint160{subjAddr}, subjects) - ffsid.a.await(ffsid.cli.RemoveSubjectFromNamespace(subjAddr)) - - subj, err = ffsid.cli.GetSubject(subjAddr) - require.NoError(t, err) - require.Empty(t, subj.Namespace) + ffsid.a.await(ffsid.cli.DeleteSubject(subjAddr)) subjects, err = ffsid.cli.ListNamespaceSubjects(namespace) require.NoError(t, err) require.Empty(t, subjects) } +func TestFrostFSID_Client_DefaultNamespace(t *testing.T) { + ffsid, cancel := initFrostfsIFClientTest(t) + defer cancel() + + group := "group" + subjKey, subjAddr := newKey(t) + + ffsid.a.await(ffsid.cli.CreateSubject(defaultNamespace, subjKey.PublicKey())) + groupID, err := ffsid.cli.ParseGroupID(ffsid.cli.Wait(ffsid.cli.CreateGroup(defaultNamespace, group))) + require.NoError(t, err) + ffsid.a.await(ffsid.cli.AddSubjectToGroup(subjAddr, groupID)) + + namespaces, err := ffsid.cli.ListNamespaces() + require.NoError(t, err) + require.EqualValues(t, []*client.Namespace{{}}, namespaces) + + groups, err := ffsid.cli.ListGroups(defaultNamespace) + require.NoError(t, err) + require.EqualValues(t, []*client.Group{{ID: groupID, Name: group, Namespace: defaultNamespace}}, groups) + + subjects, err := ffsid.cli.ListNamespaceSubjects(defaultNamespace) + require.NoError(t, err) + require.EqualValues(t, []util.Uint160{subjAddr}, subjects) + + subjects, err = ffsid.cli.ListGroupSubjects(defaultNamespace, groupID) + require.NoError(t, err) + require.EqualValues(t, []util.Uint160{subjAddr}, subjects) + + subject, err := ffsid.cli.GetSubjectExtended(subjAddr) + require.NoError(t, err) + require.True(t, subjKey.PublicKey().Equal(subject.PrimaryKey)) + require.Equal(t, defaultNamespace, subject.Namespace) + require.EqualValues(t, []*client.Group{{ID: groupID, Name: group, Namespace: defaultNamespace}}, subject.Groups) +} + func TestFrostFSID_Client_GroupManagement(t *testing.T) { ffsid, cancel := initFrostfsIFClientTest(t) defer cancel() - subjKey, subjAddr := newKey(t) - ffsid.a.await(ffsid.cli.CreateSubject(subjKey.PublicKey())) - namespace := "namespace" + subjKey, subjAddr := newKey(t) + ffsid.a.await(ffsid.cli.CreateNamespace(namespace)) + ffsid.a.await(ffsid.cli.CreateSubject(namespace, subjKey.PublicKey())) groupName := "group" groupID := int64(1) @@ -229,7 +258,6 @@ func TestFrostFSID_Client_GroupManagement(t *testing.T) { require.NoError(t, err) require.Zero(t, groupExt.SubjectsCount) - ffsid.a.await(ffsid.cli.AddSubjectToNamespace(subjAddr, namespace)) ffsid.a.await(ffsid.cli.AddSubjectToGroup(subjAddr, groupID)) subjExt, err := ffsid.cli.GetSubjectExtended(subjAddr) @@ -276,73 +304,17 @@ func TestFrostFSID_Client_Lists(t *testing.T) { ffsid, cancel := initFrostfsIFClientTest(t) defer cancel() - namespaces := []string{"empty-ns0", "ns1", "ns2", "ns3"} + namespaces := append([]string{defaultNamespace}, createNamespaces(t, ffsid, 3)...) - tx := ffsid.cli.StartTx() - for _, namespace := range namespaces { - err := tx.WrapCall(ffsid.cli.CreateNamespaceCall(namespace)) - require.NoError(t, err) - } - ffsid.a.await(ffsid.cli.SendTx(tx)) + subjects := createSubjectsInNS(t, ffsid, namespaces[1], 3) + subjects = append(subjects, createSubjectsInNS(t, ffsid, namespaces[2], 2)...) + subjects = append(subjects, createSubjectsInNS(t, ffsid, namespaces[3], 5)...) - subjects := make([]testSubject, 10) - tx = ffsid.cli.StartTx() - for i := range subjects { - subjects[i].key, subjects[i].addr = newKey(t) - err := tx.WrapCall(ffsid.cli.CreateSubjectCall(subjects[i].key.PublicKey())) - require.NoError(t, err) - } - ffsid.a.await(ffsid.cli.SendTx(tx)) + groups := createGroupsInNS(t, ffsid, namespaces[0], 1) + groups = append(groups, createGroupsInNS(t, ffsid, namespaces[1], 2)...) + groups = append(groups, createGroupsInNS(t, ffsid, namespaces[2], 1)...) + groups = append(groups, createGroupsInNS(t, ffsid, namespaces[3], 2)...) - groups := []client.Group{ - { - ID: 1, - Name: "empty-group0", - Namespace: namespaces[0], - }, - { - ID: 2, - Name: "empty-group1", - Namespace: namespaces[1], - }, - { - ID: 3, - Name: "group2", - Namespace: namespaces[1], - }, - { - ID: 4, - Name: "group3", - Namespace: namespaces[2], - }, - { - ID: 5, - Name: "group4", - Namespace: namespaces[3], - }, - { - ID: 6, - Name: "group5", - Namespace: namespaces[3], - }, - } - - tx = ffsid.cli.StartTx() - for _, group := range groups[:3] { - err := tx.WrapCall(ffsid.cli.CreateGroupCall(group.Namespace, group.Name)) - require.NoError(t, err) - } - ffsid.a.await(ffsid.cli.SendTx(tx)) - - // we split into two tx because of gas limit exceeded error - tx = ffsid.cli.StartTx() - for _, group := range groups[3:] { - err := tx.WrapCall(ffsid.cli.CreateGroupCall(group.Namespace, group.Name)) - require.NoError(t, err) - } - ffsid.a.await(ffsid.cli.SendTx(tx)) - - addSubjectsToNamespaces(t, ffsid, namespaces, subjects) addSubjectsToGroups(t, ffsid, groups, subjects) nsList, err := ffsid.cli.ListNamespaces() @@ -370,29 +342,55 @@ func TestFrostFSID_Client_Lists(t *testing.T) { checkGroupSubjects(t, ffsid.cli, groups[5], subjects, 8, 10) } -func addSubjectsToNamespaces(t *testing.T, ffsid *testFrostFSIDClientInvoker, namespaces []string, subjects []testSubject) { - cli := ffsid.cli +func createSubjectsInNS(t *testing.T, ffsid *testFrostFSIDClientInvoker, ns string, count int) []testSubject { + subjects := make([]testSubject, count) - tx := cli.StartTx() - for _, subject := range subjects[:3] { - err := tx.WrapCall(cli.AddSubjectToNamespaceCall(subject.addr, namespaces[1])) + tx := ffsid.cli.StartTx() + for i := range subjects { + subjects[i].key, subjects[i].addr = newKey(t) + err := tx.WrapCall(ffsid.cli.CreateSubjectCall(ns, subjects[i].key.PublicKey())) require.NoError(t, err) } - ffsid.a.await(cli.SendTx(tx)) + ffsid.a.await(ffsid.cli.SendTx(tx)) - tx = cli.StartTx() - for _, subject := range subjects[3:5] { - err := tx.WrapCall(cli.AddSubjectToNamespaceCall(subject.addr, namespaces[2])) + return subjects +} + +func createGroupsInNS(t *testing.T, ffsid *testFrostFSIDClientInvoker, ns string, count int) []client.Group { + groups := make([]client.Group, count) + + tx := ffsid.cli.StartTx() + for i := range groups { + groups[i].Namespace = ns + groups[i].Name = ns + "/group" + strconv.Itoa(i+1) + + err := tx.WrapCall(ffsid.cli.CreateGroupCall(ns, groups[i].Name)) require.NoError(t, err) } - ffsid.a.await(cli.SendTx(tx)) + res := ffsid.a.await(ffsid.cli.SendTx(tx)) - tx = cli.StartTx() - for _, subject := range subjects[5:] { - err := tx.WrapCall(cli.AddSubjectToNamespaceCall(subject.addr, namespaces[3])) + ids, err := unwrap.ArrayOfBigInts(makeValidRes(stackitem.NewArray(res.Stack)), nil) + require.NoError(t, err) + + for i, id := range ids { + groups[i].ID = id.Int64() + } + + return groups +} + +func createNamespaces(t *testing.T, ffsid *testFrostFSIDClientInvoker, count int) []string { + namespaces := make([]string, count) + + tx := ffsid.cli.StartTx() + for i := range namespaces { + namespaces[i] = "ns" + strconv.Itoa(i+1) + err := tx.WrapCall(ffsid.cli.CreateNamespaceCall(namespaces[i])) require.NoError(t, err) } - ffsid.a.await(cli.SendTx(tx)) + ffsid.a.await(ffsid.cli.SendTx(tx)) + + return namespaces } func addSubjectsToGroups(t *testing.T, ffsid *testFrostFSIDClientInvoker, groups []client.Group, subjects []testSubject) { @@ -457,14 +455,12 @@ func TestFrostFSID_Client_UseCaseWithS3GW(t *testing.T) { tx := ffsid.cli.StartTx() err := tx.WrapCall(ffsid.cli.CreateNamespaceCall(namespace)) require.NoError(t, err) - err = tx.WrapCall(ffsid.cli.CreateSubjectCall(dataUserKey.PublicKey())) + err = tx.WrapCall(ffsid.cli.CreateSubjectCall(namespace, dataUserKey.PublicKey())) require.NoError(t, err) err = tx.WrapCall(ffsid.cli.SetSubjectNameCall(dataUserAddr, login)) require.NoError(t, err) err = tx.WrapCall(ffsid.cli.AddSubjectKeyCall(dataUserAddr, extraDataUserKey.PublicKey())) require.NoError(t, err) - err = tx.WrapCall(ffsid.cli.AddSubjectToNamespaceCall(dataUserAddr, namespace)) - require.NoError(t, err) ffsid.a.await(ffsid.cli.SendTx(tx)) // s3-gw @@ -497,12 +493,10 @@ func TestFrostFSID_Client_UseCaseListNSSubjects(t *testing.T) { for i := range subjects { tx = ffsid.cli.StartTx() subjects[i].key, subjects[i].addr = newKey(t) - err = tx.WrapCall(ffsid.cli.CreateSubjectCall(subjects[i].key.PublicKey())) + err = tx.WrapCall(ffsid.cli.CreateSubjectCall(namespace, subjects[i].key.PublicKey())) require.NoError(t, err) err = tx.WrapCall(ffsid.cli.SetSubjectNameCall(subjects[i].addr, "login"+strconv.Itoa(i))) require.NoError(t, err) - err = tx.WrapCall(ffsid.cli.AddSubjectToNamespaceCall(subjects[i].addr, namespace)) - require.NoError(t, err) if i > len(subjects)/2 { err = tx.WrapCall(ffsid.cli.AddSubjectToGroupCall(subjects[i].addr, groupID)) diff --git a/tests/frostfsid_test.go b/tests/frostfsid_test.go index d6ec2cc..0e5a38e 100644 --- a/tests/frostfsid_test.go +++ b/tests/frostfsid_test.go @@ -22,6 +22,8 @@ import ( const frostfsidPath = "../frostfsid" +const defaultNamespace = "" + const ( setAdminMethod = "setAdmin" getAdminMethod = "getAdmin" @@ -39,13 +41,11 @@ const ( deleteSubjectKVMethod = "deleteSubjectKV" deleteSubjectMethod = "deleteSubject" - createNamespaceMethod = "createNamespace" - getNamespaceMethod = "getNamespace" - getNamespaceExtendedMethod = "getNamespaceExtended" - listNamespacesMethod = "listNamespaces" - addSubjectToNamespaceMethod = "addSubjectToNamespace" - removeSubjectFromNamespaceMethod = "removeSubjectFromNamespace" - listNamespaceSubjectsMethod = "listNamespaceSubjects" + createNamespaceMethod = "createNamespace" + getNamespaceMethod = "getNamespace" + getNamespaceExtendedMethod = "getNamespaceExtended" + listNamespacesMethod = "listNamespaces" + listNamespaceSubjectsMethod = "listNamespaceSubjects" createGroupMethod = "createGroup" getGroupMethod = "getGroup" @@ -174,6 +174,26 @@ func checkOwner(t *testing.T, invoker *neotest.ContractInvoker, owner ...util.Ui require.Equal(t, bs, owner[0].BytesBE()) } +func TestFrostFSID_DefaultNamespace(t *testing.T) { + f := newFrostFSIDInvoker(t) + + subjKey, err := keys.NewPrivateKey() + require.NoError(t, err) + subjKeyAddr := subjKey.PublicKey().GetScriptHash() + + invoker := f.OwnerInvoker() + + groupID := int64(1) + groupName := "group" + + invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, subjKey.PublicKey().Bytes()) + invoker.Invoke(t, stackitem.Make(groupID), createGroupMethod, defaultNamespace, groupName) + invoker.Invoke(t, stackitem.Null{}, addSubjectToGroupMethod, subjKeyAddr, groupID) + invoker.Invoke(t, stackitem.Null{}, removeSubjectFromGroupMethod, subjKeyAddr, groupID) + invoker.Invoke(t, stackitem.Null{}, deleteGroupMethod, defaultNamespace, groupID) + invoker.Invoke(t, stackitem.Null{}, deleteSubjectMethod, subjKeyAddr) +} + func TestFrostFSID_SubjectManagement(t *testing.T) { f := newFrostFSIDInvoker(t) @@ -184,9 +204,9 @@ func TestFrostFSID_SubjectManagement(t *testing.T) { anonInvoker := f.AnonInvoker(t) invoker := f.OwnerInvoker() - anonInvoker.InvokeFail(t, notWitnessedError, createSubjectMethod, subjKey.PublicKey().Bytes()) - invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, subjKey.PublicKey().Bytes()) - invoker.InvokeFail(t, "already exists", createSubjectMethod, subjKey.PublicKey().Bytes()) + anonInvoker.InvokeFail(t, notWitnessedError, createSubjectMethod, defaultNamespace, subjKey.PublicKey().Bytes()) + invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, subjKey.PublicKey().Bytes()) + invoker.InvokeFail(t, "already exists", createSubjectMethod, defaultNamespace, subjKey.PublicKey().Bytes()) s, err := anonInvoker.TestInvoke(t, getSubjectMethod, subjKeyAddr) require.NoError(t, err) @@ -263,7 +283,7 @@ func TestFrostFSID_SubjectManagement(t *testing.T) { newSubjKey, err := keys.NewPrivateKey() require.NoError(t, err) - invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, newSubjKey.PublicKey().Bytes()) + invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, newSubjKey.PublicKey().Bytes()) s, err = anonInvoker.TestInvoke(t, listSubjectsMethod) require.NoError(t, err) @@ -293,20 +313,22 @@ func TestFrostFSIS_SubjectNameRelatedInvariants(t *testing.T) { require.NoError(t, err) subjKeyAddr2 := subjKey2.PublicKey().GetScriptHash() + subjName3 := "subj3" + subjKey3, err := keys.NewPrivateKey() + require.NoError(t, err) + subjKeyAddr3 := subjKey3.PublicKey().GetScriptHash() + invoker := f.OwnerInvoker() ns1, ns2 := "ns1", "ns2" - // Create two subject (one of them with name) - // Create two namespace. - // Add these subjects to ns1 - invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, subjKey1.PublicKey().Bytes()) - invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjKeyAddr1, subjName1) - invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, subjKey2.PublicKey().Bytes()) invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, ns1) invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, ns2) - invoker.Invoke(t, stackitem.Null{}, addSubjectToNamespaceMethod, subjKeyAddr1, ns1) - invoker.Invoke(t, stackitem.Null{}, addSubjectToNamespaceMethod, subjKeyAddr2, ns1) + invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, ns1, subjKey1.PublicKey().Bytes()) + invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjKeyAddr1, subjName1) + invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, ns1, subjKey2.PublicKey().Bytes()) + invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, ns2, subjKey3.PublicKey().Bytes()) + invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjKeyAddr3, subjName3) // Check that we can find public key by name for subj1 (with name) // and cannot find key for subj2 (without name) @@ -323,25 +345,17 @@ func TestFrostFSIS_SubjectNameRelatedInvariants(t *testing.T) { // Check that we cannot set for second subject name that the first subject has already taken invoker.InvokeFail(t, "not available", setSubjectNameMethod, subjKeyAddr2, subjName1) - // Check that we cannot move subject from one namespace to another - invoker.InvokeFail(t, "cannot be moved", addSubjectToNamespaceMethod, subjKeyAddr2, ns2) - - // Check that we cannot find public key by name for subject that was removed from namespace - invoker.Invoke(t, stackitem.Null{}, removeSubjectFromNamespaceMethod, subjKeyAddr2) + // Check that we cannot find public key by name for subject that was removed + invoker.Invoke(t, stackitem.Null{}, deleteSubjectMethod, subjKeyAddr2) s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns1, subjName2) checkPublicKeyResult(t, s, err, nil) - // Check that we can find public key by name for subject in new namespace - invoker.Invoke(t, stackitem.Null{}, addSubjectToNamespaceMethod, subjKeyAddr2, ns2) - s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns2, subjName2) - checkPublicKeyResult(t, s, err, subjKey2) - - // Check that subj2 can have the same name as subj1 if they belong to different namespaces + // Check that subj3 can have the same name as subj1 if they belong to different namespaces // Also check that after subject renaming its key cannot be found by old name - invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjKeyAddr2, subjName1) + invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjKeyAddr3, subjName1) s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns2, subjName1) - checkPublicKeyResult(t, s, err, subjKey2) - s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns2, subjName2) + checkPublicKeyResult(t, s, err, subjKey3) + s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns2, subjName3) checkPublicKeyResult(t, s, err, nil) } @@ -414,16 +428,12 @@ func TestFrostFSID_NamespaceManagement(t *testing.T) { t.Run("add user to namespace", func(t *testing.T) { subjKey, err := keys.NewPrivateKey() require.NoError(t, err) - invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, subjKey.PublicKey().Bytes()) + invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, ns.Name, subjKey.PublicKey().Bytes()) subjName := "name" subjAddress := subjKey.PublicKey().GetScriptHash() invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjAddress, subjName) - anonInvoker.InvokeFail(t, notWitnessedError, addSubjectToNamespaceMethod, subjAddress, namespace) - invoker.Invoke(t, stackitem.Null{}, addSubjectToNamespaceMethod, subjAddress, namespace) - invoker.InvokeFail(t, "already added", addSubjectToNamespaceMethod, subjAddress, namespace) - s, err := anonInvoker.TestInvoke(t, getSubjectMethod, subjAddress) require.NoError(t, err) subj := parseSubject(t, s) @@ -456,7 +466,7 @@ func TestFrostFSID_NamespaceManagement(t *testing.T) { namespaces := parseNamespaces(t, readIteratorAll(s)) require.NoError(t, err) - require.ElementsMatch(t, namespaces, []Namespace{{Name: namespace}, {Name: namespace2}}) + require.ElementsMatch(t, namespaces, []Namespace{{Name: defaultNamespace}, {Name: namespace}, {Name: namespace2}}) t.Run("find namespaces with some subjects", func(t *testing.T) { for _, ns := range namespaces { @@ -468,21 +478,6 @@ func TestFrostFSID_NamespaceManagement(t *testing.T) { require.Equal(t, namespace, nsExt.Name) } } - - t.Run("remove subject from namespace", func(t *testing.T) { - anonInvoker.InvokeFail(t, notWitnessedError, removeSubjectFromNamespaceMethod, subjAddress) - invoker.Invoke(t, stackitem.Null{}, removeSubjectFromNamespaceMethod, subjAddress) - - s, err := anonInvoker.TestInvoke(t, getSubjectMethod, subjAddress) - require.NoError(t, err) - subj := parseSubject(t, s) - require.Empty(t, subj.Namespace) - - s, err = anonInvoker.TestInvoke(t, getNamespaceExtendedMethod, namespace) - require.NoError(t, err) - nsExt := parseNamespaceExtended(t, s.Pop().Item()) - require.Zero(t, nsExt.SubjectsCount) - }) }) }) }) @@ -516,10 +511,9 @@ func TestFrostFSID_GroupManagement(t *testing.T) { t.Run("add subjects to group", func(t *testing.T) { subjKey, err := keys.NewPrivateKey() require.NoError(t, err) - invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, subjKey.PublicKey().Bytes()) + invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, nsName, subjKey.PublicKey().Bytes()) subjAddress := subjKey.PublicKey().GetScriptHash() - invoker.Invoke(t, stackitem.Null{}, addSubjectToNamespaceMethod, subjAddress, nsName) anonInvoker.InvokeFail(t, "not witnessed", addSubjectToGroupMethod, subjAddress, groupID) invoker.Invoke(t, stackitem.Null{}, addSubjectToGroupMethod, subjAddress, groupID) @@ -546,8 +540,7 @@ func TestFrostFSID_GroupManagement(t *testing.T) { for i := 0; i < subjectsCount; i++ { subjKey, err := keys.NewPrivateKey() require.NoError(t, err) - invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, subjKey.PublicKey().Bytes()) - invoker.Invoke(t, stackitem.Null{}, addSubjectToNamespaceMethod, subjKey.PublicKey().GetScriptHash(), nsName) + invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, nsName, subjKey.PublicKey().Bytes()) invoker.Invoke(t, stackitem.Null{}, addSubjectToGroupMethod, subjKey.PublicKey().GetScriptHash(), groupID) }