forked from TrueCloudLab/frostfs-contract
Compare commits
11 commits
Author | SHA1 | Date | |
---|---|---|---|
ae07280ae8 | |||
883a39011d | |||
fb9c8e97c2 | |||
819104db74 | |||
9f65415fac | |||
afe4eb3d76 | |||
a005dc1161 | |||
6674526a5b | |||
c350b7372f | |||
b38d42baf3 | |||
9283641cb4 |
18 changed files with 734 additions and 226 deletions
|
@ -7,6 +7,7 @@ Changelog for FrostFS Contract
|
||||||
### Added
|
### Added
|
||||||
- Field `state` to a namespace to indicate its' lifecycle stage (#154).
|
- Field `state` to a namespace to indicate its' lifecycle stage (#154).
|
||||||
- Method `UpdateNamespace` to adjust namespace state (#154).
|
- Method `UpdateNamespace` to adjust namespace state (#154).
|
||||||
|
- Method `DeleteNamespace` to remove existing namespace (#168).
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
### Removed
|
### Removed
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
v0.21.2
|
v0.21.4
|
||||||
|
|
10
common/address.go
Normal file
10
common/address.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/interop"
|
||||||
|
|
||||||
|
const (
|
||||||
|
NEO3PrefixLen = 1
|
||||||
|
ChecksumLen = 4
|
||||||
|
|
||||||
|
AddressLen = NEO3PrefixLen + interop.Hash160Len + ChecksumLen
|
||||||
|
)
|
|
@ -5,7 +5,7 @@ import "github.com/nspcc-dev/neo-go/pkg/interop/native/std"
|
||||||
const (
|
const (
|
||||||
major = 0
|
major = 0
|
||||||
minor = 21
|
minor = 21
|
||||||
patch = 2
|
patch = 4
|
||||||
|
|
||||||
// Versions from which an update should be performed.
|
// Versions from which an update should be performed.
|
||||||
// These should be used in a group (so prevMinor can be equal to minor if there are
|
// These should be used in a group (so prevMinor can be equal to minor if there are
|
||||||
|
|
|
@ -114,6 +114,7 @@ const (
|
||||||
|
|
||||||
createNamespaceMethod = "createNamespace"
|
createNamespaceMethod = "createNamespace"
|
||||||
updateNamespaceMethod = "updateNamespace"
|
updateNamespaceMethod = "updateNamespace"
|
||||||
|
deleteNamespaceMethod = "deleteNamespace"
|
||||||
getNamespaceMethod = "getNamespace"
|
getNamespaceMethod = "getNamespace"
|
||||||
getNamespaceExtendedMethod = "getNamespaceExtended"
|
getNamespaceExtendedMethod = "getNamespaceExtended"
|
||||||
listNamespacesMethod = "listNamespaces"
|
listNamespacesMethod = "listNamespaces"
|
||||||
|
@ -463,6 +464,18 @@ func (c Client) UpdateNamespaceCall(namespace string, state string) (method stri
|
||||||
return updateNamespaceMethod, []any{namespace, state}
|
return updateNamespaceMethod, []any{namespace, state}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteNamespace idempotently removes the namespace.
|
||||||
|
// Must be invoked by contract owner.
|
||||||
|
func (c Client) DeleteNamespace(namespace string) (tx util.Uint256, vub uint32, err error) {
|
||||||
|
method, args := c.DeleteNamespaceCall(namespace)
|
||||||
|
return c.act.SendCall(c.contract, method, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteNamespaceCall provides args for DeleteNamespace to use in commonclient.Transaction.
|
||||||
|
func (c Client) DeleteNamespaceCall(namespace string) (method string, args []any) {
|
||||||
|
return deleteNamespaceMethod, []any{namespace}
|
||||||
|
}
|
||||||
|
|
||||||
// ListNamespaces gets all namespaces.
|
// ListNamespaces gets all namespaces.
|
||||||
func (c Client) ListNamespaces() ([]*Namespace, error) {
|
func (c Client) ListNamespaces() ([]*Namespace, error) {
|
||||||
items, err := commonclient.ReadIteratorItems(c.act, iteratorBatchSize, c.contract, listNamespacesMethod)
|
items, err := commonclient.ReadIteratorItems(c.act, iteratorBatchSize, c.contract, listNamespacesMethod)
|
||||||
|
|
|
@ -185,7 +185,6 @@ func ParseNamespaceExtended(structArr []stackitem.Item) (*NamespaceExtended, err
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [cleanup] (#151) rewrite this method after new release.
|
|
||||||
func parseNamespace(structArr []stackitem.Item, stateIndex int) (*Namespace, error) {
|
func parseNamespace(structArr []stackitem.Item, stateIndex int) (*Namespace, error) {
|
||||||
name, err := structArr[0].TryBytes()
|
name, err := structArr[0].TryBytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -193,7 +192,7 @@ func parseNamespace(structArr []stackitem.Item, stateIndex int) (*Namespace, err
|
||||||
}
|
}
|
||||||
|
|
||||||
nsState := Active
|
nsState := Active
|
||||||
if len(structArr) == stateIndex+1 {
|
if len(structArr) >= stateIndex+1 {
|
||||||
nsStateBytes, err := structArr[stateIndex].TryBytes()
|
nsStateBytes, err := structArr[stateIndex].TryBytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -72,6 +72,10 @@ events:
|
||||||
type: String
|
type: String
|
||||||
- name: state
|
- name: state
|
||||||
type: String
|
type: String
|
||||||
|
- name: DeleteNamespace
|
||||||
|
parameters:
|
||||||
|
- name: namespace
|
||||||
|
type: String
|
||||||
- name: AddSubjectToNamespace
|
- name: AddSubjectToNamespace
|
||||||
parameters:
|
parameters:
|
||||||
- name: subjectAddress
|
- name: subjectAddress
|
||||||
|
|
|
@ -21,6 +21,7 @@ FrostFSID contract does not produce notifications to process.
|
||||||
| `c` | Int | group id counter |
|
| `c` | Int | group id counter |
|
||||||
| `m` + [ RIPEMD160 of namespace ] + [ RIPEMD160 of subject name ] | Serialized group id int | group name to group id index |
|
| `m` + [ RIPEMD160 of namespace ] + [ RIPEMD160 of subject name ] | Serialized group id int | group name to group id index |
|
||||||
| `A` + [ subject address ] | bool | means that the wallet has been used |
|
| `A` + [ subject address ] | bool | means that the wallet has been used |
|
||||||
|
| `d` + [ subject address ] + [ pk address ] | []byte{1} | link subject to extra public keys |
|
||||||
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -21,6 +21,7 @@ type (
|
||||||
// - Name: a string representing the name of the subject.
|
// - Name: a string representing the name of the subject.
|
||||||
// The name must match the following regex pattern: ^[\w+=,.@-]{1,64}$
|
// The name must match the following regex pattern: ^[\w+=,.@-]{1,64}$
|
||||||
// The Subject is stored in the storage as a hash(namespace) + hash(name).
|
// The Subject is stored in the storage as a hash(namespace) + hash(name).
|
||||||
|
// AdditionalKeys are stored in records with subject's address prefix.
|
||||||
Subject struct {
|
Subject struct {
|
||||||
PrimaryKey interop.PublicKey
|
PrimaryKey interop.PublicKey
|
||||||
AdditionalKeys []interop.PublicKey
|
AdditionalKeys []interop.PublicKey
|
||||||
|
@ -99,10 +100,15 @@ const (
|
||||||
groupCounterKey = 'c'
|
groupCounterKey = 'c'
|
||||||
namespaceGroupsNamesPrefix = 'm'
|
namespaceGroupsNamesPrefix = 'm'
|
||||||
addressPrefix = 'A'
|
addressPrefix = 'A'
|
||||||
|
subjectToAddKeyPrefix = 'd'
|
||||||
|
|
||||||
nsActiveState = "active"
|
nsActiveState = "active"
|
||||||
|
nsFrozenState = "frozen"
|
||||||
|
nsPurgeState = "purge"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var dummyValue = []byte{1}
|
||||||
|
|
||||||
func _deploy(data any, isUpdate bool) {
|
func _deploy(data any, isUpdate bool) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
|
|
||||||
|
@ -154,7 +160,23 @@ func _deploy(data any, isUpdate bool) {
|
||||||
storage.Put(ctx, groupCounterKey, maxGroupID)
|
storage.Put(ctx, groupCounterKey, maxGroupID)
|
||||||
}
|
}
|
||||||
|
|
||||||
migrateNamespacesState(ctx)
|
if args.version < common.GetVersion(0, 21, 3) {
|
||||||
|
migrateNamespacesState(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.version < common.GetVersion(0, 21, 4) {
|
||||||
|
it := storage.Find(ctx, subjectKeysPrefix, storage.ValuesOnly)
|
||||||
|
for iterator.Next(it) {
|
||||||
|
subject := std.Deserialize(iterator.Value(it).([]byte)).(Subject)
|
||||||
|
subjAddr := contract.CreateStandardAccount(subject.PrimaryKey)
|
||||||
|
for i := 0; i < len(subject.AdditionalKeys); i++ {
|
||||||
|
storage.Put(ctx, subjectToAdditionalKeyKey(subjAddr, subject.AdditionalKeys[i]), dummyValue)
|
||||||
|
}
|
||||||
|
subject.AdditionalKeys = nil
|
||||||
|
storage.Put(ctx, subjectKeyFromAddr(subjAddr), std.Serialize(subject))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,6 +231,7 @@ func Version() int {
|
||||||
// CreateSubject creates a new subject in the specified namespace with the provided public key.
|
// CreateSubject creates a new subject in the specified namespace with the provided public key.
|
||||||
func CreateSubject(ns string, key interop.PublicKey) {
|
func CreateSubject(ns string, key interop.PublicKey) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
|
checkNamespaceState(ns)
|
||||||
checkContractOwner(ctx)
|
checkContractOwner(ctx)
|
||||||
|
|
||||||
if len(key) != interop.PublicKeyCompressedLen {
|
if len(key) != interop.PublicKeyCompressedLen {
|
||||||
|
@ -222,7 +245,7 @@ func CreateSubject(ns string, key interop.PublicKey) {
|
||||||
panic("subject already exists")
|
panic("subject already exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
saPrefix := subjectAdditionalPrefix(key)
|
saPrefix := additionalKeyToSubjectPrefix(key)
|
||||||
it := storage.Find(ctx, saPrefix, storage.KeysOnly)
|
it := storage.Find(ctx, saPrefix, storage.KeysOnly)
|
||||||
for iterator.Next(it) {
|
for iterator.Next(it) {
|
||||||
panic("key is occupied")
|
panic("key is occupied")
|
||||||
|
@ -247,7 +270,7 @@ func CreateSubject(ns string, key interop.PublicKey) {
|
||||||
storage.Put(ctx, sKey, std.Serialize(subj))
|
storage.Put(ctx, sKey, std.Serialize(subj))
|
||||||
|
|
||||||
nsSubjKey := namespaceSubjectKey(ns, addr)
|
nsSubjKey := namespaceSubjectKey(ns, addr)
|
||||||
storage.Put(ctx, nsSubjKey, []byte{1})
|
storage.Put(ctx, nsSubjKey, dummyValue)
|
||||||
storage.Put(ctx, allAddressKey, true)
|
storage.Put(ctx, allAddressKey, true)
|
||||||
|
|
||||||
runtime.Notify("CreateSubject", interop.Hash160(addr))
|
runtime.Notify("CreateSubject", interop.Hash160(addr))
|
||||||
|
@ -270,13 +293,13 @@ func AddSubjectKey(addr interop.Hash160, key interop.PublicKey) {
|
||||||
panic("key is occupied")
|
panic("key is occupied")
|
||||||
}
|
}
|
||||||
|
|
||||||
saKey := subjectAdditionalKey(key, addr)
|
saKey := additionalKeyToSubjectKey(key, addr)
|
||||||
data := storage.Get(ctx, saKey).([]byte)
|
data := storage.Get(ctx, saKey).([]byte)
|
||||||
if data != nil {
|
if data != nil {
|
||||||
panic("key already added")
|
panic("key already added")
|
||||||
}
|
}
|
||||||
|
|
||||||
storage.Put(ctx, saKey, []byte{1})
|
storage.Put(ctx, saKey, dummyValue)
|
||||||
|
|
||||||
sKey := subjectKeyFromAddr(addr)
|
sKey := subjectKeyFromAddr(addr)
|
||||||
data = storage.Get(ctx, sKey).([]byte)
|
data = storage.Get(ctx, sKey).([]byte)
|
||||||
|
@ -284,9 +307,9 @@ func AddSubjectKey(addr interop.Hash160, key interop.PublicKey) {
|
||||||
panic("address not found")
|
panic("address not found")
|
||||||
}
|
}
|
||||||
subject := std.Deserialize(data).(Subject)
|
subject := std.Deserialize(data).(Subject)
|
||||||
subject.AdditionalKeys = append(subject.AdditionalKeys, key)
|
checkNamespaceState(subject.Namespace)
|
||||||
|
|
||||||
storage.Put(ctx, sKey, std.Serialize(subject))
|
storage.Put(ctx, subjectToAdditionalKeyKey(addr, key), dummyValue)
|
||||||
storage.Put(ctx, addressKey, true)
|
storage.Put(ctx, addressKey, true)
|
||||||
runtime.Notify("AddSubjectKey", addr, key)
|
runtime.Notify("AddSubjectKey", addr, key)
|
||||||
}
|
}
|
||||||
|
@ -303,7 +326,7 @@ func RemoveSubjectKey(addr interop.Hash160, key interop.PublicKey) {
|
||||||
panic("incorrect public key length")
|
panic("incorrect public key length")
|
||||||
}
|
}
|
||||||
|
|
||||||
saKey := subjectAdditionalKey(key, addr)
|
saKey := additionalKeyToSubjectKey(key, addr)
|
||||||
data := storage.Get(ctx, saKey).([]byte)
|
data := storage.Get(ctx, saKey).([]byte)
|
||||||
if data == nil {
|
if data == nil {
|
||||||
panic("key already removed")
|
panic("key already removed")
|
||||||
|
@ -316,17 +339,14 @@ func RemoveSubjectKey(addr interop.Hash160, key interop.PublicKey) {
|
||||||
if data == nil {
|
if data == nil {
|
||||||
panic("address not found")
|
panic("address not found")
|
||||||
}
|
}
|
||||||
subject := std.Deserialize(data).(Subject)
|
|
||||||
|
|
||||||
var additionalKeys []interop.PublicKey
|
subjToAddKey := subjectToAdditionalKeyKey(addr, key)
|
||||||
for i := 0; i < len(subject.AdditionalKeys); i++ {
|
data = storage.Get(ctx, subjToAddKey).([]byte)
|
||||||
if !common.BytesEqual(subject.AdditionalKeys[i], key) {
|
if data == nil {
|
||||||
additionalKeys = append(additionalKeys, subject.AdditionalKeys[i])
|
panic("key already removed")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
subject.AdditionalKeys = additionalKeys
|
storage.Delete(ctx, subjToAddKey)
|
||||||
|
|
||||||
storage.Put(ctx, sKey, std.Serialize(subject))
|
|
||||||
storage.Delete(ctx, addressKey(contract.CreateStandardAccount(key)))
|
storage.Delete(ctx, addressKey(contract.CreateStandardAccount(key)))
|
||||||
runtime.Notify("RemoveSubjectKey", addr, key)
|
runtime.Notify("RemoveSubjectKey", addr, key)
|
||||||
}
|
}
|
||||||
|
@ -347,6 +367,7 @@ func SetSubjectName(addr interop.Hash160, name string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
subject := std.Deserialize(data).(Subject)
|
subject := std.Deserialize(data).(Subject)
|
||||||
|
checkNamespaceState(subject.Namespace)
|
||||||
oldName := subject.Name
|
oldName := subject.Name
|
||||||
subject.Name = name
|
subject.Name = name
|
||||||
storage.Put(ctx, sKey, std.Serialize(subject))
|
storage.Put(ctx, sKey, std.Serialize(subject))
|
||||||
|
@ -372,6 +393,7 @@ func SetSubjectKV(addr interop.Hash160, key, val string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
subject := std.Deserialize(data).(Subject)
|
subject := std.Deserialize(data).(Subject)
|
||||||
|
checkNamespaceState(subject.Namespace)
|
||||||
if subject.KV == nil {
|
if subject.KV == nil {
|
||||||
subject.KV = map[string]string{}
|
subject.KV = map[string]string{}
|
||||||
}
|
}
|
||||||
|
@ -419,8 +441,10 @@ func DeleteSubject(addr interop.Hash160) {
|
||||||
}
|
}
|
||||||
subj := std.Deserialize(data).(Subject)
|
subj := std.Deserialize(data).(Subject)
|
||||||
|
|
||||||
|
subj.AdditionalKeys = getSubjectAdditionalKeys(ctx, addr)
|
||||||
for i := 0; i < len(subj.AdditionalKeys); i++ {
|
for i := 0; i < len(subj.AdditionalKeys); i++ {
|
||||||
storage.Delete(ctx, subjectAdditionalKey(subj.AdditionalKeys[i], addr))
|
storage.Delete(ctx, additionalKeyToSubjectKey(subj.AdditionalKeys[i], addr))
|
||||||
|
storage.Delete(ctx, subjectToAdditionalKeyKey(addr, subj.AdditionalKeys[i]))
|
||||||
storage.Delete(ctx, addressKey(contract.CreateStandardAccount(subj.AdditionalKeys[i])))
|
storage.Delete(ctx, addressKey(contract.CreateStandardAccount(subj.AdditionalKeys[i])))
|
||||||
}
|
}
|
||||||
storage.Delete(ctx, addressKey(addr))
|
storage.Delete(ctx, addressKey(addr))
|
||||||
|
@ -442,15 +466,17 @@ func GetSubject(addr interop.Hash160) Subject {
|
||||||
sKey := subjectKeyFromAddr(addr)
|
sKey := subjectKeyFromAddr(addr)
|
||||||
data := storage.Get(ctx, sKey).([]byte)
|
data := storage.Get(ctx, sKey).([]byte)
|
||||||
if data == nil {
|
if data == nil {
|
||||||
a := getPrimaryAddr(ctx, addr)
|
addr = getPrimaryAddr(ctx, addr)
|
||||||
sKey = subjectKeyFromAddr(a)
|
sKey = subjectKeyFromAddr(addr)
|
||||||
data = storage.Get(ctx, sKey).([]byte)
|
data = storage.Get(ctx, sKey).([]byte)
|
||||||
if data == nil {
|
if data == nil {
|
||||||
panic("subject not found")
|
panic("subject not found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return std.Deserialize(data).(Subject)
|
subj := std.Deserialize(data).(Subject)
|
||||||
|
subj.AdditionalKeys = getSubjectAdditionalKeys(ctx, addr)
|
||||||
|
return subj
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSubjectExtended retrieves the extended information of the subject with the specified address.
|
// GetSubjectExtended retrieves the extended information of the subject with the specified address.
|
||||||
|
@ -496,14 +522,18 @@ func GetSubjectByKey(key interop.PublicKey) Subject {
|
||||||
sKey := subjectKey(key)
|
sKey := subjectKey(key)
|
||||||
data := storage.Get(ctx, sKey).([]byte)
|
data := storage.Get(ctx, sKey).([]byte)
|
||||||
if data != nil {
|
if data != nil {
|
||||||
return std.Deserialize(data).(Subject)
|
subj := std.Deserialize(data).(Subject)
|
||||||
|
subj.AdditionalKeys = getSubjectAdditionalKeys(ctx, contract.CreateStandardAccount(key))
|
||||||
|
return subj
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := getPrimaryAddr(ctx, contract.CreateStandardAccount(key))
|
addr := getPrimaryAddr(ctx, contract.CreateStandardAccount(key))
|
||||||
sKey = subjectKeyFromAddr(addr)
|
sKey = subjectKeyFromAddr(addr)
|
||||||
data = storage.Get(ctx, sKey).([]byte)
|
data = storage.Get(ctx, sKey).([]byte)
|
||||||
if data != nil {
|
if data != nil {
|
||||||
return std.Deserialize(data).(Subject)
|
subj := std.Deserialize(data).(Subject)
|
||||||
|
subj.AdditionalKeys = getSubjectAdditionalKeys(ctx, addr)
|
||||||
|
return subj
|
||||||
}
|
}
|
||||||
|
|
||||||
panic("subject not found")
|
panic("subject not found")
|
||||||
|
@ -518,6 +548,20 @@ func getPrimaryAddr(ctx storage.Context, addr interop.Hash160) interop.Hash160 {
|
||||||
panic("subject not found")
|
panic("subject not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getSubjectAdditionalKeys(ctx storage.Context, addr interop.Hash160) []interop.PublicKey {
|
||||||
|
var result []interop.PublicKey
|
||||||
|
subjToAddKeyPrefix := subjectToAdditionalKeyPrefix(addr)
|
||||||
|
it := storage.Find(ctx, subjToAddKeyPrefix, storage.KeysOnly|storage.RemovePrefix)
|
||||||
|
if iterator.Next(it) {
|
||||||
|
key := iterator.Value(it).([]byte)
|
||||||
|
if len(key) < interop.PublicKeyCompressedLen {
|
||||||
|
panic("invalid subject additional key")
|
||||||
|
}
|
||||||
|
result = append(result, interop.PublicKey(key[:interop.PublicKeyCompressedLen]))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// GetSubjectByName retrieves the subject with the specified name within the given namespace.
|
// GetSubjectByName retrieves the subject with the specified name within the given namespace.
|
||||||
func GetSubjectByName(ns, name string) Subject {
|
func GetSubjectByName(ns, name string) Subject {
|
||||||
key := GetSubjectKeyByName(ns, name)
|
key := GetSubjectKeyByName(ns, name)
|
||||||
|
@ -595,6 +639,36 @@ func CreateNamespace(ns string) {
|
||||||
runtime.Notify("CreateNamespace", ns)
|
runtime.Notify("CreateNamespace", ns)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteNamespace idempotently removes a namespace with the specified name.
|
||||||
|
func DeleteNamespace(ns string) {
|
||||||
|
ctx := storage.GetContext()
|
||||||
|
checkContractOwner(ctx)
|
||||||
|
|
||||||
|
nsKey := namespaceKey(ns)
|
||||||
|
data := storage.Get(ctx, nsKey).([]byte)
|
||||||
|
if data == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace := std.Deserialize(data).(Namespace)
|
||||||
|
if namespace.State != nsPurgeState {
|
||||||
|
panic("namespace should be in 'purge' state for deletion")
|
||||||
|
}
|
||||||
|
|
||||||
|
it := storage.Find(ctx, groupPrefix(ns), storage.KeysOnly)
|
||||||
|
if iterator.Next(it) {
|
||||||
|
panic("can't delete non-empty namespace: groups still present")
|
||||||
|
}
|
||||||
|
|
||||||
|
it = storage.Find(ctx, namespaceSubjectPrefix(ns), storage.KeysOnly)
|
||||||
|
if iterator.Next(it) {
|
||||||
|
panic("can't delete non-empty namespace: users still present")
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.Delete(ctx, nsKey)
|
||||||
|
runtime.Notify("DeleteNamespace", ns)
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateNamespace updates existing namespace.
|
// UpdateNamespace updates existing namespace.
|
||||||
func UpdateNamespace(ns string, state string) {
|
func UpdateNamespace(ns string, state string) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
|
@ -668,6 +742,7 @@ func ListNamespaceSubjects(ns string) iterator.Iterator {
|
||||||
// CreateGroup creates a new group within the specified namespace.
|
// CreateGroup creates a new group within the specified namespace.
|
||||||
func CreateGroup(ns, group string) int {
|
func CreateGroup(ns, group string) int {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
|
checkNamespaceState(ns)
|
||||||
checkContractOwner(ctx)
|
checkContractOwner(ctx)
|
||||||
|
|
||||||
if group == "" {
|
if group == "" {
|
||||||
|
@ -781,6 +856,7 @@ func SetGroupName(ns string, groupID int, name string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
gr := std.Deserialize(data).(Group)
|
gr := std.Deserialize(data).(Group)
|
||||||
|
checkNamespaceState(gr.Namespace)
|
||||||
oldName := gr.Name
|
oldName := gr.Name
|
||||||
gr.Name = name
|
gr.Name = name
|
||||||
storage.Put(ctx, gKey, std.Serialize(gr))
|
storage.Put(ctx, gKey, std.Serialize(gr))
|
||||||
|
@ -802,6 +878,7 @@ func SetGroupKV(ns string, groupID int, key, val string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
gr := std.Deserialize(data).(Group)
|
gr := std.Deserialize(data).(Group)
|
||||||
|
checkNamespaceState(gr.Namespace)
|
||||||
if gr.KV == nil {
|
if gr.KV == nil {
|
||||||
gr.KV = map[string]string{}
|
gr.KV = map[string]string{}
|
||||||
}
|
}
|
||||||
|
@ -849,6 +926,7 @@ func AddSubjectToGroup(addr interop.Hash160, groupID int) {
|
||||||
panic("subject not found")
|
panic("subject not found")
|
||||||
}
|
}
|
||||||
subject := std.Deserialize(data).(Subject)
|
subject := std.Deserialize(data).(Subject)
|
||||||
|
checkNamespaceState(subject.Namespace)
|
||||||
|
|
||||||
gKey := groupKey(subject.Namespace, groupID)
|
gKey := groupKey(subject.Namespace, groupID)
|
||||||
data = storage.Get(ctx, gKey).([]byte)
|
data = storage.Get(ctx, gKey).([]byte)
|
||||||
|
@ -862,7 +940,7 @@ func AddSubjectToGroup(addr interop.Hash160, groupID int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
gsKey := groupSubjectKey(subject.Namespace, groupID, addr)
|
gsKey := groupSubjectKey(subject.Namespace, groupID, addr)
|
||||||
storage.Put(ctx, gsKey, []byte{1})
|
storage.Put(ctx, gsKey, dummyValue)
|
||||||
|
|
||||||
runtime.Notify("AddSubjectToGroup", addr, subject.Namespace, groupID)
|
runtime.Notify("AddSubjectToGroup", addr, subject.Namespace, groupID)
|
||||||
}
|
}
|
||||||
|
@ -1031,15 +1109,25 @@ func subjectKeyFromAddr(addr interop.Hash160) []byte {
|
||||||
return append([]byte{subjectKeysPrefix}, addr...)
|
return append([]byte{subjectKeysPrefix}, addr...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func subjectAdditionalKey(additionalKey interop.PublicKey, primeAddr interop.Hash160) []byte {
|
func additionalKeyToSubjectKey(additionalKey interop.PublicKey, primeAddr interop.Hash160) []byte {
|
||||||
return append(subjectAdditionalPrefix(additionalKey), primeAddr...)
|
return append(additionalKeyToSubjectPrefix(additionalKey), primeAddr...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func subjectAdditionalPrefix(additionalKey interop.PublicKey) []byte {
|
func additionalKeyToSubjectPrefix(additionalKey interop.PublicKey) []byte {
|
||||||
addr := contract.CreateStandardAccount(additionalKey)
|
addr := contract.CreateStandardAccount(additionalKey)
|
||||||
return append([]byte{additionalKeysPrefix}, addr...)
|
return append([]byte{additionalKeysPrefix}, addr...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// subjectToAdditionalKeyKey returns 'd' + [20]byte subjectAddr + [33]byte additionalKey.
|
||||||
|
func subjectToAdditionalKeyKey(subjectAddr interop.Hash160, additionalKey interop.PublicKey) []byte {
|
||||||
|
return append(subjectToAdditionalKeyPrefix(subjectAddr), additionalKey...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// subjectToAdditionalKeyPrefix returns 'd' + [20]byte subjectAddr.
|
||||||
|
func subjectToAdditionalKeyPrefix(subjectAddr interop.Hash160) []byte {
|
||||||
|
return append([]byte{subjectToAddKeyPrefix}, subjectAddr...)
|
||||||
|
}
|
||||||
|
|
||||||
func namespaceKey(ns string) []byte {
|
func namespaceKey(ns string) []byte {
|
||||||
return namespaceKeyFromHash(ripemd160Hash(ns))
|
return namespaceKeyFromHash(ripemd160Hash(ns))
|
||||||
}
|
}
|
||||||
|
@ -1126,7 +1214,6 @@ func addressKey(address []byte) []byte {
|
||||||
return append([]byte{addressPrefix}, address...)
|
return append([]byte{addressPrefix}, address...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [cleanup] (#151) remove this migration after new release.
|
|
||||||
func migrateNamespacesState(ctx storage.Context) {
|
func migrateNamespacesState(ctx storage.Context) {
|
||||||
it := storage.Find(ctx, []byte{namespaceKeysPrefix}, storage.None)
|
it := storage.Find(ctx, []byte{namespaceKeysPrefix}, storage.None)
|
||||||
|
|
||||||
|
@ -1149,3 +1236,10 @@ func migrateNamespacesState(ctx storage.Context) {
|
||||||
storage.Put(ctx, string(kv.Key), namespaceData)
|
storage.Put(ctx, string(kv.Key), namespaceData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkNamespaceState(name string) {
|
||||||
|
ns := GetNamespace(name)
|
||||||
|
if ns.State == nsFrozenState || ns.State == nsPurgeState {
|
||||||
|
panic("namespace is non-active")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,19 +7,14 @@ import (
|
||||||
|
|
||||||
// NameState represents domain name state.
|
// NameState represents domain name state.
|
||||||
type NameState struct {
|
type NameState struct {
|
||||||
Owner interop.Hash160
|
Owner interop.Hash160
|
||||||
Name string
|
Name string
|
||||||
|
// Expiration field used to contain wall-clock time of a domain expiration.
|
||||||
|
// It is preserved for backwards compatibility, but is unused by the contract and should be ignored.
|
||||||
Expiration int64
|
Expiration int64
|
||||||
Admin interop.Hash160
|
Admin interop.Hash160
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensureNotExpired panics if domain name is expired.
|
|
||||||
func (n NameState) ensureNotExpired() {
|
|
||||||
if int64(runtime.GetTime()) >= n.Expiration {
|
|
||||||
panic("name has expired")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkAdmin panics if script container is not signed by the domain name admin.
|
// checkAdmin panics if script container is not signed by the domain name admin.
|
||||||
func (n NameState) checkAdmin() {
|
func (n NameState) checkAdmin() {
|
||||||
if runtime.CheckWitness(n.Owner) {
|
if runtime.CheckWitness(n.Owner) {
|
||||||
|
|
|
@ -148,8 +148,7 @@ func Properties(tokenID []byte) map[string]any {
|
||||||
ctx := storage.GetReadOnlyContext()
|
ctx := storage.GetReadOnlyContext()
|
||||||
ns := getNameState(ctx, tokenID)
|
ns := getNameState(ctx, tokenID)
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
"name": ns.Name,
|
"name": ns.Name,
|
||||||
"expiration": ns.Expiration,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,7 +307,6 @@ func extractCnametgt(ctx storage.Context, name, domain string) string {
|
||||||
|
|
||||||
// checkParent returns parent domain or empty string if domain not found.
|
// checkParent returns parent domain or empty string if domain not found.
|
||||||
func checkParent(ctx storage.Context, fragments []string) string {
|
func checkParent(ctx storage.Context, fragments []string) string {
|
||||||
now := int64(runtime.GetTime())
|
|
||||||
last := len(fragments) - 1
|
last := len(fragments) - 1
|
||||||
name := fragments[last]
|
name := fragments[last]
|
||||||
parent := ""
|
parent := ""
|
||||||
|
@ -320,10 +318,6 @@ func checkParent(ctx storage.Context, fragments []string) string {
|
||||||
if nsBytes == nil {
|
if nsBytes == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
|
|
||||||
if now >= ns.Expiration {
|
|
||||||
panic("domain expired: " + name)
|
|
||||||
}
|
|
||||||
parent = name
|
parent = name
|
||||||
}
|
}
|
||||||
return parent
|
return parent
|
||||||
|
@ -390,19 +384,15 @@ func register(ctx storage.Context, name string, owner interop.Hash160, email str
|
||||||
nsBytes := storage.Get(ctx, append([]byte{prefixName}, tokenKey...))
|
nsBytes := storage.Get(ctx, append([]byte{prefixName}, tokenKey...))
|
||||||
if nsBytes != nil {
|
if nsBytes != nil {
|
||||||
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
|
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
|
||||||
if int64(runtime.GetTime()) < ns.Expiration {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
oldOwner = ns.Owner
|
oldOwner = ns.Owner
|
||||||
updateBalance(ctx, []byte(name), oldOwner, -1)
|
updateBalance(ctx, []byte(name), oldOwner, -1)
|
||||||
} else {
|
} else {
|
||||||
updateTotalSupply(ctx, +1)
|
updateTotalSupply(ctx, +1)
|
||||||
}
|
}
|
||||||
ns := NameState{
|
ns := NameState{
|
||||||
Owner: owner,
|
Owner: owner,
|
||||||
Name: name,
|
Name: name,
|
||||||
// NNS expiration is in milliseconds
|
Expiration: 0,
|
||||||
Expiration: int64(runtime.GetTime() + expire*1000),
|
|
||||||
}
|
}
|
||||||
checkAvailableGlobalDomain(ctx, name)
|
checkAvailableGlobalDomain(ctx, name)
|
||||||
|
|
||||||
|
@ -415,18 +405,6 @@ func register(ctx storage.Context, name string, owner interop.Hash160, email str
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renew increases domain expiration date.
|
|
||||||
func Renew(name string) int64 {
|
|
||||||
checkDomainNameLength(name)
|
|
||||||
runtime.BurnGas(GetPrice())
|
|
||||||
ctx := storage.GetContext()
|
|
||||||
ns := getNameState(ctx, []byte(name))
|
|
||||||
ns.checkAdmin()
|
|
||||||
ns.Expiration += millisecondsInYear
|
|
||||||
putNameState(ctx, ns)
|
|
||||||
return ns.Expiration
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateSOA updates soa record.
|
// UpdateSOA updates soa record.
|
||||||
func UpdateSOA(name, email string, refresh, retry, expire, ttl int) {
|
func UpdateSOA(name, email string, refresh, retry, expire, ttl int) {
|
||||||
checkDomainNameLength(name)
|
checkDomainNameLength(name)
|
||||||
|
@ -731,9 +709,7 @@ func getNameStateWithKey(ctx storage.Context, tokenKey []byte) NameState {
|
||||||
if nsBytes == nil {
|
if nsBytes == nil {
|
||||||
panic("token not found")
|
panic("token not found")
|
||||||
}
|
}
|
||||||
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
|
return std.Deserialize(nsBytes.([]byte)).(NameState)
|
||||||
ns.ensureNotExpired()
|
|
||||||
return ns
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// putNameState stores domain name state.
|
// putNameState stores domain name state.
|
||||||
|
@ -801,7 +777,7 @@ func addRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType,
|
||||||
ns := NameState{
|
ns := NameState{
|
||||||
Name: globalDomain,
|
Name: globalDomain,
|
||||||
Owner: nsOriginal.Owner,
|
Owner: nsOriginal.Owner,
|
||||||
Expiration: nsOriginal.Expiration,
|
Expiration: 0,
|
||||||
Admin: nsOriginal.Admin,
|
Admin: nsOriginal.Admin,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1119,16 +1095,13 @@ func tokenIDFromName(name string) string {
|
||||||
|
|
||||||
ctx := storage.GetReadOnlyContext()
|
ctx := storage.GetReadOnlyContext()
|
||||||
sum := 0
|
sum := 0
|
||||||
l := len(fragments) - 1
|
l := len(fragments)
|
||||||
for i := 0; i < l; i++ {
|
for i := 0; i < l; i++ {
|
||||||
tokenKey := getTokenKey([]byte(name[sum:]))
|
tokenKey := getTokenKey([]byte(name[sum:]))
|
||||||
nameKey := append([]byte{prefixName}, tokenKey...)
|
nameKey := append([]byte{prefixName}, tokenKey...)
|
||||||
nsBytes := storage.Get(ctx, nameKey)
|
nsBytes := storage.Get(ctx, nameKey)
|
||||||
if nsBytes != nil {
|
if nsBytes != nil {
|
||||||
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
|
return name[sum:]
|
||||||
if int64(runtime.GetTime()) < ns.Expiration {
|
|
||||||
return name[sum:]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
sum += len(fragments[i]) + 1
|
sum += len(fragments[i]) + 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,13 @@ package policy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop"
|
"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/iterator"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/std"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
"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/storage"
|
||||||
)
|
)
|
||||||
|
@ -36,6 +40,11 @@ const (
|
||||||
ErrNotAuthorized = "none of the signers is authorized to change the contract"
|
ErrNotAuthorized = "none of the signers is authorized to change the contract"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
purgeNsState = "purge"
|
||||||
|
frozenNsState = "frozen"
|
||||||
|
)
|
||||||
|
|
||||||
// _deploy function sets up initial list of inner ring public keys.
|
// _deploy function sets up initial list of inner ring public keys.
|
||||||
func _deploy(data any, isUpdate bool) {
|
func _deploy(data any, isUpdate bool) {
|
||||||
if isUpdate {
|
if isUpdate {
|
||||||
|
@ -50,7 +59,7 @@ func _deploy(data any, isUpdate bool) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
if args.Admin != nil {
|
if args.Admin != nil {
|
||||||
if len(args.Admin) != 20 {
|
if len(args.Admin) != 20 {
|
||||||
panic("invaliad admin hash length")
|
panic("invalid admin hash length")
|
||||||
}
|
}
|
||||||
storage.Put(ctx, []byte{ownerKeyPrefix}, args.Admin)
|
storage.Put(ctx, []byte{ownerKeyPrefix}, args.Admin)
|
||||||
}
|
}
|
||||||
|
@ -142,9 +151,51 @@ func mapToNumericCreateIfNotExists(ctx storage.Context, kind Kind, name []byte)
|
||||||
return numericID.(int)
|
return numericID.(int)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkChainNamespace(entity Kind, name string) {
|
||||||
|
if entity != Namespace {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
frostfsidAddr := getContractHash(nns.FrostfsIDNNSName)
|
||||||
|
if frostfsidAddr == nil || management.GetContract(frostfsidAddr) == nil {
|
||||||
|
panic("could not get frostfsid contract")
|
||||||
|
}
|
||||||
|
ns := contract.Call(frostfsidAddr, "getNamespace", contract.ReadOnly, name).(frostfsid.Namespace)
|
||||||
|
if ns.State == purgeNsState || ns.State == frozenNsState {
|
||||||
|
panic("namespace is non-active")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getContractHash returns nil when it can't resolve contract name,
|
||||||
|
// so custom error message can be thrown.
|
||||||
|
func getContractHash(name string) interop.Hash160 {
|
||||||
|
nnsContract := management.GetContractByID(1)
|
||||||
|
records := contract.Call(nnsContract.Hash, "getRecords", contract.ReadOnly, name, nns.TXT).([]string)
|
||||||
|
for _, record := range records {
|
||||||
|
contractHash := readContractHashFromNNSRecord(record)
|
||||||
|
if contractHash != nil {
|
||||||
|
return contractHash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readContractHashFromNNSRecord(nnsResponse string) interop.Hash160 {
|
||||||
|
// 40 is size of hex encoded contract hash as string
|
||||||
|
if len(nnsResponse) == 40 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded := std.Base58Decode([]byte(nnsResponse))
|
||||||
|
if len(decoded) != common.AddressLen || management.GetContract(decoded[1:21]) == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return decoded[1:21]
|
||||||
|
}
|
||||||
|
|
||||||
func AddChain(entity Kind, entityName string, name []byte, chain []byte) {
|
func AddChain(entity Kind, entityName string, name []byte, chain []byte) {
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
checkAuthorization(ctx)
|
checkAuthorization(ctx)
|
||||||
|
checkChainNamespace(entity, entityName)
|
||||||
|
|
||||||
entityNameBytes := mapToNumericCreateIfNotExists(ctx, entity, []byte(entityName))
|
entityNameBytes := mapToNumericCreateIfNotExists(ctx, entity, []byte(entityName))
|
||||||
key := storageKey(entity, entityNameBytes, name)
|
key := storageKey(entity, entityNameBytes, name)
|
||||||
|
|
|
@ -70,6 +70,11 @@ type UpdateNamespaceEvent struct {
|
||||||
State string
|
State string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteNamespaceEvent represents "DeleteNamespace" event emitted by the contract.
|
||||||
|
type DeleteNamespaceEvent struct {
|
||||||
|
Namespace string
|
||||||
|
}
|
||||||
|
|
||||||
// AddSubjectToNamespaceEvent represents "AddSubjectToNamespace" event emitted by the contract.
|
// AddSubjectToNamespaceEvent represents "AddSubjectToNamespace" event emitted by the contract.
|
||||||
type AddSubjectToNamespaceEvent struct {
|
type AddSubjectToNamespaceEvent struct {
|
||||||
SubjectAddress util.Uint160
|
SubjectAddress util.Uint160
|
||||||
|
@ -489,6 +494,28 @@ func (c *Contract) DeleteGroupKVUnsigned(ns string, groupID *big.Int, key string
|
||||||
return c.actor.MakeUnsignedCall(c.hash, "deleteGroupKV", nil, ns, groupID, key)
|
return c.actor.MakeUnsignedCall(c.hash, "deleteGroupKV", nil, ns, groupID, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteNamespace creates a transaction invoking `deleteNamespace` 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) DeleteNamespace(ns string) (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "deleteNamespace", ns)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteNamespaceTransaction creates a transaction invoking `deleteNamespace` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) DeleteNamespaceTransaction(ns string) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "deleteNamespace", ns)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteNamespaceUnsigned creates a transaction invoking `deleteNamespace` 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) DeleteNamespaceUnsigned(ns string) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "deleteNamespace", nil, ns)
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteSubject creates a transaction invoking `deleteSubject` method of the contract.
|
// DeleteSubject creates a transaction invoking `deleteSubject` method of the contract.
|
||||||
// This transaction is signed and immediately sent to the network.
|
// This transaction is signed and immediately sent to the network.
|
||||||
// The values returned are its hash, ValidUntilBlock value and error if any.
|
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||||
|
@ -1394,6 +1421,67 @@ func (e *UpdateNamespaceEvent) FromStackItem(item *stackitem.Array) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteNamespaceEventsFromApplicationLog retrieves a set of all emitted events
|
||||||
|
// with "DeleteNamespace" name from the provided [result.ApplicationLog].
|
||||||
|
func DeleteNamespaceEventsFromApplicationLog(log *result.ApplicationLog) ([]*DeleteNamespaceEvent, error) {
|
||||||
|
if log == nil {
|
||||||
|
return nil, errors.New("nil application log")
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*DeleteNamespaceEvent
|
||||||
|
for i, ex := range log.Executions {
|
||||||
|
for j, e := range ex.Events {
|
||||||
|
if e.Name != "DeleteNamespace" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event := new(DeleteNamespaceEvent)
|
||||||
|
err := event.FromStackItem(e.Item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deserialize DeleteNamespaceEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||||
|
}
|
||||||
|
res = append(res, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem converts provided [stackitem.Array] to DeleteNamespaceEvent or
|
||||||
|
// returns an error if it's not possible to do to so.
|
||||||
|
func (e *DeleteNamespaceEvent) FromStackItem(item *stackitem.Array) error {
|
||||||
|
if item == nil {
|
||||||
|
return errors.New("nil item")
|
||||||
|
}
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 1 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
e.Namespace, err = func(item stackitem.Item) (string, error) {
|
||||||
|
b, err := item.TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !utf8.Valid(b) {
|
||||||
|
return "", errors.New("not a UTF-8 string")
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}(arr[index])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field Namespace: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// AddSubjectToNamespaceEventsFromApplicationLog retrieves a set of all emitted events
|
// AddSubjectToNamespaceEventsFromApplicationLog retrieves a set of all emitted events
|
||||||
// with "AddSubjectToNamespace" name from the provided [result.ApplicationLog].
|
// with "AddSubjectToNamespace" name from the provided [result.ApplicationLog].
|
||||||
func AddSubjectToNamespaceEventsFromApplicationLog(log *result.ApplicationLog) ([]*AddSubjectToNamespaceEvent, error) {
|
func AddSubjectToNamespaceEventsFromApplicationLog(log *result.ApplicationLog) ([]*AddSubjectToNamespaceEvent, error) {
|
||||||
|
|
|
@ -286,28 +286,6 @@ func (c *Contract) RegisterUnsigned(name string, owner util.Uint160, email strin
|
||||||
return c.actor.MakeUnsignedRun(script, nil)
|
return c.actor.MakeUnsignedRun(script, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renew creates a transaction invoking `renew` 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) Renew(name string) (util.Uint256, uint32, error) {
|
|
||||||
return c.actor.SendCall(c.hash, "renew", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenewTransaction creates a transaction invoking `renew` method of the contract.
|
|
||||||
// This transaction is signed, but not sent to the network, instead it's
|
|
||||||
// returned to the caller.
|
|
||||||
func (c *Contract) RenewTransaction(name string) (*transaction.Transaction, error) {
|
|
||||||
return c.actor.MakeCall(c.hash, "renew", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenewUnsigned creates a transaction invoking `renew` 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) RenewUnsigned(name string) (*transaction.Transaction, error) {
|
|
||||||
return c.actor.MakeUnsignedCall(c.hash, "renew", nil, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetAdmin creates a transaction invoking `setAdmin` method of the contract.
|
// SetAdmin creates a transaction invoking `setAdmin` method of the contract.
|
||||||
// This transaction is signed and immediately sent to the network.
|
// This transaction is signed and immediately sent to the network.
|
||||||
// The values returned are its hash, ValidUntilBlock value and error if any.
|
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||||
|
|
|
@ -189,6 +189,79 @@ func TestFrostFSID_Client_NamespaceManagement(t *testing.T) {
|
||||||
subjects, err = ffsid.cli.ListNamespaceSubjects(namespace)
|
subjects, err = ffsid.cli.ListNamespaceSubjects(namespace)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Empty(t, subjects)
|
require.Empty(t, subjects)
|
||||||
|
|
||||||
|
namespace2 := "namespace2"
|
||||||
|
ffsid.a.await(ffsid.cli.CreateNamespace(namespace2))
|
||||||
|
ns1, err := ffsid.cli.GetNamespace(namespace)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, namespace, ns1.Name)
|
||||||
|
require.Equal(t, client.Active, ns1.State)
|
||||||
|
|
||||||
|
nsExt2, err := ffsid.cli.GetNamespaceExtended(namespace2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, namespace2, nsExt2.Name)
|
||||||
|
require.Equal(t, client.Active, nsExt2.State)
|
||||||
|
|
||||||
|
ffsid.a.await(ffsid.cli.UpdateNamespace(namespace2, client.Frozen))
|
||||||
|
nsExt2, err = ffsid.cli.GetNamespaceExtended(namespace2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, namespace2, nsExt2.Name)
|
||||||
|
require.Equal(t, client.Frozen, nsExt2.State)
|
||||||
|
|
||||||
|
_, _, err = ffsid.cli.DeleteNamespace(namespace2)
|
||||||
|
require.ErrorContains(t, err, "namespace should be in 'purge' state for deletion")
|
||||||
|
|
||||||
|
ffsid.a.await(ffsid.cli.UpdateNamespace(namespace2, client.Active))
|
||||||
|
nsExt2, err = ffsid.cli.GetNamespaceExtended(namespace2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, client.Active, nsExt2.State)
|
||||||
|
|
||||||
|
subjKey2, subjAddr2 := newKey(t)
|
||||||
|
ffsid.a.await(ffsid.cli.CreateSubject(namespace2, subjKey2.PublicKey()))
|
||||||
|
|
||||||
|
subj2, err := ffsid.cli.GetSubject(subjAddr2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, namespace2, subj2.Namespace)
|
||||||
|
|
||||||
|
ffsid.a.await(ffsid.cli.UpdateNamespace(namespace2, client.Purge))
|
||||||
|
nsExt2, err = ffsid.cli.GetNamespaceExtended(namespace2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, client.Purge, nsExt2.State)
|
||||||
|
|
||||||
|
_, _, err = ffsid.cli.DeleteNamespace(namespace2)
|
||||||
|
require.ErrorContains(t, err, "can't delete non-empty namespace: users still present")
|
||||||
|
|
||||||
|
ffsid.a.await(ffsid.cli.DeleteSubject(subjAddr2))
|
||||||
|
|
||||||
|
ffsid.a.await(ffsid.cli.UpdateNamespace(namespace2, client.Active))
|
||||||
|
nsExt2, err = ffsid.cli.GetNamespaceExtended(namespace2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, client.Active, nsExt2.State)
|
||||||
|
|
||||||
|
groupName := "ns_group"
|
||||||
|
ffsid.a.await(ffsid.cli.CreateGroup(namespace2, groupName))
|
||||||
|
group, err := ffsid.cli.GetGroupByName(namespace2, groupName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ffsid.a.await(ffsid.cli.UpdateNamespace(namespace2, client.Purge))
|
||||||
|
nsExt2, err = ffsid.cli.GetNamespaceExtended(namespace2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, client.Purge, nsExt2.State)
|
||||||
|
|
||||||
|
_, _, err = ffsid.cli.DeleteNamespace(namespace2)
|
||||||
|
require.ErrorContains(t, err, "can't delete non-empty namespace: groups still present")
|
||||||
|
|
||||||
|
ffsid.a.await(ffsid.cli.DeleteGroup(namespace2, group.ID))
|
||||||
|
|
||||||
|
ffsid.a.await(ffsid.cli.DeleteNamespace(namespace2))
|
||||||
|
_, err = ffsid.cli.GetNamespace(namespace2)
|
||||||
|
require.ErrorContains(t, err, "not found")
|
||||||
|
|
||||||
|
namespace3 := "namespace3"
|
||||||
|
_, err = ffsid.cli.GetNamespace(namespace3)
|
||||||
|
require.ErrorContains(t, err, "not found")
|
||||||
|
_, _, err = ffsid.cli.DeleteNamespace(namespace3)
|
||||||
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFrostFSID_Client_DefaultNamespace(t *testing.T) {
|
func TestFrostFSID_Client_DefaultNamespace(t *testing.T) {
|
||||||
|
@ -580,3 +653,25 @@ func prettyPrintExtendedSubjects(subjects []*client.SubjectExtended) {
|
||||||
fmt.Println(sb.String())
|
fmt.Println(sb.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFrostfsID_ConcurrentAddSubjectKey(t *testing.T) {
|
||||||
|
f := newFrostFSIDInvoker(t)
|
||||||
|
|
||||||
|
newKey := func(t *testing.T) *keys.PrivateKey {
|
||||||
|
pk, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
return pk
|
||||||
|
}
|
||||||
|
|
||||||
|
subjKey := newKey(t)
|
||||||
|
subjKeyAddr := subjKey.PublicKey().GetScriptHash()
|
||||||
|
invoker := f.OwnerInvoker()
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, subjKey.PublicKey().Bytes())
|
||||||
|
|
||||||
|
additionalKey1 := newKey(t)
|
||||||
|
additionalKey2 := newKey(t)
|
||||||
|
tx1 := invoker.PrepareInvoke(t, addSubjectKeyMethod, subjKeyAddr, additionalKey1.PublicKey().Bytes())
|
||||||
|
tx2 := invoker.PrepareInvoke(t, addSubjectKeyMethod, subjKeyAddr, additionalKey2.PublicKey().Bytes())
|
||||||
|
|
||||||
|
invoker.AddBlockCheckHalt(t, tx1, tx2)
|
||||||
|
}
|
||||||
|
|
|
@ -24,7 +24,10 @@ import (
|
||||||
|
|
||||||
const frostfsidPath = "../frostfsid"
|
const frostfsidPath = "../frostfsid"
|
||||||
|
|
||||||
const defaultNamespace = ""
|
const (
|
||||||
|
defaultNamespace = ""
|
||||||
|
customNamespace = "custom"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
setAdminMethod = "setAdmin"
|
setAdminMethod = "setAdmin"
|
||||||
|
@ -47,6 +50,7 @@ const (
|
||||||
getNamespaceMethod = "getNamespace"
|
getNamespaceMethod = "getNamespace"
|
||||||
getNamespaceExtendedMethod = "getNamespaceExtended"
|
getNamespaceExtendedMethod = "getNamespaceExtended"
|
||||||
updateNamespaceMethod = "updateNamespace"
|
updateNamespaceMethod = "updateNamespace"
|
||||||
|
deleteNamespaceMethod = "deleteNamespace"
|
||||||
listNamespacesMethod = "listNamespaces"
|
listNamespacesMethod = "listNamespaces"
|
||||||
listNamespaceSubjectsMethod = "listNamespaceSubjects"
|
listNamespaceSubjectsMethod = "listNamespaceSubjects"
|
||||||
|
|
||||||
|
@ -66,7 +70,16 @@ const (
|
||||||
nsActiveState = "active"
|
nsActiveState = "active"
|
||||||
)
|
)
|
||||||
|
|
||||||
const notWitnessedError = "not witnessed"
|
const (
|
||||||
|
frozenState = "frozen"
|
||||||
|
purgeState = "purge"
|
||||||
|
namespaceNonActive = "namespace is non-active"
|
||||||
|
notWitnessedError = "not witnessed"
|
||||||
|
notFoundError = "namespace not found"
|
||||||
|
cantDeleteNonEmptyNamespceGroupsPresent = "can't delete non-empty namespace: groups still present"
|
||||||
|
cantDeleteNonEmptyNamespceUsersPresent = "can't delete non-empty namespace: users still present"
|
||||||
|
namespaceShouldBeInPurgeStateError = "namespace should be in 'purge' state for deletion"
|
||||||
|
)
|
||||||
|
|
||||||
type testFrostFSIDInvoker struct {
|
type testFrostFSIDInvoker struct {
|
||||||
e *neotest.Executor
|
e *neotest.Executor
|
||||||
|
@ -306,6 +319,56 @@ func TestFrostFSID_SubjectManagement(t *testing.T) {
|
||||||
require.ElementsMatch(t, addresses, []util.Uint160{subjKeyAddr, newSubjKey.PublicKey().GetScriptHash()})
|
require.ElementsMatch(t, addresses, []util.Uint160{subjKeyAddr, newSubjKey.PublicKey().GetScriptHash()})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("subject operations for non-active namespace", func(t *testing.T) {
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, customNamespace)
|
||||||
|
|
||||||
|
baseKey, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
baseAddr, baseBytes := baseKey.PublicKey().GetScriptHash(), baseKey.PublicKey().Bytes()
|
||||||
|
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, updateNamespaceMethod, customNamespace, frozenState)
|
||||||
|
|
||||||
|
invoker.InvokeFail(t, namespaceNonActive, createSubjectMethod, customNamespace, baseBytes)
|
||||||
|
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, updateNamespaceMethod, customNamespace, purgeState)
|
||||||
|
invoker.InvokeFail(t, namespaceNonActive, createSubjectMethod, customNamespace, baseBytes)
|
||||||
|
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, updateNamespaceMethod, customNamespace, "active")
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, customNamespace, baseBytes)
|
||||||
|
|
||||||
|
t.Run("addSubjectKey", func(t *testing.T) {
|
||||||
|
newSubjKey, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
keyBytes := newSubjKey.PublicKey().Bytes()
|
||||||
|
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, updateNamespaceMethod, customNamespace, frozenState)
|
||||||
|
invoker.InvokeFail(t, namespaceNonActive, addSubjectKeyMethod, baseAddr, keyBytes)
|
||||||
|
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, updateNamespaceMethod, customNamespace, purgeState)
|
||||||
|
invoker.InvokeFail(t, namespaceNonActive, addSubjectKeyMethod, baseAddr, keyBytes)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("setSubjectKV", func(t *testing.T) {
|
||||||
|
const key, val = "key", "val"
|
||||||
|
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, updateNamespaceMethod, customNamespace, frozenState)
|
||||||
|
invoker.InvokeFail(t, namespaceNonActive, setSubjectKVMethod, baseAddr, key, val)
|
||||||
|
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, updateNamespaceMethod, customNamespace, purgeState)
|
||||||
|
invoker.InvokeFail(t, namespaceNonActive, setSubjectKVMethod, baseAddr, key, val)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("setSubjectName", func(t *testing.T) {
|
||||||
|
const login = "testlogin"
|
||||||
|
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, updateNamespaceMethod, customNamespace, frozenState)
|
||||||
|
invoker.InvokeFail(t, namespaceNonActive, setSubjectNameMethod, baseAddr, login)
|
||||||
|
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, updateNamespaceMethod, customNamespace, purgeState)
|
||||||
|
invoker.InvokeFail(t, namespaceNonActive, setSubjectNameMethod, baseAddr, login)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
anonInvoker.InvokeFail(t, notWitnessedError, deleteSubjectMethod, subjKeyAddr)
|
anonInvoker.InvokeFail(t, notWitnessedError, deleteSubjectMethod, subjKeyAddr)
|
||||||
invoker.Invoke(t, stackitem.Null{}, deleteSubjectMethod, subjKeyAddr)
|
invoker.Invoke(t, stackitem.Null{}, deleteSubjectMethod, subjKeyAddr)
|
||||||
|
|
||||||
|
@ -514,6 +577,59 @@ func TestFrostFSID_NamespaceManagement(t *testing.T) {
|
||||||
require.Equal(t, namespace, ns.Name)
|
require.Equal(t, namespace, ns.Name)
|
||||||
require.Equal(t, "frozen", ns.State)
|
require.Equal(t, "frozen", ns.State)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("delete namespace", func(t *testing.T) {
|
||||||
|
namespace3 := "some-namespace3"
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, namespace3)
|
||||||
|
|
||||||
|
s, err = invoker.TestInvoke(t, getNamespaceMethod, namespace3)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ns := parseNamespace(t, s.Pop().Item())
|
||||||
|
require.Equal(t, namespace3, ns.Name)
|
||||||
|
|
||||||
|
t.Run("delete existing namespace not in a 'purge' state", func(t *testing.T) {
|
||||||
|
anonInvoker.InvokeFail(t, notWitnessedError, deleteNamespaceMethod, namespace3)
|
||||||
|
invoker.InvokeFail(t, namespaceShouldBeInPurgeStateError, deleteNamespaceMethod, namespace3)
|
||||||
|
})
|
||||||
|
|
||||||
|
subjKey, err := keys.NewPrivateKey()
|
||||||
|
subjKeyAddr := subjKey.PublicKey().GetScriptHash()
|
||||||
|
require.NoError(t, err)
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, ns.Name, subjKey.PublicKey().Bytes())
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, updateNamespaceMethod, namespace3, "purge")
|
||||||
|
|
||||||
|
t.Run("delete namespace with user fails", func(t *testing.T) {
|
||||||
|
invoker.InvokeFail(t, cantDeleteNonEmptyNamespceUsersPresent, deleteNamespaceMethod, namespace3)
|
||||||
|
})
|
||||||
|
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, deleteSubjectMethod, subjKeyAddr)
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, updateNamespaceMethod, namespace3, "active")
|
||||||
|
|
||||||
|
groupID1 := int64(1)
|
||||||
|
groupName1 := "group1"
|
||||||
|
invoker.Invoke(t, stackitem.Make(groupID1), createGroupMethod, ns.Name, groupName1)
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, updateNamespaceMethod, namespace3, "purge")
|
||||||
|
|
||||||
|
t.Run("delete namespace with group fails", func(t *testing.T) {
|
||||||
|
invoker.InvokeFail(t, cantDeleteNonEmptyNamespceGroupsPresent, deleteNamespaceMethod, namespace3)
|
||||||
|
})
|
||||||
|
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, deleteGroupMethod, ns.Name, groupID1)
|
||||||
|
|
||||||
|
t.Run("delete existing namespace", func(t *testing.T) {
|
||||||
|
anonInvoker.InvokeFail(t, notWitnessedError, deleteNamespaceMethod, namespace3)
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, deleteNamespaceMethod, namespace3)
|
||||||
|
invoker.InvokeFail(t, notFoundError, getNamespaceMethod, namespace3)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("delete non-existing namespace", func(t *testing.T) {
|
||||||
|
nonExistingNamespace := "non-existing-namespace"
|
||||||
|
invoker.InvokeFail(t, notFoundError, getNamespaceMethod, nonExistingNamespace)
|
||||||
|
anonInvoker.InvokeFail(t, notWitnessedError, deleteNamespaceMethod, nonExistingNamespace)
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, deleteNamespaceMethod, nonExistingNamespace)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFrostFSID_GroupManagement(t *testing.T) {
|
func TestFrostFSID_GroupManagement(t *testing.T) {
|
||||||
|
@ -635,6 +751,50 @@ func TestFrostFSID_GroupManagement(t *testing.T) {
|
||||||
groups := parseGroups(t, readIteratorAll(s))
|
groups := parseGroups(t, readIteratorAll(s))
|
||||||
require.Empty(t, groups)
|
require.Empty(t, groups)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("operations with non-active namespace", func(t *testing.T) {
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, customNamespace)
|
||||||
|
|
||||||
|
customGroupID := int64(2)
|
||||||
|
customGroupName := "customGroup"
|
||||||
|
invoker.Invoke(t, stackitem.Make(customGroupID), createGroupMethod, customNamespace, customGroupName)
|
||||||
|
|
||||||
|
subjKey, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, customNamespace, subjKey.PublicKey().Bytes())
|
||||||
|
|
||||||
|
t.Run("createGroup", func(t *testing.T) {
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, updateNamespaceMethod, customNamespace, frozenState)
|
||||||
|
invoker.InvokeFail(t, namespaceNonActive, createGroupMethod, customNamespace, customGroupName)
|
||||||
|
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, updateNamespaceMethod, customNamespace, purgeState)
|
||||||
|
invoker.InvokeFail(t, namespaceNonActive, createGroupMethod, customNamespace, customGroupName)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("addSubjectToGroup", func(t *testing.T) {
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, updateNamespaceMethod, customNamespace, frozenState)
|
||||||
|
invoker.InvokeFail(t, namespaceNonActive, addSubjectToGroupMethod, subjKey.PublicKey().GetScriptHash(), customGroupID)
|
||||||
|
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, updateNamespaceMethod, customNamespace, purgeState)
|
||||||
|
invoker.InvokeFail(t, namespaceNonActive, addSubjectToGroupMethod, subjKey.PublicKey().GetScriptHash(), customGroupID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("setGroupKV", func(t *testing.T) {
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, updateNamespaceMethod, customNamespace, frozenState)
|
||||||
|
invoker.InvokeFail(t, namespaceNonActive, setGroupKVMethod, customNamespace, customGroupID, client.IAMARNKey, "arn")
|
||||||
|
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, updateNamespaceMethod, customNamespace, purgeState)
|
||||||
|
invoker.InvokeFail(t, namespaceNonActive, setGroupKVMethod, customNamespace, customGroupID, client.IAMARNKey, "arn")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("setGroupName", func(t *testing.T) {
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, updateNamespaceMethod, customNamespace, frozenState)
|
||||||
|
invoker.InvokeFail(t, namespaceNonActive, setGroupNameMethod, customNamespace, customGroupID, "newCustomGroup")
|
||||||
|
|
||||||
|
invoker.Invoke(t, stackitem.Null{}, updateNamespaceMethod, customNamespace, purgeState)
|
||||||
|
invoker.InvokeFail(t, namespaceNonActive, setGroupNameMethod, customNamespace, customGroupID, "newCustomGroup")
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAdditionalKeyFromPrimarySubject(t *testing.T) {
|
func TestAdditionalKeyFromPrimarySubject(t *testing.T) {
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -270,7 +269,58 @@ func TestNNSRegister(t *testing.T) {
|
||||||
expected = stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte("testdomain.com"))})
|
expected = stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte("testdomain.com"))})
|
||||||
c.CheckTxNotificationEvent(t, tx, 4, state.NotificationEvent{ScriptHash: c.Hash, Name: "DeleteDomain", Item: expected})
|
c.CheckTxNotificationEvent(t, tx, 4, state.NotificationEvent{ScriptHash: c.Hash, Name: "DeleteDomain", Item: expected})
|
||||||
|
|
||||||
c.InvokeFail(t, "token not found", "getRecords", "testdomain.com", int64(nns.SOA))
|
c.Invoke(t, stackitem.Null{}, "getRecords", "testdomain.com", int64(nns.SOA))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteRecords_SubdomainNoRegister(t *testing.T) {
|
||||||
|
c := newNNSInvoker(t, true)
|
||||||
|
|
||||||
|
refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104)
|
||||||
|
c.Invoke(t, true, "register",
|
||||||
|
"test.com", c.CommitteeHash,
|
||||||
|
"myemail@frostfs.info", refresh, retry, expire, ttl)
|
||||||
|
|
||||||
|
checkRecords := func(t *testing.T, domain string, typ nns.RecordType, expected ...string) {
|
||||||
|
s, err := c.TestInvoke(t, "getRecords", domain, int64(typ))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if len(expected) == 0 {
|
||||||
|
_, ok := s.Pop().Item().(stackitem.Null)
|
||||||
|
require.True(t, ok, "expected 0 records")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
arr, ok := s.Pop().Value().([]stackitem.Item)
|
||||||
|
require.True(t, ok, "expected an array '%s' %d", domain, typ)
|
||||||
|
|
||||||
|
actual := make([]string, len(arr))
|
||||||
|
for i := range actual {
|
||||||
|
b, err := arr[i].TryBytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
actual[i] = string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.ElementsMatch(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Invoke(t, stackitem.Null{}, "addRecord", "a.test.com", int64(nns.TXT), "recA1")
|
||||||
|
c.Invoke(t, stackitem.Null{}, "addRecord", "a.test.com", int64(nns.TXT), "recA2")
|
||||||
|
c.Invoke(t, stackitem.Null{}, "addRecord", "b.test.com", int64(nns.TXT), "recB")
|
||||||
|
c.Invoke(t, stackitem.Null{}, "addRecord", "test.com", int64(nns.TXT), "recTop")
|
||||||
|
|
||||||
|
{ // Delete subdomain records.
|
||||||
|
c.Invoke(t, stackitem.Null{}, "deleteRecords", "a.test.com", int64(nns.TXT))
|
||||||
|
checkRecords(t, "test.com", nns.TXT, "recTop")
|
||||||
|
checkRecords(t, "a.test.com", nns.TXT)
|
||||||
|
checkRecords(t, "b.test.com", nns.TXT, "recB")
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // Delete domain records.
|
||||||
|
c.Invoke(t, stackitem.Null{}, "deleteRecords", "test.com", int64(nns.TXT))
|
||||||
|
checkRecords(t, "test.com", nns.TXT)
|
||||||
|
checkRecords(t, "a.test.com", nns.TXT)
|
||||||
|
checkRecords(t, "b.test.com", nns.TXT, "recB")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteDomain(t *testing.T) {
|
func TestDeleteDomain(t *testing.T) {
|
||||||
|
@ -387,6 +437,10 @@ func TestTLDRecord(t *testing.T) {
|
||||||
|
|
||||||
result := []stackitem.Item{stackitem.NewByteArray([]byte("1.2.3.4"))}
|
result := []stackitem.Item{stackitem.NewByteArray([]byte("1.2.3.4"))}
|
||||||
c.Invoke(t, result, "resolve", "com", int64(nns.A))
|
c.Invoke(t, result, "resolve", "com", int64(nns.A))
|
||||||
|
|
||||||
|
t.Run("subdomain", func(t *testing.T) {
|
||||||
|
c.Invoke(t, stackitem.Null{}, "addRecord", "a.com", int64(nns.TXT), "test=frostfs")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNNSRegisterMulti(t *testing.T) {
|
func TestNNSRegisterMulti(t *testing.T) {
|
||||||
|
@ -500,45 +554,6 @@ func TestNNSGetAllRecords(t *testing.T) {
|
||||||
require.False(t, iter.Next())
|
require.False(t, iter.Next())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExpiration(t *testing.T) {
|
|
||||||
c := newNNSInvoker(t, true)
|
|
||||||
|
|
||||||
refresh, retry, expire, ttl := int64(101), int64(102), int64(msPerYear/1000*10), int64(104)
|
|
||||||
c.Invoke(t, true, "register",
|
|
||||||
"testdomain.com", c.CommitteeHash,
|
|
||||||
"myemail@frostfs.info", refresh, retry, expire, ttl)
|
|
||||||
|
|
||||||
checkProperties := func(t *testing.T, expiration uint64) {
|
|
||||||
expected := stackitem.NewMapWithValue([]stackitem.MapElement{
|
|
||||||
{Key: stackitem.Make("name"), Value: stackitem.Make("testdomain.com")},
|
|
||||||
{Key: stackitem.Make("expiration"), Value: stackitem.Make(expiration)},
|
|
||||||
})
|
|
||||||
s, err := c.TestInvoke(t, "properties", "testdomain.com")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, expected.Value(), s.Top().Item().Value())
|
|
||||||
}
|
|
||||||
|
|
||||||
top := c.TopBlock(t)
|
|
||||||
expiration := top.Timestamp + uint64(expire*1000)
|
|
||||||
checkProperties(t, expiration)
|
|
||||||
|
|
||||||
b := c.NewUnsignedBlock(t)
|
|
||||||
b.Timestamp = expiration - 2 // test invoke is done with +1 timestamp
|
|
||||||
require.NoError(t, c.Chain.AddBlock(c.SignBlock(b)))
|
|
||||||
checkProperties(t, expiration)
|
|
||||||
|
|
||||||
b = c.NewUnsignedBlock(t)
|
|
||||||
b.Timestamp = expiration - 1
|
|
||||||
require.NoError(t, c.Chain.AddBlock(c.SignBlock(b)))
|
|
||||||
|
|
||||||
_, err := c.TestInvoke(t, "properties", "testdomain.com")
|
|
||||||
require.Error(t, err)
|
|
||||||
require.True(t, strings.Contains(err.Error(), "name has expired"))
|
|
||||||
|
|
||||||
c.InvokeFail(t, "name has expired", "getAllRecords", "testdomain.com")
|
|
||||||
c.InvokeFail(t, "name has expired", "ownerOf", "testdomain.com")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNNSSetAdmin(t *testing.T) {
|
func TestNNSSetAdmin(t *testing.T) {
|
||||||
c := newNNSInvoker(t, true)
|
c := newNNSInvoker(t, true)
|
||||||
|
|
||||||
|
@ -640,31 +655,6 @@ func TestNNSIsAvailable(t *testing.T) {
|
||||||
c.InvokeFail(t, "domain name too long", "isAvailable", getTooLongDomainName(255))
|
c.InvokeFail(t, "domain name too long", "isAvailable", getTooLongDomainName(255))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNNSRenew(t *testing.T) {
|
|
||||||
c := newNNSInvoker(t, true)
|
|
||||||
|
|
||||||
acc := c.NewAccount(t)
|
|
||||||
c1 := c.WithSigners(c.Committee, acc)
|
|
||||||
refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104)
|
|
||||||
c1.Invoke(t, true, "register",
|
|
||||||
"testdomain.com", c.CommitteeHash,
|
|
||||||
"myemail@frostfs.info", refresh, retry, expire, ttl)
|
|
||||||
|
|
||||||
const msPerYear = 365 * 24 * time.Hour / time.Millisecond
|
|
||||||
b := c.TopBlock(t)
|
|
||||||
ts := b.Timestamp + uint64(expire*1000) + uint64(msPerYear)
|
|
||||||
|
|
||||||
cAcc := c.WithSigners(acc)
|
|
||||||
cAcc.InvokeFail(t, "not witnessed by admin", "renew", "testdomain.com")
|
|
||||||
c1.Invoke(t, ts, "renew", "testdomain.com")
|
|
||||||
expected := stackitem.NewMapWithValue([]stackitem.MapElement{
|
|
||||||
{Key: stackitem.Make("name"), Value: stackitem.Make("testdomain.com")},
|
|
||||||
{Key: stackitem.Make("expiration"), Value: stackitem.Make(ts)},
|
|
||||||
})
|
|
||||||
cAcc.Invoke(t, expected, "properties", "testdomain.com")
|
|
||||||
c.InvokeFail(t, "domain name too long", "renew", getTooLongDomainName(255))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNNSResolve(t *testing.T) {
|
func TestNNSResolve(t *testing.T) {
|
||||||
c := newNNSInvoker(t, true)
|
c := newNNSInvoker(t, true)
|
||||||
|
|
||||||
|
|
|
@ -5,35 +5,76 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/policy"
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/policy"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"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/wallet"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
const policyPath = "../policy"
|
const policyPath = "../policy"
|
||||||
|
|
||||||
func deployPolicyContract(t *testing.T, e *neotest.Executor) util.Uint160 {
|
type policyContracts struct {
|
||||||
|
policy *neotest.ContractInvoker
|
||||||
|
frostfsid *neotest.ContractInvoker
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPolicyInvokers(t *testing.T) *policyContracts {
|
||||||
|
e := newExecutor(t)
|
||||||
|
|
||||||
|
n := deployNNSContract(t, e)
|
||||||
|
ffid := deployFrostfsid(t, e)
|
||||||
|
polic := deployPolicyContract(t, e)
|
||||||
|
|
||||||
|
n.Invoke(t, true, "register",
|
||||||
|
nns.FrostfsIDNNSName, n.CommitteeHash,
|
||||||
|
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
|
||||||
|
n.Invoke(t, stackitem.Null{}, "addRecord",
|
||||||
|
nns.FrostfsIDNNSName, int64(nns.TXT), ffid.Hash.StringLE())
|
||||||
|
n.Invoke(t, stackitem.Null{}, "addRecord",
|
||||||
|
nns.FrostfsIDNNSName, int64(nns.TXT), address.Uint160ToString(ffid.Hash))
|
||||||
|
|
||||||
|
return &policyContracts{
|
||||||
|
policy: polic,
|
||||||
|
frostfsid: ffid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func deployNNSContract(t *testing.T, e *neotest.Executor) *neotest.ContractInvoker {
|
||||||
|
ctrNNS := neotest.CompileFile(t, e.CommitteeHash, nnsPath, path.Join(nnsPath, "config.yml"))
|
||||||
|
e.DeployContract(t, ctrNNS, nil)
|
||||||
|
|
||||||
|
n := e.CommitteeInvoker(ctrNNS.Hash)
|
||||||
|
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func deployFrostfsid(t *testing.T, e *neotest.Executor) *neotest.ContractInvoker {
|
||||||
|
acc, err := wallet.NewAccount()
|
||||||
|
require.NoError(t, err)
|
||||||
|
args := make([]any, 5)
|
||||||
|
args[0] = acc.ScriptHash()
|
||||||
|
frostfsID := neotest.CompileFile(t, e.CommitteeHash, frostfsidPath, path.Join(frostfsidPath, "config.yml"))
|
||||||
|
e.DeployContract(t, frostfsID, args)
|
||||||
|
return e.CommitteeInvoker(frostfsID.Hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deployPolicyContract(t *testing.T, e *neotest.Executor) *neotest.ContractInvoker {
|
||||||
cfgPath := path.Join(policyPath, "config.yml")
|
cfgPath := path.Join(policyPath, "config.yml")
|
||||||
c := neotest.CompileFile(t, e.CommitteeHash, policyPath, cfgPath)
|
c := neotest.CompileFile(t, e.CommitteeHash, policyPath, cfgPath)
|
||||||
e.DeployContract(t, c, []any{nil})
|
e.DeployContract(t, c, []any{nil})
|
||||||
return c.Hash
|
return e.CommitteeInvoker(c.Hash)
|
||||||
}
|
|
||||||
|
|
||||||
func newPolicyInvoker(t *testing.T) *neotest.ContractInvoker {
|
|
||||||
e := newExecutor(t)
|
|
||||||
h := deployPolicyContract(t, e)
|
|
||||||
return e.CommitteeInvoker(h)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPolicy(t *testing.T) {
|
func TestPolicy(t *testing.T) {
|
||||||
e := newPolicyInvoker(t)
|
c := newPolicyInvokers(t)
|
||||||
|
|
||||||
checkChainsIteratorByPrefix(t, e, policy.Namespace, "mynamespace", "ingress", [][]byte{})
|
checkChainsIteratorByPrefix(t, c.policy, policy.Namespace, "mynamespace", "ingress", [][]byte{})
|
||||||
checkChainsIteratorByPrefix(t, e, policy.Container, "cnr1", "ingress", [][]byte{})
|
checkChainsIteratorByPrefix(t, c.policy, policy.Container, "cnr1", "ingress", [][]byte{})
|
||||||
|
|
||||||
// Policies are opaque to the contract and are just raw bytes to store.
|
// Policies are opaque to the contract and are just raw bytes to store.
|
||||||
p1 := []byte("chain1")
|
p1 := []byte("chain1")
|
||||||
|
@ -41,82 +82,97 @@ func TestPolicy(t *testing.T) {
|
||||||
p3 := []byte("chain3")
|
p3 := []byte("chain3")
|
||||||
p33 := []byte("chain33")
|
p33 := []byte("chain33")
|
||||||
|
|
||||||
e.Invoke(t, stackitem.Null{}, "addChain", policy.Namespace, "mynamespace", "ingress:123", p1)
|
c.frostfsid.Invoke(t, stackitem.Null{}, "createNamespace", "mynamespace")
|
||||||
checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")})
|
|
||||||
checkChains(t, e, "mynamespace", "", "ingress", [][]byte{p1})
|
|
||||||
checkChains(t, e, "mynamespace", "", "all", nil)
|
|
||||||
|
|
||||||
e.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule2", p2)
|
c.policy.Invoke(t, stackitem.Null{}, "addChain", policy.Namespace, "mynamespace", "ingress:123", p1)
|
||||||
checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")})
|
checkTargets(t, c.policy, policy.Namespace, [][]byte{[]byte("mynamespace")})
|
||||||
checkTargets(t, e, policy.Container, [][]byte{[]byte("cnr1")})
|
checkChains(t, c.policy, "mynamespace", "", "ingress", [][]byte{p1})
|
||||||
checkChains(t, e, "mynamespace", "", "ingress", [][]byte{p1}) // Only namespace chains.
|
checkChains(t, c.policy, "mynamespace", "", "all", nil)
|
||||||
checkChains(t, e, "mynamespace", "cnr1", "ingress", [][]byte{p1, p2})
|
|
||||||
checkChains(t, e, "mynamespace", "cnr1", "all", nil) // No chains attached to 'all'.
|
|
||||||
checkChains(t, e, "mynamespace", "cnr2", "ingress", [][]byte{p1}) // Only namespace, no chains for the container.
|
|
||||||
|
|
||||||
e.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p3)
|
c.policy.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule2", p2)
|
||||||
checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")})
|
checkTargets(t, c.policy, policy.Namespace, [][]byte{[]byte("mynamespace")})
|
||||||
checkTargets(t, e, policy.Container, [][]byte{[]byte("cnr1")})
|
checkTargets(t, c.policy, policy.Container, [][]byte{[]byte("cnr1")})
|
||||||
checkChains(t, e, "mynamespace", "cnr1", "ingress", [][]byte{p1, p2, p3})
|
checkChains(t, c.policy, "mynamespace", "", "ingress", [][]byte{p1}) // Only namespace chains.
|
||||||
|
checkChains(t, c.policy, "mynamespace", "cnr1", "ingress", [][]byte{p1, p2})
|
||||||
|
checkChains(t, c.policy, "mynamespace", "cnr1", "all", nil) // No chains attached to 'all'.
|
||||||
|
checkChains(t, c.policy, "mynamespace", "cnr2", "ingress", [][]byte{p1}) // Only namespace, no chains for the container.
|
||||||
|
|
||||||
e.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p33)
|
c.policy.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p3)
|
||||||
checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")})
|
checkTargets(t, c.policy, policy.Namespace, [][]byte{[]byte("mynamespace")})
|
||||||
checkTargets(t, e, policy.Container, [][]byte{[]byte("cnr1")})
|
checkTargets(t, c.policy, policy.Container, [][]byte{[]byte("cnr1")})
|
||||||
checkChain(t, e, policy.Container, "cnr1", "ingress:myrule3", p33)
|
checkChains(t, c.policy, "mynamespace", "cnr1", "ingress", [][]byte{p1, p2, p3})
|
||||||
checkChains(t, e, "mynamespace", "cnr1", "ingress", [][]byte{p1, p2, p33}) // Override chain.
|
|
||||||
checkChainsByPrefix(t, e, policy.Container, "cnr1", "", [][]byte{p2, p33})
|
|
||||||
checkChainsByPrefix(t, e, policy.IAM, "", "", nil)
|
|
||||||
checkChainKeys(t, e, policy.Container, "cnr1", []string{"ingress:myrule2", "ingress:myrule3"})
|
|
||||||
|
|
||||||
checkChainsIteratorByPrefix(t, e, policy.Container, "cnr1", "ingress:myrule3", [][]byte{p33})
|
c.policy.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p33)
|
||||||
checkChainsIteratorByPrefix(t, e, policy.Container, "cnr1", "ingress", [][]byte{p2, p33})
|
checkTargets(t, c.policy, policy.Namespace, [][]byte{[]byte("mynamespace")})
|
||||||
|
checkTargets(t, c.policy, policy.Container, [][]byte{[]byte("cnr1")})
|
||||||
|
checkChain(t, c.policy, policy.Container, "cnr1", "ingress:myrule3", p33)
|
||||||
|
checkChains(t, c.policy, "mynamespace", "cnr1", "ingress", [][]byte{p1, p2, p33}) // Override chain.
|
||||||
|
checkChainsByPrefix(t, c.policy, policy.Container, "cnr1", "", [][]byte{p2, p33})
|
||||||
|
checkChainsByPrefix(t, c.policy, policy.IAM, "", "", nil)
|
||||||
|
checkChainKeys(t, c.policy, policy.Container, "cnr1", []string{"ingress:myrule2", "ingress:myrule3"})
|
||||||
|
|
||||||
|
checkChainsIteratorByPrefix(t, c.policy, policy.Container, "cnr1", "ingress:myrule3", [][]byte{p33})
|
||||||
|
checkChainsIteratorByPrefix(t, c.policy, policy.Container, "cnr1", "ingress", [][]byte{p2, p33})
|
||||||
|
|
||||||
t.Run("removal", func(t *testing.T) {
|
t.Run("removal", func(t *testing.T) {
|
||||||
t.Run("wrong name", func(t *testing.T) {
|
t.Run("wrong name", func(t *testing.T) {
|
||||||
e.Invoke(t, stackitem.Null{}, "removeChain", policy.Namespace, "mynamespace", "ingress")
|
c.policy.Invoke(t, stackitem.Null{}, "removeChain", policy.Namespace, "mynamespace", "ingress")
|
||||||
checkChains(t, e, "mynamespace", "", "ingress", [][]byte{p1})
|
checkChains(t, c.policy, "mynamespace", "", "ingress", [][]byte{p1})
|
||||||
})
|
})
|
||||||
|
|
||||||
e.Invoke(t, stackitem.Null{}, "removeChain", policy.Namespace, "mynamespace", "ingress:123")
|
c.policy.Invoke(t, stackitem.Null{}, "removeChain", policy.Namespace, "mynamespace", "ingress:123")
|
||||||
checkChains(t, e, "mynamespace", "", "ingress", nil)
|
checkChains(t, c.policy, "mynamespace", "", "ingress", nil)
|
||||||
checkChains(t, e, "mynamespace", "cnr1", "ingress", [][]byte{p2, p33}) // Container chains still exist.
|
checkChains(t, c.policy, "mynamespace", "cnr1", "ingress", [][]byte{p2, p33}) // Container chains still exist.
|
||||||
|
|
||||||
// Remove by prefix.
|
// Remove by prefix.
|
||||||
e.Invoke(t, stackitem.Null{}, "removeChainsByPrefix", policy.Container, "cnr1", "ingress")
|
c.policy.Invoke(t, stackitem.Null{}, "removeChainsByPrefix", policy.Container, "cnr1", "ingress")
|
||||||
checkChains(t, e, "mynamespace", "cnr1", "ingress", nil)
|
checkChains(t, c.policy, "mynamespace", "cnr1", "ingress", nil)
|
||||||
|
|
||||||
// Remove by prefix.
|
// Remove by prefix.
|
||||||
e.Invoke(t, stackitem.Null{}, "removeChainsByPrefix", policy.Container, "cnr1", "ingress")
|
c.policy.Invoke(t, stackitem.Null{}, "removeChainsByPrefix", policy.Container, "cnr1", "ingress")
|
||||||
checkChains(t, e, "mynamespace", "cnr1", "ingress", nil)
|
checkChains(t, c.policy, "mynamespace", "cnr1", "ingress", nil)
|
||||||
|
|
||||||
checkTargets(t, e, policy.Namespace, [][]byte{})
|
checkTargets(t, c.policy, policy.Namespace, [][]byte{})
|
||||||
checkTargets(t, e, policy.Container, [][]byte{})
|
checkTargets(t, c.policy, policy.Container, [][]byte{})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("add again after removal", func(t *testing.T) {
|
t.Run("add again after removal", func(t *testing.T) {
|
||||||
e.Invoke(t, stackitem.Null{}, "addChain", policy.Namespace, "mynamespace", "ingress:123", p1)
|
c.policy.Invoke(t, stackitem.Null{}, "addChain", policy.Namespace, "mynamespace", "ingress:123", p1)
|
||||||
e.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p33)
|
c.policy.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p33)
|
||||||
|
|
||||||
checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")})
|
checkTargets(t, c.policy, policy.Namespace, [][]byte{[]byte("mynamespace")})
|
||||||
checkTargets(t, e, policy.Container, [][]byte{[]byte("cnr1")})
|
checkTargets(t, c.policy, policy.Container, [][]byte{[]byte("cnr1")})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("add chain for non-active namespace", func(t *testing.T) {
|
||||||
|
c.frostfsid.Invoke(t, stackitem.Null{}, "createNamespace", "nsdisabled")
|
||||||
|
|
||||||
|
c.frostfsid.Invoke(t, stackitem.Null{}, "updateNamespace", "nsdisabled", "frozen")
|
||||||
|
c.policy.InvokeFail(t, "namespace is non-active", "addChain", policy.Namespace, "nsdisabled", "ingress:3", p1)
|
||||||
|
|
||||||
|
c.frostfsid.Invoke(t, stackitem.Null{}, "updateNamespace", "nsdisabled", "purge")
|
||||||
|
c.policy.InvokeFail(t, "namespace is non-active", "addChain", policy.Namespace, "nsdisabled", "ingress:3", p1)
|
||||||
|
|
||||||
|
c.frostfsid.Invoke(t, stackitem.Null{}, "updateNamespace", "nsdisabled", "active")
|
||||||
|
c.policy.Invoke(t, stackitem.Null{}, "addChain", policy.Namespace, "nsdisabled", "ingress:3", p1)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAutorization(t *testing.T) {
|
func TestAutorization(t *testing.T) {
|
||||||
e := newPolicyInvoker(t)
|
c := newPolicyInvokers(t)
|
||||||
|
|
||||||
e.Invoke(t, stackitem.Null{}, "getAdmin")
|
c.policy.Invoke(t, stackitem.Null{}, "getAdmin")
|
||||||
|
|
||||||
s := e.NewAccount(t, 1_0000_0000)
|
s := c.policy.NewAccount(t, 1_0000_0000)
|
||||||
c := e.WithSigners(s)
|
cs := c.policy.WithSigners(s)
|
||||||
|
|
||||||
args := []any{policy.Container, "cnr1", "ingress:myrule3", []byte("opaque")}
|
args := []any{policy.Container, "cnr1", "ingress:myrule3", []byte("opaque")}
|
||||||
c.InvokeFail(t, policy.ErrNotAuthorized, "addChain", args...)
|
cs.InvokeFail(t, policy.ErrNotAuthorized, "addChain", args...)
|
||||||
|
|
||||||
e.Invoke(t, stackitem.Null{}, "setAdmin", s.ScriptHash())
|
c.policy.Invoke(t, stackitem.Null{}, "setAdmin", s.ScriptHash())
|
||||||
e.Invoke(t, stackitem.NewBuffer(s.ScriptHash().BytesBE()), "getAdmin")
|
c.policy.Invoke(t, stackitem.NewBuffer(s.ScriptHash().BytesBE()), "getAdmin")
|
||||||
|
|
||||||
c.Invoke(t, stackitem.Null{}, "addChain", args...)
|
cs.Invoke(t, stackitem.Null{}, "addChain", args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkChains(t *testing.T, e *neotest.ContractInvoker, namespace, container, name string, expected [][]byte) {
|
func checkChains(t *testing.T, e *neotest.ContractInvoker, namespace, container, name string, expected [][]byte) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue