forked from TrueCloudLab/frostfs-contract
[#48] frostfsid: Add tests
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
This commit is contained in:
parent
c8b14d1376
commit
5cc810096f
1 changed files with 677 additions and 72 deletions
|
@ -1,110 +1,715 @@
|
||||||
package tests
|
package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"errors"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/container"
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
const frostfsidPath = "../frostfsid"
|
const frostfsidPath = "../frostfsid"
|
||||||
|
|
||||||
func deployFrostFSIDContract(t *testing.T, e *neotest.Executor, addrNetmap, addrContainer util.Uint160) util.Uint160 {
|
const (
|
||||||
args := make([]any, 2)
|
addOwnerMethod = "addOwner"
|
||||||
args[0] = addrNetmap
|
deleteOwnerMethod = "deleteOwner"
|
||||||
args[1] = addrContainer
|
listOwnersMethod = "listOwners"
|
||||||
|
|
||||||
|
createSubjectMethod = "createSubject"
|
||||||
|
getSubjectMethod = "getSubject"
|
||||||
|
listSubjectsMethod = "listSubjects"
|
||||||
|
addSubjectKeyMethod = "addSubjectKey"
|
||||||
|
removeSubjectKeyMethod = "removeSubjectKey"
|
||||||
|
getSubjectByKeyMethod = "getSubjectByKey"
|
||||||
|
getSubjectKeyByNameMethod = "getSubjectKeyByName"
|
||||||
|
setSubjectNameMethod = "setSubjectName"
|
||||||
|
setSubjectKVMethod = "setSubjectKV"
|
||||||
|
deleteSubjectKVMethod = "deleteSubjectKV"
|
||||||
|
deleteSubjectMethod = "deleteSubject"
|
||||||
|
|
||||||
|
createNamespaceMethod = "createNamespace"
|
||||||
|
getNamespaceMethod = "getNamespace"
|
||||||
|
getNamespaceExtendedMethod = "getNamespaceExtended"
|
||||||
|
listNamespacesMethod = "listNamespaces"
|
||||||
|
addSubjectToNamespaceMethod = "addSubjectToNamespace"
|
||||||
|
removeSubjectFromNamespaceMethod = "removeSubjectFromNamespace"
|
||||||
|
listNamespaceSubjectsMethod = "listNamespaceSubjects"
|
||||||
|
|
||||||
|
createGroupMethod = "createGroup"
|
||||||
|
getGroupMethod = "getGroup"
|
||||||
|
getGroupExtendedMethod = "getGroupExtended"
|
||||||
|
listGroupsMethod = "listGroups"
|
||||||
|
addSubjectToGroupMethod = "addSubjectToGroup"
|
||||||
|
removeSubjectFromGroupMethod = "removeSubjectFromGroup"
|
||||||
|
listGroupSubjectsMethod = "listGroupSubjects"
|
||||||
|
deleteGroupMethod = "deleteGroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
const notWitnessedError = "not witnessed"
|
||||||
|
|
||||||
|
type testFrostFSIDInvoker struct {
|
||||||
|
e *neotest.Executor
|
||||||
|
contractHash util.Uint160
|
||||||
|
owner *wallet.Account
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *testFrostFSIDInvoker) OwnerInvoker() *neotest.ContractInvoker {
|
||||||
|
return f.e.NewInvoker(f.contractHash, neotest.NewSingleSigner(f.owner))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *testFrostFSIDInvoker) CommitteeInvoker() *neotest.ContractInvoker {
|
||||||
|
return f.e.CommitteeInvoker(f.contractHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *testFrostFSIDInvoker) AnonInvoker(t *testing.T) *neotest.ContractInvoker {
|
||||||
|
acc, err := wallet.NewAccount()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return f.e.NewInvoker(f.contractHash, newSigner(t, f.CommitteeInvoker(), acc))
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSigner(t *testing.T, c *neotest.ContractInvoker, acc *wallet.Account) neotest.Signer {
|
||||||
|
amount := int64(100_0000_0000)
|
||||||
|
|
||||||
|
tx := c.NewTx(t, []neotest.Signer{c.Validator},
|
||||||
|
c.NativeHash(t, nativenames.Gas), "transfer",
|
||||||
|
c.Validator.ScriptHash(), acc.Contract.ScriptHash(), amount, nil)
|
||||||
|
c.AddNewBlock(t, tx)
|
||||||
|
c.CheckHalt(t, tx.Hash())
|
||||||
|
return neotest.NewSingleSigner(acc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deployFrostFSIDContract(t *testing.T, e *neotest.Executor, contractOwner util.Uint160) util.Uint160 {
|
||||||
|
args := make([]any, 5)
|
||||||
|
args[0] = []any{contractOwner}
|
||||||
|
|
||||||
c := neotest.CompileFile(t, e.CommitteeHash, frostfsidPath, path.Join(frostfsidPath, "config.yml"))
|
c := neotest.CompileFile(t, e.CommitteeHash, frostfsidPath, path.Join(frostfsidPath, "config.yml"))
|
||||||
e.DeployContract(t, c, args)
|
e.DeployContract(t, c, args)
|
||||||
return c.Hash
|
return c.Hash
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFrostFSIDInvoker(t *testing.T) *neotest.ContractInvoker {
|
func newFrostFSIDInvoker(t *testing.T) *testFrostFSIDInvoker {
|
||||||
e := newExecutor(t)
|
e := newExecutor(t)
|
||||||
|
|
||||||
ctrNNS := neotest.CompileFile(t, e.CommitteeHash, nnsPath, path.Join(nnsPath, "config.yml"))
|
acc, err := wallet.NewAccount()
|
||||||
ctrNetmap := neotest.CompileFile(t, e.CommitteeHash, netmapPath, path.Join(netmapPath, "config.yml"))
|
|
||||||
ctrBalance := neotest.CompileFile(t, e.CommitteeHash, balancePath, path.Join(balancePath, "config.yml"))
|
|
||||||
ctrContainer := neotest.CompileFile(t, e.CommitteeHash, containerPath, path.Join(containerPath, "config.yml"))
|
|
||||||
|
|
||||||
e.DeployContract(t, ctrNNS, nil)
|
|
||||||
deployNetmapContract(t, e, ctrBalance.Hash, ctrContainer.Hash,
|
|
||||||
container.RegistrationFeeKey, int64(containerFee),
|
|
||||||
container.AliasFeeKey, int64(containerAliasFee))
|
|
||||||
deployBalanceContract(t, e, ctrNetmap.Hash, ctrContainer.Hash)
|
|
||||||
deployContainerContract(t, e, ctrNetmap.Hash, ctrBalance.Hash, ctrNNS.Hash)
|
|
||||||
h := deployFrostFSIDContract(t, e, ctrNetmap.Hash, ctrContainer.Hash)
|
|
||||||
return e.CommitteeInvoker(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFrostFSID_AddKey(t *testing.T) {
|
|
||||||
e := newFrostFSIDInvoker(t)
|
|
||||||
|
|
||||||
pubs := make([][]byte, 6)
|
|
||||||
for i := range pubs {
|
|
||||||
p, err := keys.NewPrivateKey()
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
pubs[i] = p.PublicKey().Bytes()
|
|
||||||
}
|
|
||||||
acc := e.NewAccount(t)
|
|
||||||
owner := signerToOwner(acc)
|
|
||||||
e.Invoke(t, stackitem.Null{}, "addKey", owner,
|
|
||||||
[]any{pubs[0], pubs[1]})
|
|
||||||
|
|
||||||
sort.Slice(pubs[:2], func(i, j int) bool {
|
h := deployFrostFSIDContract(t, e, acc.ScriptHash())
|
||||||
return bytes.Compare(pubs[i], pubs[j]) == -1
|
|
||||||
|
newSigner(t, e.CommitteeInvoker(h), acc)
|
||||||
|
|
||||||
|
return &testFrostFSIDInvoker{
|
||||||
|
e: e,
|
||||||
|
contractHash: h,
|
||||||
|
owner: acc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFrostFSID_ContractOwnersManagement(t *testing.T) {
|
||||||
|
f := newFrostFSIDInvoker(t)
|
||||||
|
|
||||||
|
anonInvoker := f.AnonInvoker(t)
|
||||||
|
anonInvokerHash := anonInvoker.Signers[0].ScriptHash()
|
||||||
|
invoker := f.OwnerInvoker()
|
||||||
|
invokerHash := invoker.Signers[0].ScriptHash()
|
||||||
|
committeeInvoker := f.CommitteeInvoker()
|
||||||
|
|
||||||
|
checkListOwners(t, anonInvoker, invokerHash)
|
||||||
|
|
||||||
|
anonInvoker.InvokeFail(t, notWitnessedError, createNamespaceMethod, "namespace")
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, "namespace")
|
||||||
|
|
||||||
|
invoker.InvokeFail(t, notWitnessedError, addOwnerMethod, anonInvokerHash)
|
||||||
|
committeeInvoker.Invoke(t, stackitem.Null{}, addOwnerMethod, anonInvokerHash)
|
||||||
|
anonInvoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, "namespace2")
|
||||||
|
|
||||||
|
checkListOwners(t, anonInvoker, invokerHash, anonInvokerHash)
|
||||||
|
|
||||||
|
anonInvoker.InvokeFail(t, notWitnessedError, deleteOwnerMethod, anonInvokerHash)
|
||||||
|
committeeInvoker.Invoke(t, stackitem.Null{}, deleteOwnerMethod, anonInvokerHash)
|
||||||
|
anonInvoker.InvokeFail(t, notWitnessedError, createNamespaceMethod, "namespace3")
|
||||||
|
|
||||||
|
checkListOwners(t, anonInvoker, invokerHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkListOwners(t *testing.T, invoker *neotest.ContractInvoker, expectedAddresses ...util.Uint160) {
|
||||||
|
s, err := invoker.TestInvoke(t, listOwnersMethod)
|
||||||
|
require.NoError(t, err)
|
||||||
|
addresses, err := unwrap.ArrayOfUint160(makeValidRes(stackitem.NewArray(readIteratorAll(s))), nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.ElementsMatch(t, addresses, expectedAddresses)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFrostFSID_SubjectManagement(t *testing.T) {
|
||||||
|
f := newFrostFSIDInvoker(t)
|
||||||
|
|
||||||
|
subjKey, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
subjKeyAddr := subjKey.PublicKey().GetScriptHash()
|
||||||
|
|
||||||
|
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())
|
||||||
|
|
||||||
|
s, err := anonInvoker.TestInvoke(t, getSubjectMethod, subjKeyAddr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
subj := parseSubject(t, s)
|
||||||
|
require.True(t, subjKey.PublicKey().Equal(&subj.PrimaryKey))
|
||||||
|
|
||||||
|
t.Run("add subject key", func(t *testing.T) {
|
||||||
|
subjNewKey, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
anonInvoker.InvokeFail(t, notWitnessedError, addSubjectKeyMethod, subjKeyAddr, subjNewKey.PublicKey().Bytes())
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, addSubjectKeyMethod, subjKeyAddr, subjNewKey.PublicKey().Bytes())
|
||||||
|
|
||||||
|
s, err = anonInvoker.TestInvoke(t, getSubjectMethod, subjKeyAddr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
subj := parseSubject(t, s)
|
||||||
|
require.Len(t, subj.AdditionalKeys, 1)
|
||||||
|
require.True(t, subjNewKey.PublicKey().Equal(subj.AdditionalKeys[0]))
|
||||||
|
|
||||||
|
t.Run("get subject by additional key", func(t *testing.T) {
|
||||||
|
s, err = anonInvoker.TestInvoke(t, getSubjectByKeyMethod, subjNewKey.PublicKey().Bytes())
|
||||||
|
require.NoError(t, err)
|
||||||
|
subj := parseSubject(t, s)
|
||||||
|
require.True(t, subjKey.PublicKey().Equal(&subj.PrimaryKey), "keys must be the same")
|
||||||
|
|
||||||
|
s, err = anonInvoker.TestInvoke(t, getSubjectByKeyMethod, subjKey.PublicKey().Bytes())
|
||||||
|
require.NoError(t, err)
|
||||||
|
subj = parseSubject(t, s)
|
||||||
|
require.True(t, subjKey.PublicKey().Equal(&subj.PrimaryKey), "keys must be the same")
|
||||||
|
|
||||||
|
t.Run("remove subject key", func(t *testing.T) {
|
||||||
|
anonInvoker.InvokeFail(t, notWitnessedError, removeSubjectKeyMethod, subjKeyAddr, subjNewKey.PublicKey().Bytes())
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, removeSubjectKeyMethod, subjKeyAddr, subjNewKey.PublicKey().Bytes())
|
||||||
|
|
||||||
|
anonInvoker.InvokeFail(t, "not found", getSubjectByKeyMethod, subjNewKey.PublicKey().Bytes())
|
||||||
})
|
})
|
||||||
arr := []stackitem.Item{
|
|
||||||
stackitem.NewBuffer(pubs[0]),
|
|
||||||
stackitem.NewBuffer(pubs[1]),
|
|
||||||
}
|
|
||||||
e.Invoke(t, stackitem.NewArray(arr), "key", owner)
|
|
||||||
|
|
||||||
t.Run("multiple addKey per block", func(t *testing.T) {
|
|
||||||
tx1 := e.PrepareInvoke(t, "addKey", owner, []any{pubs[2]})
|
|
||||||
tx2 := e.PrepareInvoke(t, "addKey", owner, []any{pubs[3], pubs[4]})
|
|
||||||
e.AddNewBlock(t, tx1, tx2)
|
|
||||||
e.CheckHalt(t, tx1.Hash(), stackitem.Null{})
|
|
||||||
e.CheckHalt(t, tx2.Hash(), stackitem.Null{})
|
|
||||||
|
|
||||||
sort.Slice(pubs[:5], func(i, j int) bool {
|
|
||||||
return bytes.Compare(pubs[i], pubs[j]) == -1
|
|
||||||
})
|
})
|
||||||
arr = []stackitem.Item{
|
|
||||||
stackitem.NewBuffer(pubs[0]),
|
|
||||||
stackitem.NewBuffer(pubs[1]),
|
|
||||||
stackitem.NewBuffer(pubs[2]),
|
|
||||||
stackitem.NewBuffer(pubs[3]),
|
|
||||||
stackitem.NewBuffer(pubs[4]),
|
|
||||||
}
|
|
||||||
e.Invoke(t, stackitem.NewArray(arr), "key", owner)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
e.Invoke(t, stackitem.Null{}, "removeKey", owner,
|
t.Run("set subject name", func(t *testing.T) {
|
||||||
[]any{pubs[1], pubs[5]})
|
login := "some-login"
|
||||||
arr = []stackitem.Item{
|
|
||||||
stackitem.NewBuffer(pubs[0]),
|
anonInvoker.InvokeFail(t, notWitnessedError, setSubjectNameMethod, subjKeyAddr, login)
|
||||||
stackitem.NewBuffer(pubs[2]),
|
invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjKeyAddr, login)
|
||||||
stackitem.NewBuffer(pubs[3]),
|
|
||||||
stackitem.NewBuffer(pubs[4]),
|
s, err = anonInvoker.TestInvoke(t, getSubjectMethod, subjKeyAddr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
subj = parseSubject(t, s)
|
||||||
|
require.Equal(t, login, subj.Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("set subject KVs", func(t *testing.T) {
|
||||||
|
iamPath := "iam/path"
|
||||||
|
|
||||||
|
anonInvoker.InvokeFail(t, notWitnessedError, setSubjectKVMethod, subjKeyAddr, client.SubjectIAMPathKey, iamPath)
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, setSubjectKVMethod, subjKeyAddr, client.SubjectIAMPathKey, iamPath)
|
||||||
|
|
||||||
|
s, err = anonInvoker.TestInvoke(t, getSubjectMethod, subjKeyAddr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
subj = parseSubject(t, s)
|
||||||
|
require.Equal(t, iamPath, subj.KV[client.SubjectIAMPathKey])
|
||||||
|
|
||||||
|
anonInvoker.InvokeFail(t, notWitnessedError, deleteSubjectKVMethod, subjKeyAddr, client.SubjectIAMPathKey)
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, deleteSubjectKVMethod, subjKeyAddr, client.SubjectIAMPathKey)
|
||||||
|
|
||||||
|
s, err = anonInvoker.TestInvoke(t, getSubjectMethod, subjKeyAddr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
subj = parseSubject(t, s)
|
||||||
|
require.Empty(t, subj.KV)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("list subjects", func(t *testing.T) {
|
||||||
|
newSubjKey, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, newSubjKey.PublicKey().Bytes())
|
||||||
|
|
||||||
|
s, err = anonInvoker.TestInvoke(t, listSubjectsMethod)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
addresses, err := unwrap.ArrayOfUint160(makeValidRes(stackitem.NewArray(readIteratorAll(s))), nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, addresses, 2)
|
||||||
|
require.ElementsMatch(t, addresses, []util.Uint160{subjKeyAddr, newSubjKey.PublicKey().GetScriptHash()})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
anonInvoker.InvokeFail(t, notWitnessedError, deleteSubjectMethod, subjKeyAddr)
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, deleteSubjectMethod, subjKeyAddr)
|
||||||
|
|
||||||
|
anonInvoker.InvokeFail(t, "subject not found", getSubjectMethod, subjKeyAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFrostFSIS_SubjectNameRelatedInvariants(t *testing.T) {
|
||||||
|
f := newFrostFSIDInvoker(t)
|
||||||
|
|
||||||
|
subjName1 := "subj1"
|
||||||
|
subjKey1, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
subjKeyAddr1 := subjKey1.PublicKey().GetScriptHash()
|
||||||
|
|
||||||
|
subjName2 := "subj2"
|
||||||
|
subjKey2, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
subjKeyAddr2 := subjKey2.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)
|
||||||
|
|
||||||
|
// Check that we can find public key by name for subj1 (with name)
|
||||||
|
// and cannot find key for subj2 (without name)
|
||||||
|
s, err := invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns1, subjName1)
|
||||||
|
checkPublicKeyResult(t, s, err, subjKey1)
|
||||||
|
s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns1, subjName2)
|
||||||
|
checkPublicKeyResult(t, s, err, nil)
|
||||||
|
|
||||||
|
// Check that we can find public key for by name for subj2 when we set its name
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjKeyAddr2, subjName2)
|
||||||
|
s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns1, subjName2)
|
||||||
|
checkPublicKeyResult(t, s, err, subjKey2)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
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
|
||||||
|
// Also check that after subject renaming its key cannot be found by old name
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjKeyAddr2, 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, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFrostFSID_NamespaceManagement(t *testing.T) {
|
||||||
|
f := newFrostFSIDInvoker(t)
|
||||||
|
|
||||||
|
anonInvoker := f.AnonInvoker(t)
|
||||||
|
invoker := f.OwnerInvoker()
|
||||||
|
|
||||||
|
namespace := "some-namespace"
|
||||||
|
|
||||||
|
anonInvoker.InvokeFail(t, notWitnessedError, createNamespaceMethod, namespace)
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, namespace)
|
||||||
|
invoker.InvokeFail(t, "already exists", createNamespaceMethod, namespace)
|
||||||
|
|
||||||
|
s, err := anonInvoker.TestInvoke(t, getNamespaceMethod, namespace)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ns := parseNamespace(t, s.Pop().Item())
|
||||||
|
require.Equal(t, namespace, ns.Name)
|
||||||
|
|
||||||
|
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())
|
||||||
|
|
||||||
|
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)
|
||||||
|
require.Equal(t, namespace, subj.Namespace)
|
||||||
|
|
||||||
|
t.Run("list namespace subjects", func(t *testing.T) {
|
||||||
|
s, err := anonInvoker.TestInvoke(t, listNamespaceSubjectsMethod, namespace)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
addresses, err := unwrap.ArrayOfUint160(makeValidRes(stackitem.NewArray(readIteratorAll(s))), nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.ElementsMatch(t, addresses, []util.Uint160{subjAddress})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("get subject key by name", func(t *testing.T) {
|
||||||
|
s, err := anonInvoker.TestInvoke(t, getSubjectKeyByNameMethod, namespace, subjName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
foundKey, err := unwrap.PublicKey(makeValidRes(s.Pop().Item()), nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, subjKey.PublicKey(), foundKey)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("list namespaces", func(t *testing.T) {
|
||||||
|
namespace2 := "some-namespace2"
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, namespace2)
|
||||||
|
|
||||||
|
s, err := anonInvoker.TestInvoke(t, listNamespacesMethod)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
namespaces := parseNamespaces(t, readIteratorAll(s))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.ElementsMatch(t, namespaces, []Namespace{{Name: namespace}, {Name: namespace2}})
|
||||||
|
|
||||||
|
t.Run("find namespaces with some subjects", func(t *testing.T) {
|
||||||
|
for _, ns := range namespaces {
|
||||||
|
s, err := anonInvoker.TestInvoke(t, getNamespaceExtendedMethod, ns.Name)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
nsExt := parseNamespaceExtended(t, s.Pop().Item())
|
||||||
|
if nsExt.SubjectsCount > 0 {
|
||||||
|
require.Equal(t, namespace, nsExt.Name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
e.Invoke(t, stackitem.NewArray(arr), "key", owner)
|
|
||||||
|
|
||||||
t.Run("multiple removeKey per block", func(t *testing.T) {
|
t.Run("remove subject from namespace", func(t *testing.T) {
|
||||||
tx1 := e.PrepareInvoke(t, "removeKey", owner, []any{pubs[2]})
|
anonInvoker.InvokeFail(t, notWitnessedError, removeSubjectFromNamespaceMethod, subjAddress)
|
||||||
tx2 := e.PrepareInvoke(t, "removeKey", owner, []any{pubs[0], pubs[4]})
|
invoker.Invoke(t, stackitem.Null{}, removeSubjectFromNamespaceMethod, subjAddress)
|
||||||
e.AddNewBlock(t, tx1, tx2)
|
|
||||||
e.CheckHalt(t, tx1.Hash(), stackitem.Null{})
|
|
||||||
e.CheckHalt(t, tx2.Hash(), stackitem.Null{})
|
|
||||||
|
|
||||||
arr = []stackitem.Item{stackitem.NewBuffer(pubs[3])}
|
s, err := anonInvoker.TestInvoke(t, getSubjectMethod, subjAddress)
|
||||||
e.Invoke(t, stackitem.NewArray(arr), "key", owner)
|
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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFrostFSID_GroupManagement(t *testing.T) {
|
||||||
|
f := newFrostFSIDInvoker(t)
|
||||||
|
|
||||||
|
anonInvoker := f.AnonInvoker(t)
|
||||||
|
invoker := f.OwnerInvoker()
|
||||||
|
|
||||||
|
nsName := "namespace"
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, nsName)
|
||||||
|
|
||||||
|
groupName := "group"
|
||||||
|
anonInvoker.InvokeFail(t, notWitnessedError, createGroupMethod, nsName, groupName)
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, createGroupMethod, nsName, groupName)
|
||||||
|
|
||||||
|
s, err := anonInvoker.TestInvoke(t, getGroupMethod, nsName, groupName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
group := parseGroup(t, s.Pop().Item())
|
||||||
|
require.Equal(t, groupName, group.Name)
|
||||||
|
|
||||||
|
s, err = anonInvoker.TestInvoke(t, listGroupsMethod, nsName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
groups := parseGroups(t, readIteratorAll(s))
|
||||||
|
require.ElementsMatch(t, groups, []Group{{Name: groupName, Namespace: nsName}})
|
||||||
|
|
||||||
|
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())
|
||||||
|
|
||||||
|
subjAddress := subjKey.PublicKey().GetScriptHash()
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, addSubjectToNamespaceMethod, subjAddress, nsName)
|
||||||
|
anonInvoker.InvokeFail(t, "not witnessed", addSubjectToGroupMethod, subjAddress, groupName)
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, addSubjectToGroupMethod, subjAddress, groupName)
|
||||||
|
|
||||||
|
t.Run("list group subjects", func(t *testing.T) {
|
||||||
|
s, err = anonInvoker.TestInvoke(t, listGroupSubjectsMethod, nsName, groupName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
addresses, err := unwrap.ArrayOfUint160(makeValidRes(stackitem.NewArray(readIteratorAll(s))), nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.ElementsMatch(t, addresses, []util.Uint160{subjAddress})
|
||||||
|
|
||||||
|
anonInvoker.InvokeFail(t, "not witnessed", removeSubjectFromGroupMethod, subjAddress, nsName, groupName)
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, removeSubjectFromGroupMethod, subjAddress, nsName, groupName)
|
||||||
|
|
||||||
|
s, err = anonInvoker.TestInvoke(t, listGroupSubjectsMethod, nsName, groupName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
addresses, err = unwrap.ArrayOfUint160(makeValidRes(stackitem.NewArray(readIteratorAll(s))), nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Empty(t, addresses)
|
||||||
|
|
||||||
|
t.Run("get group extended", func(t *testing.T) {
|
||||||
|
subjectsCount := 10
|
||||||
|
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{}, addSubjectToGroupMethod, subjKey.PublicKey().GetScriptHash(), groupName)
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err = anonInvoker.TestInvoke(t, getGroupExtendedMethod, nsName, groupName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
groupExt := parseGroupExtended(t, s.Pop().Item())
|
||||||
|
require.Equal(t, groupName, groupExt.Name)
|
||||||
|
require.EqualValues(t, subjectsCount, groupExt.SubjectsCount)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("delete group", func(t *testing.T) {
|
||||||
|
anonInvoker.InvokeFail(t, notWitnessedError, deleteGroupMethod, nsName, groupName)
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, deleteGroupMethod, nsName, groupName)
|
||||||
|
|
||||||
|
anonInvoker.InvokeFail(t, "group not found", getGroupMethod, nsName, groupName)
|
||||||
|
|
||||||
|
s, err = anonInvoker.TestInvoke(t, listGroupsMethod, nsName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
groups := parseGroups(t, readIteratorAll(s))
|
||||||
|
require.Empty(t, groups)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPublicKeyResult(t *testing.T, s *vm.Stack, err error, key *keys.PrivateKey) {
|
||||||
|
if key == nil {
|
||||||
|
require.ErrorContains(t, err, "not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
foundKey, err := unwrap.PublicKey(makeValidRes(s.Pop().Item()), nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, key.PublicKey(), foundKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readIteratorAll(s *vm.Stack) []stackitem.Item {
|
||||||
|
iter := s.Pop().Value().(*storage.Iterator)
|
||||||
|
|
||||||
|
stackItems := make([]stackitem.Item, 0)
|
||||||
|
for iter.Next() {
|
||||||
|
stackItems = append(stackItems, iter.Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
return stackItems
|
||||||
|
}
|
||||||
|
|
||||||
|
type Subject struct {
|
||||||
|
PrimaryKey keys.PublicKey
|
||||||
|
AdditionalKeys keys.PublicKeys
|
||||||
|
Namespace string
|
||||||
|
Name string
|
||||||
|
KV map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSubject(t *testing.T, s *vm.Stack) Subject {
|
||||||
|
var subj Subject
|
||||||
|
|
||||||
|
subjStruct := s.Pop().Array()
|
||||||
|
require.Len(t, subjStruct, 5)
|
||||||
|
|
||||||
|
pkBytes, err := subjStruct[0].TryBytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = subj.PrimaryKey.DecodeBytes(pkBytes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if !subjStruct[1].Equals(stackitem.Null{}) {
|
||||||
|
subj.AdditionalKeys, err = unwrap.ArrayOfPublicKeys(makeValidRes(subjStruct[1]), nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nsBytes, err := subjStruct[2].TryBytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
subj.Namespace = string(nsBytes)
|
||||||
|
|
||||||
|
nameBytes, err := subjStruct[3].TryBytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
subj.Name = string(nameBytes)
|
||||||
|
|
||||||
|
subj.KV, err = parseMap(subjStruct[4])
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return subj
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseMap(item stackitem.Item) (map[string]string, error) {
|
||||||
|
if item.Equals(stackitem.Null{}) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
metaMap, err := unwrap.Map(makeValidRes(item), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
meta, ok := metaMap.Value().([]stackitem.MapElement)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("invalid map type")
|
||||||
|
}
|
||||||
|
|
||||||
|
res := make(map[string]string, len(meta))
|
||||||
|
for _, element := range meta {
|
||||||
|
key, err := element.Key.TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
val, err := element.Value.TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res[string(key)] = string(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Namespace struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type NamespaceExtended struct {
|
||||||
|
Name string
|
||||||
|
SubjectsCount int64
|
||||||
|
GroupsCount int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseNamespace(t *testing.T, item stackitem.Item) Namespace {
|
||||||
|
var ns Namespace
|
||||||
|
|
||||||
|
subjStruct := item.Value().([]stackitem.Item)
|
||||||
|
require.Len(t, subjStruct, 1)
|
||||||
|
|
||||||
|
nameBytes, err := subjStruct[0].TryBytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
ns.Name = string(nameBytes)
|
||||||
|
|
||||||
|
return ns
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseNamespaceExtended(t *testing.T, item stackitem.Item) NamespaceExtended {
|
||||||
|
var ns NamespaceExtended
|
||||||
|
|
||||||
|
subjStruct := item.Value().([]stackitem.Item)
|
||||||
|
require.Len(t, subjStruct, 3)
|
||||||
|
|
||||||
|
nameBytes, err := subjStruct[0].TryBytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
ns.Name = string(nameBytes)
|
||||||
|
|
||||||
|
groupCountInt, err := subjStruct[1].TryInteger()
|
||||||
|
require.NoError(t, err)
|
||||||
|
ns.GroupsCount = groupCountInt.Int64()
|
||||||
|
|
||||||
|
subjectsCountInt, err := subjStruct[2].TryInteger()
|
||||||
|
require.NoError(t, err)
|
||||||
|
ns.SubjectsCount = subjectsCountInt.Int64()
|
||||||
|
|
||||||
|
return ns
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseNamespaces(t *testing.T, items []stackitem.Item) []Namespace {
|
||||||
|
res := make([]Namespace, len(items))
|
||||||
|
|
||||||
|
for i := 0; i < len(items); i++ {
|
||||||
|
res[i] = parseNamespace(t, items[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
type Group struct {
|
||||||
|
Name string
|
||||||
|
Namespace string
|
||||||
|
}
|
||||||
|
|
||||||
|
type GroupExtended struct {
|
||||||
|
Name string
|
||||||
|
Namespace string
|
||||||
|
SubjectsCount int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseGroup(t *testing.T, item stackitem.Item) Group {
|
||||||
|
var group Group
|
||||||
|
|
||||||
|
subjStruct := item.Value().([]stackitem.Item)
|
||||||
|
require.Len(t, subjStruct, 2)
|
||||||
|
|
||||||
|
nameBytes, err := subjStruct[0].TryBytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
group.Name = string(nameBytes)
|
||||||
|
|
||||||
|
namespaceBytes, err := subjStruct[1].TryBytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
group.Namespace = string(namespaceBytes)
|
||||||
|
|
||||||
|
return group
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseGroupExtended(t *testing.T, item stackitem.Item) GroupExtended {
|
||||||
|
var gr GroupExtended
|
||||||
|
|
||||||
|
subjStruct := item.Value().([]stackitem.Item)
|
||||||
|
require.Len(t, subjStruct, 3)
|
||||||
|
|
||||||
|
nameBytes, err := subjStruct[0].TryBytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
gr.Name = string(nameBytes)
|
||||||
|
|
||||||
|
namespaceBytes, err := subjStruct[1].TryBytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
gr.Namespace = string(namespaceBytes)
|
||||||
|
|
||||||
|
subjectsCountInt, err := subjStruct[2].TryInteger()
|
||||||
|
require.NoError(t, err)
|
||||||
|
gr.SubjectsCount = subjectsCountInt.Int64()
|
||||||
|
|
||||||
|
return gr
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseGroups(t *testing.T, items []stackitem.Item) []Group {
|
||||||
|
res := make([]Group, len(items))
|
||||||
|
|
||||||
|
for i := 0; i < len(items); i++ {
|
||||||
|
res[i] = parseGroup(t, items[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeValidRes(item stackitem.Item) *result.Invoke {
|
||||||
|
return &result.Invoke{
|
||||||
|
Stack: []stackitem.Item{item},
|
||||||
|
State: vmstate.Halt.String(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue