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
|
||||
- Field `state` to a namespace to indicate its' lifecycle stage (#154).
|
||||
- Method `UpdateNamespace` to adjust namespace state (#154).
|
||||
- Method `DeleteNamespace` to remove existing namespace (#168).
|
||||
|
||||
### Changed
|
||||
### 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 (
|
||||
major = 0
|
||||
minor = 21
|
||||
patch = 2
|
||||
patch = 4
|
||||
|
||||
// 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
|
||||
|
|
|
@ -114,6 +114,7 @@ const (
|
|||
|
||||
createNamespaceMethod = "createNamespace"
|
||||
updateNamespaceMethod = "updateNamespace"
|
||||
deleteNamespaceMethod = "deleteNamespace"
|
||||
getNamespaceMethod = "getNamespace"
|
||||
getNamespaceExtendedMethod = "getNamespaceExtended"
|
||||
listNamespacesMethod = "listNamespaces"
|
||||
|
@ -463,6 +464,18 @@ func (c Client) UpdateNamespaceCall(namespace string, state string) (method stri
|
|||
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.
|
||||
func (c Client) ListNamespaces() ([]*Namespace, error) {
|
||||
items, err := commonclient.ReadIteratorItems(c.act, iteratorBatchSize, c.contract, listNamespacesMethod)
|
||||
|
|
|
@ -185,7 +185,6 @@ func ParseNamespaceExtended(structArr []stackitem.Item) (*NamespaceExtended, err
|
|||
}, nil
|
||||
}
|
||||
|
||||
// TODO: [cleanup] (#151) rewrite this method after new release.
|
||||
func parseNamespace(structArr []stackitem.Item, stateIndex int) (*Namespace, error) {
|
||||
name, err := structArr[0].TryBytes()
|
||||
if err != nil {
|
||||
|
@ -193,7 +192,7 @@ func parseNamespace(structArr []stackitem.Item, stateIndex int) (*Namespace, err
|
|||
}
|
||||
|
||||
nsState := Active
|
||||
if len(structArr) == stateIndex+1 {
|
||||
if len(structArr) >= stateIndex+1 {
|
||||
nsStateBytes, err := structArr[stateIndex].TryBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -72,6 +72,10 @@ events:
|
|||
type: String
|
||||
- name: state
|
||||
type: String
|
||||
- name: DeleteNamespace
|
||||
parameters:
|
||||
- name: namespace
|
||||
type: String
|
||||
- name: AddSubjectToNamespace
|
||||
parameters:
|
||||
- name: subjectAddress
|
||||
|
|
|
@ -21,6 +21,7 @@ FrostFSID contract does not produce notifications to process.
|
|||
| `c` | Int | group id counter |
|
||||
| `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 |
|
||||
| `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.
|
||||
// The name must match the following regex pattern: ^[\w+=,.@-]{1,64}$
|
||||
// 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 {
|
||||
PrimaryKey interop.PublicKey
|
||||
AdditionalKeys []interop.PublicKey
|
||||
|
@ -99,10 +100,15 @@ const (
|
|||
groupCounterKey = 'c'
|
||||
namespaceGroupsNamesPrefix = 'm'
|
||||
addressPrefix = 'A'
|
||||
subjectToAddKeyPrefix = 'd'
|
||||
|
||||
nsActiveState = "active"
|
||||
nsFrozenState = "frozen"
|
||||
nsPurgeState = "purge"
|
||||
)
|
||||
|
||||
var dummyValue = []byte{1}
|
||||
|
||||
func _deploy(data any, isUpdate bool) {
|
||||
ctx := storage.GetContext()
|
||||
|
||||
|
@ -154,7 +160,23 @@ func _deploy(data any, isUpdate bool) {
|
|||
storage.Put(ctx, groupCounterKey, maxGroupID)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -209,6 +231,7 @@ func Version() int {
|
|||
// CreateSubject creates a new subject in the specified namespace with the provided public key.
|
||||
func CreateSubject(ns string, key interop.PublicKey) {
|
||||
ctx := storage.GetContext()
|
||||
checkNamespaceState(ns)
|
||||
checkContractOwner(ctx)
|
||||
|
||||
if len(key) != interop.PublicKeyCompressedLen {
|
||||
|
@ -222,7 +245,7 @@ func CreateSubject(ns string, key interop.PublicKey) {
|
|||
panic("subject already exists")
|
||||
}
|
||||
|
||||
saPrefix := subjectAdditionalPrefix(key)
|
||||
saPrefix := additionalKeyToSubjectPrefix(key)
|
||||
it := storage.Find(ctx, saPrefix, storage.KeysOnly)
|
||||
for iterator.Next(it) {
|
||||
panic("key is occupied")
|
||||
|
@ -247,7 +270,7 @@ func CreateSubject(ns string, key interop.PublicKey) {
|
|||
storage.Put(ctx, sKey, std.Serialize(subj))
|
||||
|
||||
nsSubjKey := namespaceSubjectKey(ns, addr)
|
||||
storage.Put(ctx, nsSubjKey, []byte{1})
|
||||
storage.Put(ctx, nsSubjKey, dummyValue)
|
||||
storage.Put(ctx, allAddressKey, true)
|
||||
|
||||
runtime.Notify("CreateSubject", interop.Hash160(addr))
|
||||
|
@ -270,13 +293,13 @@ func AddSubjectKey(addr interop.Hash160, key interop.PublicKey) {
|
|||
panic("key is occupied")
|
||||
}
|
||||
|
||||
saKey := subjectAdditionalKey(key, addr)
|
||||
saKey := additionalKeyToSubjectKey(key, addr)
|
||||
data := storage.Get(ctx, saKey).([]byte)
|
||||
if data != nil {
|
||||
panic("key already added")
|
||||
}
|
||||
|
||||
storage.Put(ctx, saKey, []byte{1})
|
||||
storage.Put(ctx, saKey, dummyValue)
|
||||
|
||||
sKey := subjectKeyFromAddr(addr)
|
||||
data = storage.Get(ctx, sKey).([]byte)
|
||||
|
@ -284,9 +307,9 @@ func AddSubjectKey(addr interop.Hash160, key interop.PublicKey) {
|
|||
panic("address not found")
|
||||
}
|
||||
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)
|
||||
runtime.Notify("AddSubjectKey", addr, key)
|
||||
}
|
||||
|
@ -303,7 +326,7 @@ func RemoveSubjectKey(addr interop.Hash160, key interop.PublicKey) {
|
|||
panic("incorrect public key length")
|
||||
}
|
||||
|
||||
saKey := subjectAdditionalKey(key, addr)
|
||||
saKey := additionalKeyToSubjectKey(key, addr)
|
||||
data := storage.Get(ctx, saKey).([]byte)
|
||||
if data == nil {
|
||||
panic("key already removed")
|
||||
|
@ -316,17 +339,14 @@ func RemoveSubjectKey(addr interop.Hash160, key interop.PublicKey) {
|
|||
if data == nil {
|
||||
panic("address not found")
|
||||
}
|
||||
subject := std.Deserialize(data).(Subject)
|
||||
|
||||
var additionalKeys []interop.PublicKey
|
||||
for i := 0; i < len(subject.AdditionalKeys); i++ {
|
||||
if !common.BytesEqual(subject.AdditionalKeys[i], key) {
|
||||
additionalKeys = append(additionalKeys, subject.AdditionalKeys[i])
|
||||
subjToAddKey := subjectToAdditionalKeyKey(addr, key)
|
||||
data = storage.Get(ctx, subjToAddKey).([]byte)
|
||||
if data == nil {
|
||||
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)))
|
||||
runtime.Notify("RemoveSubjectKey", addr, key)
|
||||
}
|
||||
|
@ -347,6 +367,7 @@ func SetSubjectName(addr interop.Hash160, name string) {
|
|||
}
|
||||
|
||||
subject := std.Deserialize(data).(Subject)
|
||||
checkNamespaceState(subject.Namespace)
|
||||
oldName := subject.Name
|
||||
subject.Name = name
|
||||
storage.Put(ctx, sKey, std.Serialize(subject))
|
||||
|
@ -372,6 +393,7 @@ func SetSubjectKV(addr interop.Hash160, key, val string) {
|
|||
}
|
||||
|
||||
subject := std.Deserialize(data).(Subject)
|
||||
checkNamespaceState(subject.Namespace)
|
||||
if subject.KV == nil {
|
||||
subject.KV = map[string]string{}
|
||||
}
|
||||
|
@ -419,8 +441,10 @@ func DeleteSubject(addr interop.Hash160) {
|
|||
}
|
||||
subj := std.Deserialize(data).(Subject)
|
||||
|
||||
subj.AdditionalKeys = getSubjectAdditionalKeys(ctx, addr)
|
||||
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(addr))
|
||||
|
@ -442,15 +466,17 @@ func GetSubject(addr interop.Hash160) Subject {
|
|||
sKey := subjectKeyFromAddr(addr)
|
||||
data := storage.Get(ctx, sKey).([]byte)
|
||||
if data == nil {
|
||||
a := getPrimaryAddr(ctx, addr)
|
||||
sKey = subjectKeyFromAddr(a)
|
||||
addr = getPrimaryAddr(ctx, addr)
|
||||
sKey = subjectKeyFromAddr(addr)
|
||||
data = storage.Get(ctx, sKey).([]byte)
|
||||
if data == nil {
|
||||
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.
|
||||
|
@ -496,14 +522,18 @@ func GetSubjectByKey(key interop.PublicKey) Subject {
|
|||
sKey := subjectKey(key)
|
||||
data := storage.Get(ctx, sKey).([]byte)
|
||||
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))
|
||||
sKey = subjectKeyFromAddr(addr)
|
||||
data = storage.Get(ctx, sKey).([]byte)
|
||||
if data != nil {
|
||||
return std.Deserialize(data).(Subject)
|
||||
subj := std.Deserialize(data).(Subject)
|
||||
subj.AdditionalKeys = getSubjectAdditionalKeys(ctx, addr)
|
||||
return subj
|
||||
}
|
||||
|
||||
panic("subject not found")
|
||||
|
@ -518,6 +548,20 @@ func getPrimaryAddr(ctx storage.Context, addr interop.Hash160) interop.Hash160 {
|
|||
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.
|
||||
func GetSubjectByName(ns, name string) Subject {
|
||||
key := GetSubjectKeyByName(ns, name)
|
||||
|
@ -595,6 +639,36 @@ func CreateNamespace(ns string) {
|
|||
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.
|
||||
func UpdateNamespace(ns string, state string) {
|
||||
ctx := storage.GetContext()
|
||||
|
@ -668,6 +742,7 @@ func ListNamespaceSubjects(ns string) iterator.Iterator {
|
|||
// CreateGroup creates a new group within the specified namespace.
|
||||
func CreateGroup(ns, group string) int {
|
||||
ctx := storage.GetContext()
|
||||
checkNamespaceState(ns)
|
||||
checkContractOwner(ctx)
|
||||
|
||||
if group == "" {
|
||||
|
@ -781,6 +856,7 @@ func SetGroupName(ns string, groupID int, name string) {
|
|||
}
|
||||
|
||||
gr := std.Deserialize(data).(Group)
|
||||
checkNamespaceState(gr.Namespace)
|
||||
oldName := gr.Name
|
||||
gr.Name = name
|
||||
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)
|
||||
checkNamespaceState(gr.Namespace)
|
||||
if gr.KV == nil {
|
||||
gr.KV = map[string]string{}
|
||||
}
|
||||
|
@ -849,6 +926,7 @@ func AddSubjectToGroup(addr interop.Hash160, groupID int) {
|
|||
panic("subject not found")
|
||||
}
|
||||
subject := std.Deserialize(data).(Subject)
|
||||
checkNamespaceState(subject.Namespace)
|
||||
|
||||
gKey := groupKey(subject.Namespace, groupID)
|
||||
data = storage.Get(ctx, gKey).([]byte)
|
||||
|
@ -862,7 +940,7 @@ func AddSubjectToGroup(addr interop.Hash160, groupID int) {
|
|||
}
|
||||
|
||||
gsKey := groupSubjectKey(subject.Namespace, groupID, addr)
|
||||
storage.Put(ctx, gsKey, []byte{1})
|
||||
storage.Put(ctx, gsKey, dummyValue)
|
||||
|
||||
runtime.Notify("AddSubjectToGroup", addr, subject.Namespace, groupID)
|
||||
}
|
||||
|
@ -1031,15 +1109,25 @@ func subjectKeyFromAddr(addr interop.Hash160) []byte {
|
|||
return append([]byte{subjectKeysPrefix}, addr...)
|
||||
}
|
||||
|
||||
func subjectAdditionalKey(additionalKey interop.PublicKey, primeAddr interop.Hash160) []byte {
|
||||
return append(subjectAdditionalPrefix(additionalKey), primeAddr...)
|
||||
func additionalKeyToSubjectKey(additionalKey interop.PublicKey, primeAddr interop.Hash160) []byte {
|
||||
return append(additionalKeyToSubjectPrefix(additionalKey), primeAddr...)
|
||||
}
|
||||
|
||||
func subjectAdditionalPrefix(additionalKey interop.PublicKey) []byte {
|
||||
func additionalKeyToSubjectPrefix(additionalKey interop.PublicKey) []byte {
|
||||
addr := contract.CreateStandardAccount(additionalKey)
|
||||
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 {
|
||||
return namespaceKeyFromHash(ripemd160Hash(ns))
|
||||
}
|
||||
|
@ -1126,7 +1214,6 @@ func addressKey(address []byte) []byte {
|
|||
return append([]byte{addressPrefix}, address...)
|
||||
}
|
||||
|
||||
// TODO: [cleanup] (#151) remove this migration after new release.
|
||||
func migrateNamespacesState(ctx storage.Context) {
|
||||
it := storage.Find(ctx, []byte{namespaceKeysPrefix}, storage.None)
|
||||
|
||||
|
@ -1149,3 +1236,10 @@ func migrateNamespacesState(ctx storage.Context) {
|
|||
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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,17 +9,12 @@ import (
|
|||
type NameState struct {
|
||||
Owner interop.Hash160
|
||||
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
|
||||
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.
|
||||
func (n NameState) checkAdmin() {
|
||||
if runtime.CheckWitness(n.Owner) {
|
||||
|
|
|
@ -149,7 +149,6 @@ func Properties(tokenID []byte) map[string]any {
|
|||
ns := getNameState(ctx, tokenID)
|
||||
return map[string]any{
|
||||
"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.
|
||||
func checkParent(ctx storage.Context, fragments []string) string {
|
||||
now := int64(runtime.GetTime())
|
||||
last := len(fragments) - 1
|
||||
name := fragments[last]
|
||||
parent := ""
|
||||
|
@ -320,10 +318,6 @@ func checkParent(ctx storage.Context, fragments []string) string {
|
|||
if nsBytes == nil {
|
||||
continue
|
||||
}
|
||||
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
|
||||
if now >= ns.Expiration {
|
||||
panic("domain expired: " + name)
|
||||
}
|
||||
parent = name
|
||||
}
|
||||
return parent
|
||||
|
@ -390,9 +384,6 @@ func register(ctx storage.Context, name string, owner interop.Hash160, email str
|
|||
nsBytes := storage.Get(ctx, append([]byte{prefixName}, tokenKey...))
|
||||
if nsBytes != nil {
|
||||
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
|
||||
if int64(runtime.GetTime()) < ns.Expiration {
|
||||
return false
|
||||
}
|
||||
oldOwner = ns.Owner
|
||||
updateBalance(ctx, []byte(name), oldOwner, -1)
|
||||
} else {
|
||||
|
@ -401,8 +392,7 @@ func register(ctx storage.Context, name string, owner interop.Hash160, email str
|
|||
ns := NameState{
|
||||
Owner: owner,
|
||||
Name: name,
|
||||
// NNS expiration is in milliseconds
|
||||
Expiration: int64(runtime.GetTime() + expire*1000),
|
||||
Expiration: 0,
|
||||
}
|
||||
checkAvailableGlobalDomain(ctx, name)
|
||||
|
||||
|
@ -415,18 +405,6 @@ func register(ctx storage.Context, name string, owner interop.Hash160, email str
|
|||
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.
|
||||
func UpdateSOA(name, email string, refresh, retry, expire, ttl int) {
|
||||
checkDomainNameLength(name)
|
||||
|
@ -731,9 +709,7 @@ func getNameStateWithKey(ctx storage.Context, tokenKey []byte) NameState {
|
|||
if nsBytes == nil {
|
||||
panic("token not found")
|
||||
}
|
||||
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
|
||||
ns.ensureNotExpired()
|
||||
return ns
|
||||
return std.Deserialize(nsBytes.([]byte)).(NameState)
|
||||
}
|
||||
|
||||
// putNameState stores domain name state.
|
||||
|
@ -801,7 +777,7 @@ func addRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType,
|
|||
ns := NameState{
|
||||
Name: globalDomain,
|
||||
Owner: nsOriginal.Owner,
|
||||
Expiration: nsOriginal.Expiration,
|
||||
Expiration: 0,
|
||||
Admin: nsOriginal.Admin,
|
||||
}
|
||||
|
||||
|
@ -1119,17 +1095,14 @@ func tokenIDFromName(name string) string {
|
|||
|
||||
ctx := storage.GetReadOnlyContext()
|
||||
sum := 0
|
||||
l := len(fragments) - 1
|
||||
l := len(fragments)
|
||||
for i := 0; i < l; i++ {
|
||||
tokenKey := getTokenKey([]byte(name[sum:]))
|
||||
nameKey := append([]byte{prefixName}, tokenKey...)
|
||||
nsBytes := storage.Get(ctx, nameKey)
|
||||
if nsBytes != nil {
|
||||
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
|
||||
if int64(runtime.GetTime()) < ns.Expiration {
|
||||
return name[sum:]
|
||||
}
|
||||
}
|
||||
sum += len(fragments[i]) + 1
|
||||
}
|
||||
return name
|
||||
|
|
|
@ -2,9 +2,13 @@ package policy
|
|||
|
||||
import (
|
||||
"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/contract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/std"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||
"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"
|
||||
)
|
||||
|
||||
const (
|
||||
purgeNsState = "purge"
|
||||
frozenNsState = "frozen"
|
||||
)
|
||||
|
||||
// _deploy function sets up initial list of inner ring public keys.
|
||||
func _deploy(data any, isUpdate bool) {
|
||||
if isUpdate {
|
||||
|
@ -50,7 +59,7 @@ func _deploy(data any, isUpdate bool) {
|
|||
ctx := storage.GetContext()
|
||||
if args.Admin != nil {
|
||||
if len(args.Admin) != 20 {
|
||||
panic("invaliad admin hash length")
|
||||
panic("invalid admin hash length")
|
||||
}
|
||||
storage.Put(ctx, []byte{ownerKeyPrefix}, args.Admin)
|
||||
}
|
||||
|
@ -142,9 +151,51 @@ func mapToNumericCreateIfNotExists(ctx storage.Context, kind Kind, name []byte)
|
|||
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) {
|
||||
ctx := storage.GetContext()
|
||||
checkAuthorization(ctx)
|
||||
checkChainNamespace(entity, entityName)
|
||||
|
||||
entityNameBytes := mapToNumericCreateIfNotExists(ctx, entity, []byte(entityName))
|
||||
key := storageKey(entity, entityNameBytes, name)
|
||||
|
|
|
@ -70,6 +70,11 @@ type UpdateNamespaceEvent struct {
|
|||
State string
|
||||
}
|
||||
|
||||
// DeleteNamespaceEvent represents "DeleteNamespace" event emitted by the contract.
|
||||
type DeleteNamespaceEvent struct {
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// AddSubjectToNamespaceEvent represents "AddSubjectToNamespace" event emitted by the contract.
|
||||
type AddSubjectToNamespaceEvent struct {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
// This transaction is signed and immediately sent to the network.
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
// with "AddSubjectToNamespace" name from the provided [result.ApplicationLog].
|
||||
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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
// This transaction is signed and immediately sent to the network.
|
||||
// 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)
|
||||
require.NoError(t, err)
|
||||
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) {
|
||||
|
@ -580,3 +653,25 @@ func prettyPrintExtendedSubjects(subjects []*client.SubjectExtended) {
|
|||
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 defaultNamespace = ""
|
||||
const (
|
||||
defaultNamespace = ""
|
||||
customNamespace = "custom"
|
||||
)
|
||||
|
||||
const (
|
||||
setAdminMethod = "setAdmin"
|
||||
|
@ -47,6 +50,7 @@ const (
|
|||
getNamespaceMethod = "getNamespace"
|
||||
getNamespaceExtendedMethod = "getNamespaceExtended"
|
||||
updateNamespaceMethod = "updateNamespace"
|
||||
deleteNamespaceMethod = "deleteNamespace"
|
||||
listNamespacesMethod = "listNamespaces"
|
||||
listNamespaceSubjectsMethod = "listNamespaceSubjects"
|
||||
|
||||
|
@ -66,7 +70,16 @@ const (
|
|||
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 {
|
||||
e *neotest.Executor
|
||||
|
@ -306,6 +319,56 @@ func TestFrostFSID_SubjectManagement(t *testing.T) {
|
|||
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)
|
||||
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, "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) {
|
||||
|
@ -635,6 +751,50 @@ func TestFrostFSID_GroupManagement(t *testing.T) {
|
|||
groups := parseGroups(t, readIteratorAll(s))
|
||||
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) {
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"fmt"
|
||||
"math/big"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -270,7 +269,58 @@ func TestNNSRegister(t *testing.T) {
|
|||
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.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) {
|
||||
|
@ -387,6 +437,10 @@ func TestTLDRecord(t *testing.T) {
|
|||
|
||||
result := []stackitem.Item{stackitem.NewByteArray([]byte("1.2.3.4"))}
|
||||
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) {
|
||||
|
@ -500,45 +554,6 @@ func TestNNSGetAllRecords(t *testing.T) {
|
|||
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) {
|
||||
c := newNNSInvoker(t, true)
|
||||
|
||||
|
@ -640,31 +655,6 @@ func TestNNSIsAvailable(t *testing.T) {
|
|||
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) {
|
||||
c := newNNSInvoker(t, true)
|
||||
|
||||
|
|
|
@ -5,35 +5,76 @@ import (
|
|||
"path"
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/policy"
|
||||
"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/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/wallet"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
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")
|
||||
c := neotest.CompileFile(t, e.CommitteeHash, policyPath, cfgPath)
|
||||
e.DeployContract(t, c, []any{nil})
|
||||
return c.Hash
|
||||
}
|
||||
|
||||
func newPolicyInvoker(t *testing.T) *neotest.ContractInvoker {
|
||||
e := newExecutor(t)
|
||||
h := deployPolicyContract(t, e)
|
||||
return e.CommitteeInvoker(h)
|
||||
return e.CommitteeInvoker(c.Hash)
|
||||
}
|
||||
|
||||
func TestPolicy(t *testing.T) {
|
||||
e := newPolicyInvoker(t)
|
||||
c := newPolicyInvokers(t)
|
||||
|
||||
checkChainsIteratorByPrefix(t, e, policy.Namespace, "mynamespace", "ingress", [][]byte{})
|
||||
checkChainsIteratorByPrefix(t, e, policy.Container, "cnr1", "ingress", [][]byte{})
|
||||
checkChainsIteratorByPrefix(t, c.policy, policy.Namespace, "mynamespace", "ingress", [][]byte{})
|
||||
checkChainsIteratorByPrefix(t, c.policy, policy.Container, "cnr1", "ingress", [][]byte{})
|
||||
|
||||
// Policies are opaque to the contract and are just raw bytes to store.
|
||||
p1 := []byte("chain1")
|
||||
|
@ -41,82 +82,97 @@ func TestPolicy(t *testing.T) {
|
|||
p3 := []byte("chain3")
|
||||
p33 := []byte("chain33")
|
||||
|
||||
e.Invoke(t, stackitem.Null{}, "addChain", policy.Namespace, "mynamespace", "ingress:123", p1)
|
||||
checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")})
|
||||
checkChains(t, e, "mynamespace", "", "ingress", [][]byte{p1})
|
||||
checkChains(t, e, "mynamespace", "", "all", nil)
|
||||
c.frostfsid.Invoke(t, stackitem.Null{}, "createNamespace", "mynamespace")
|
||||
|
||||
e.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule2", p2)
|
||||
checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")})
|
||||
checkTargets(t, e, policy.Container, [][]byte{[]byte("cnr1")})
|
||||
checkChains(t, e, "mynamespace", "", "ingress", [][]byte{p1}) // Only namespace chains.
|
||||
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.
|
||||
c.policy.Invoke(t, stackitem.Null{}, "addChain", policy.Namespace, "mynamespace", "ingress:123", p1)
|
||||
checkTargets(t, c.policy, policy.Namespace, [][]byte{[]byte("mynamespace")})
|
||||
checkChains(t, c.policy, "mynamespace", "", "ingress", [][]byte{p1})
|
||||
checkChains(t, c.policy, "mynamespace", "", "all", nil)
|
||||
|
||||
e.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p3)
|
||||
checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")})
|
||||
checkTargets(t, e, policy.Container, [][]byte{[]byte("cnr1")})
|
||||
checkChains(t, e, "mynamespace", "cnr1", "ingress", [][]byte{p1, p2, p3})
|
||||
c.policy.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule2", p2)
|
||||
checkTargets(t, c.policy, policy.Namespace, [][]byte{[]byte("mynamespace")})
|
||||
checkTargets(t, c.policy, policy.Container, [][]byte{[]byte("cnr1")})
|
||||
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)
|
||||
checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")})
|
||||
checkTargets(t, e, policy.Container, [][]byte{[]byte("cnr1")})
|
||||
checkChain(t, e, policy.Container, "cnr1", "ingress:myrule3", p33)
|
||||
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"})
|
||||
c.policy.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p3)
|
||||
checkTargets(t, c.policy, policy.Namespace, [][]byte{[]byte("mynamespace")})
|
||||
checkTargets(t, c.policy, policy.Container, [][]byte{[]byte("cnr1")})
|
||||
checkChains(t, c.policy, "mynamespace", "cnr1", "ingress", [][]byte{p1, p2, p3})
|
||||
|
||||
checkChainsIteratorByPrefix(t, e, policy.Container, "cnr1", "ingress:myrule3", [][]byte{p33})
|
||||
checkChainsIteratorByPrefix(t, e, policy.Container, "cnr1", "ingress", [][]byte{p2, p33})
|
||||
c.policy.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", 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("wrong name", func(t *testing.T) {
|
||||
e.Invoke(t, stackitem.Null{}, "removeChain", policy.Namespace, "mynamespace", "ingress")
|
||||
checkChains(t, e, "mynamespace", "", "ingress", [][]byte{p1})
|
||||
c.policy.Invoke(t, stackitem.Null{}, "removeChain", policy.Namespace, "mynamespace", "ingress")
|
||||
checkChains(t, c.policy, "mynamespace", "", "ingress", [][]byte{p1})
|
||||
})
|
||||
|
||||
e.Invoke(t, stackitem.Null{}, "removeChain", policy.Namespace, "mynamespace", "ingress:123")
|
||||
checkChains(t, e, "mynamespace", "", "ingress", nil)
|
||||
checkChains(t, e, "mynamespace", "cnr1", "ingress", [][]byte{p2, p33}) // Container chains still exist.
|
||||
c.policy.Invoke(t, stackitem.Null{}, "removeChain", policy.Namespace, "mynamespace", "ingress:123")
|
||||
checkChains(t, c.policy, "mynamespace", "", "ingress", nil)
|
||||
checkChains(t, c.policy, "mynamespace", "cnr1", "ingress", [][]byte{p2, p33}) // Container chains still exist.
|
||||
|
||||
// Remove by prefix.
|
||||
e.Invoke(t, stackitem.Null{}, "removeChainsByPrefix", policy.Container, "cnr1", "ingress")
|
||||
checkChains(t, e, "mynamespace", "cnr1", "ingress", nil)
|
||||
c.policy.Invoke(t, stackitem.Null{}, "removeChainsByPrefix", policy.Container, "cnr1", "ingress")
|
||||
checkChains(t, c.policy, "mynamespace", "cnr1", "ingress", nil)
|
||||
|
||||
// Remove by prefix.
|
||||
e.Invoke(t, stackitem.Null{}, "removeChainsByPrefix", policy.Container, "cnr1", "ingress")
|
||||
checkChains(t, e, "mynamespace", "cnr1", "ingress", nil)
|
||||
c.policy.Invoke(t, stackitem.Null{}, "removeChainsByPrefix", policy.Container, "cnr1", "ingress")
|
||||
checkChains(t, c.policy, "mynamespace", "cnr1", "ingress", nil)
|
||||
|
||||
checkTargets(t, e, policy.Namespace, [][]byte{})
|
||||
checkTargets(t, e, policy.Container, [][]byte{})
|
||||
checkTargets(t, c.policy, policy.Namespace, [][]byte{})
|
||||
checkTargets(t, c.policy, policy.Container, [][]byte{})
|
||||
})
|
||||
|
||||
t.Run("add again after removal", func(t *testing.T) {
|
||||
e.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.Namespace, "mynamespace", "ingress:123", p1)
|
||||
c.policy.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p33)
|
||||
|
||||
checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")})
|
||||
checkTargets(t, e, policy.Container, [][]byte{[]byte("cnr1")})
|
||||
checkTargets(t, c.policy, policy.Namespace, [][]byte{[]byte("mynamespace")})
|
||||
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) {
|
||||
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)
|
||||
c := e.WithSigners(s)
|
||||
s := c.policy.NewAccount(t, 1_0000_0000)
|
||||
cs := c.policy.WithSigners(s)
|
||||
|
||||
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())
|
||||
e.Invoke(t, stackitem.NewBuffer(s.ScriptHash().BytesBE()), "getAdmin")
|
||||
c.policy.Invoke(t, stackitem.Null{}, "setAdmin", s.ScriptHash())
|
||||
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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue