Compare commits

..

No commits in common. "master" and "master" have entirely different histories.

18 changed files with 226 additions and 734 deletions

View file

@ -7,7 +7,6 @@ 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

View file

@ -1 +1 @@
v0.21.4 v0.21.2

View file

@ -1,10 +0,0 @@
package common
import "github.com/nspcc-dev/neo-go/pkg/interop"
const (
NEO3PrefixLen = 1
ChecksumLen = 4
AddressLen = NEO3PrefixLen + interop.Hash160Len + ChecksumLen
)

View file

@ -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 = 4 patch = 2
// 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

View file

@ -114,7 +114,6 @@ const (
createNamespaceMethod = "createNamespace" createNamespaceMethod = "createNamespace"
updateNamespaceMethod = "updateNamespace" updateNamespaceMethod = "updateNamespace"
deleteNamespaceMethod = "deleteNamespace"
getNamespaceMethod = "getNamespace" getNamespaceMethod = "getNamespace"
getNamespaceExtendedMethod = "getNamespaceExtended" getNamespaceExtendedMethod = "getNamespaceExtended"
listNamespacesMethod = "listNamespaces" listNamespacesMethod = "listNamespaces"
@ -464,18 +463,6 @@ 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)

View file

@ -185,6 +185,7 @@ 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 {
@ -192,7 +193,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

View file

@ -72,10 +72,6 @@ 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

View file

@ -21,7 +21,6 @@ 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 |
*/ */

View file

@ -21,7 +21,6 @@ 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
@ -100,15 +99,10 @@ 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()
@ -160,23 +154,7 @@ func _deploy(data any, isUpdate bool) {
storage.Put(ctx, groupCounterKey, maxGroupID) storage.Put(ctx, groupCounterKey, maxGroupID)
} }
if args.version < common.GetVersion(0, 21, 3) { migrateNamespacesState(ctx)
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
} }
@ -231,7 +209,6 @@ 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 {
@ -245,7 +222,7 @@ func CreateSubject(ns string, key interop.PublicKey) {
panic("subject already exists") panic("subject already exists")
} }
saPrefix := additionalKeyToSubjectPrefix(key) saPrefix := subjectAdditionalPrefix(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")
@ -270,7 +247,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, dummyValue) storage.Put(ctx, nsSubjKey, []byte{1})
storage.Put(ctx, allAddressKey, true) storage.Put(ctx, allAddressKey, true)
runtime.Notify("CreateSubject", interop.Hash160(addr)) runtime.Notify("CreateSubject", interop.Hash160(addr))
@ -293,13 +270,13 @@ func AddSubjectKey(addr interop.Hash160, key interop.PublicKey) {
panic("key is occupied") panic("key is occupied")
} }
saKey := additionalKeyToSubjectKey(key, addr) saKey := subjectAdditionalKey(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, dummyValue) storage.Put(ctx, saKey, []byte{1})
sKey := subjectKeyFromAddr(addr) sKey := subjectKeyFromAddr(addr)
data = storage.Get(ctx, sKey).([]byte) data = storage.Get(ctx, sKey).([]byte)
@ -307,9 +284,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)
checkNamespaceState(subject.Namespace) subject.AdditionalKeys = append(subject.AdditionalKeys, key)
storage.Put(ctx, subjectToAdditionalKeyKey(addr, key), dummyValue) storage.Put(ctx, sKey, std.Serialize(subject))
storage.Put(ctx, addressKey, true) storage.Put(ctx, addressKey, true)
runtime.Notify("AddSubjectKey", addr, key) runtime.Notify("AddSubjectKey", addr, key)
} }
@ -326,7 +303,7 @@ func RemoveSubjectKey(addr interop.Hash160, key interop.PublicKey) {
panic("incorrect public key length") panic("incorrect public key length")
} }
saKey := additionalKeyToSubjectKey(key, addr) saKey := subjectAdditionalKey(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")
@ -339,14 +316,17 @@ 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)
subjToAddKey := subjectToAdditionalKeyKey(addr, key) var additionalKeys []interop.PublicKey
data = storage.Get(ctx, subjToAddKey).([]byte) for i := 0; i < len(subject.AdditionalKeys); i++ {
if data == nil { if !common.BytesEqual(subject.AdditionalKeys[i], key) {
panic("key already removed") additionalKeys = append(additionalKeys, subject.AdditionalKeys[i])
}
} }
storage.Delete(ctx, subjToAddKey) subject.AdditionalKeys = additionalKeys
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)
} }
@ -367,7 +347,6 @@ 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))
@ -393,7 +372,6 @@ 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{}
} }
@ -441,10 +419,8 @@ 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, additionalKeyToSubjectKey(subj.AdditionalKeys[i], addr)) storage.Delete(ctx, subjectAdditionalKey(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))
@ -466,17 +442,15 @@ 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 {
addr = getPrimaryAddr(ctx, addr) a := getPrimaryAddr(ctx, addr)
sKey = subjectKeyFromAddr(addr) sKey = subjectKeyFromAddr(a)
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")
} }
} }
subj := std.Deserialize(data).(Subject) return 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.
@ -522,18 +496,14 @@ 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 {
subj := std.Deserialize(data).(Subject) return 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 {
subj := std.Deserialize(data).(Subject) return std.Deserialize(data).(Subject)
subj.AdditionalKeys = getSubjectAdditionalKeys(ctx, addr)
return subj
} }
panic("subject not found") panic("subject not found")
@ -548,20 +518,6 @@ 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)
@ -639,36 +595,6 @@ 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()
@ -742,7 +668,6 @@ 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 == "" {
@ -856,7 +781,6 @@ 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))
@ -878,7 +802,6 @@ 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{}
} }
@ -926,7 +849,6 @@ 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)
@ -940,7 +862,7 @@ func AddSubjectToGroup(addr interop.Hash160, groupID int) {
} }
gsKey := groupSubjectKey(subject.Namespace, groupID, addr) gsKey := groupSubjectKey(subject.Namespace, groupID, addr)
storage.Put(ctx, gsKey, dummyValue) storage.Put(ctx, gsKey, []byte{1})
runtime.Notify("AddSubjectToGroup", addr, subject.Namespace, groupID) runtime.Notify("AddSubjectToGroup", addr, subject.Namespace, groupID)
} }
@ -1109,25 +1031,15 @@ func subjectKeyFromAddr(addr interop.Hash160) []byte {
return append([]byte{subjectKeysPrefix}, addr...) return append([]byte{subjectKeysPrefix}, addr...)
} }
func additionalKeyToSubjectKey(additionalKey interop.PublicKey, primeAddr interop.Hash160) []byte { func subjectAdditionalKey(additionalKey interop.PublicKey, primeAddr interop.Hash160) []byte {
return append(additionalKeyToSubjectPrefix(additionalKey), primeAddr...) return append(subjectAdditionalPrefix(additionalKey), primeAddr...)
} }
func additionalKeyToSubjectPrefix(additionalKey interop.PublicKey) []byte { func subjectAdditionalPrefix(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))
} }
@ -1214,6 +1126,7 @@ 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)
@ -1236,10 +1149,3 @@ 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")
}
}

View file

@ -7,14 +7,19 @@ 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) {

View file

@ -148,7 +148,8 @@ 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,
} }
} }
@ -307,6 +308,7 @@ 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 := ""
@ -318,6 +320,10 @@ 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
@ -384,15 +390,19 @@ 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,
Expiration: 0, // NNS expiration is in milliseconds
Expiration: int64(runtime.GetTime() + expire*1000),
} }
checkAvailableGlobalDomain(ctx, name) checkAvailableGlobalDomain(ctx, name)
@ -405,6 +415,18 @@ 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)
@ -709,7 +731,9 @@ func getNameStateWithKey(ctx storage.Context, tokenKey []byte) NameState {
if nsBytes == nil { if nsBytes == nil {
panic("token not found") panic("token not found")
} }
return std.Deserialize(nsBytes.([]byte)).(NameState) ns := std.Deserialize(nsBytes.([]byte)).(NameState)
ns.ensureNotExpired()
return ns
} }
// putNameState stores domain name state. // putNameState stores domain name state.
@ -777,7 +801,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: 0, Expiration: nsOriginal.Expiration,
Admin: nsOriginal.Admin, Admin: nsOriginal.Admin,
} }
@ -1095,13 +1119,16 @@ func tokenIDFromName(name string) string {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
sum := 0 sum := 0
l := len(fragments) l := len(fragments) - 1
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 {
return name[sum:] ns := std.Deserialize(nsBytes.([]byte)).(NameState)
if int64(runtime.GetTime()) < ns.Expiration {
return name[sum:]
}
} }
sum += len(fragments[i]) + 1 sum += len(fragments[i]) + 1
} }

View file

@ -2,13 +2,9 @@ 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"
) )
@ -40,11 +36,6 @@ 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 {
@ -59,7 +50,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("invalid admin hash length") panic("invaliad admin hash length")
} }
storage.Put(ctx, []byte{ownerKeyPrefix}, args.Admin) storage.Put(ctx, []byte{ownerKeyPrefix}, args.Admin)
} }
@ -151,51 +142,9 @@ 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)

View file

@ -70,11 +70,6 @@ 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
@ -494,28 +489,6 @@ 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.
@ -1421,67 +1394,6 @@ 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) {

View file

@ -286,6 +286,28 @@ 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.

View file

@ -189,79 +189,6 @@ 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) {
@ -653,25 +580,3 @@ 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)
}

View file

@ -24,10 +24,7 @@ import (
const frostfsidPath = "../frostfsid" const frostfsidPath = "../frostfsid"
const ( const defaultNamespace = ""
defaultNamespace = ""
customNamespace = "custom"
)
const ( const (
setAdminMethod = "setAdmin" setAdminMethod = "setAdmin"
@ -50,7 +47,6 @@ const (
getNamespaceMethod = "getNamespace" getNamespaceMethod = "getNamespace"
getNamespaceExtendedMethod = "getNamespaceExtended" getNamespaceExtendedMethod = "getNamespaceExtended"
updateNamespaceMethod = "updateNamespace" updateNamespaceMethod = "updateNamespace"
deleteNamespaceMethod = "deleteNamespace"
listNamespacesMethod = "listNamespaces" listNamespacesMethod = "listNamespaces"
listNamespaceSubjectsMethod = "listNamespaceSubjects" listNamespaceSubjectsMethod = "listNamespaceSubjects"
@ -70,16 +66,7 @@ const (
nsActiveState = "active" nsActiveState = "active"
) )
const ( const notWitnessedError = "not witnessed"
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
@ -319,56 +306,6 @@ 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)
@ -577,59 +514,6 @@ 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) {
@ -751,50 +635,6 @@ 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) {

View file

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"path" "path"
"strings"
"testing" "testing"
"time" "time"
@ -269,58 +270,7 @@ 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.Invoke(t, stackitem.Null{}, "getRecords", "testdomain.com", int64(nns.SOA)) c.InvokeFail(t, "token not found", "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) {
@ -437,10 +387,6 @@ 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) {
@ -554,6 +500,45 @@ 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)
@ -655,6 +640,31 @@ 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)

View file

@ -5,76 +5,35 @@ 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"
type policyContracts struct { func deployPolicyContract(t *testing.T, e *neotest.Executor) util.Uint160 {
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 e.CommitteeInvoker(c.Hash) return 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) {
c := newPolicyInvokers(t) e := newPolicyInvoker(t)
checkChainsIteratorByPrefix(t, c.policy, policy.Namespace, "mynamespace", "ingress", [][]byte{}) checkChainsIteratorByPrefix(t, e, policy.Namespace, "mynamespace", "ingress", [][]byte{})
checkChainsIteratorByPrefix(t, c.policy, policy.Container, "cnr1", "ingress", [][]byte{}) checkChainsIteratorByPrefix(t, e, 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")
@ -82,97 +41,82 @@ func TestPolicy(t *testing.T) {
p3 := []byte("chain3") p3 := []byte("chain3")
p33 := []byte("chain33") p33 := []byte("chain33")
c.frostfsid.Invoke(t, stackitem.Null{}, "createNamespace", "mynamespace") 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.policy.Invoke(t, stackitem.Null{}, "addChain", policy.Namespace, "mynamespace", "ingress:123", p1) e.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule2", p2)
checkTargets(t, c.policy, policy.Namespace, [][]byte{[]byte("mynamespace")}) checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")})
checkChains(t, c.policy, "mynamespace", "", "ingress", [][]byte{p1}) checkTargets(t, e, policy.Container, [][]byte{[]byte("cnr1")})
checkChains(t, c.policy, "mynamespace", "", "all", nil) 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.Container, "cnr1", "ingress:myrule2", p2) e.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p3)
checkTargets(t, c.policy, policy.Namespace, [][]byte{[]byte("mynamespace")}) checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")})
checkTargets(t, c.policy, policy.Container, [][]byte{[]byte("cnr1")}) checkTargets(t, e, policy.Container, [][]byte{[]byte("cnr1")})
checkChains(t, c.policy, "mynamespace", "", "ingress", [][]byte{p1}) // Only namespace chains. checkChains(t, e, "mynamespace", "cnr1", "ingress", [][]byte{p1, p2, p3})
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.
c.policy.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p3) e.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p33)
checkTargets(t, c.policy, policy.Namespace, [][]byte{[]byte("mynamespace")}) checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")})
checkTargets(t, c.policy, policy.Container, [][]byte{[]byte("cnr1")}) checkTargets(t, e, policy.Container, [][]byte{[]byte("cnr1")})
checkChains(t, c.policy, "mynamespace", "cnr1", "ingress", [][]byte{p1, p2, p3}) 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", p33) checkChainsIteratorByPrefix(t, e, policy.Container, "cnr1", "ingress:myrule3", [][]byte{p33})
checkTargets(t, c.policy, policy.Namespace, [][]byte{[]byte("mynamespace")}) checkChainsIteratorByPrefix(t, e, policy.Container, "cnr1", "ingress", [][]byte{p2, p33})
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) {
c.policy.Invoke(t, stackitem.Null{}, "removeChain", policy.Namespace, "mynamespace", "ingress") e.Invoke(t, stackitem.Null{}, "removeChain", policy.Namespace, "mynamespace", "ingress")
checkChains(t, c.policy, "mynamespace", "", "ingress", [][]byte{p1}) checkChains(t, e, "mynamespace", "", "ingress", [][]byte{p1})
}) })
c.policy.Invoke(t, stackitem.Null{}, "removeChain", policy.Namespace, "mynamespace", "ingress:123") e.Invoke(t, stackitem.Null{}, "removeChain", policy.Namespace, "mynamespace", "ingress:123")
checkChains(t, c.policy, "mynamespace", "", "ingress", nil) checkChains(t, e, "mynamespace", "", "ingress", nil)
checkChains(t, c.policy, "mynamespace", "cnr1", "ingress", [][]byte{p2, p33}) // Container chains still exist. checkChains(t, e, "mynamespace", "cnr1", "ingress", [][]byte{p2, p33}) // Container chains still exist.
// Remove by prefix. // Remove by prefix.
c.policy.Invoke(t, stackitem.Null{}, "removeChainsByPrefix", policy.Container, "cnr1", "ingress") e.Invoke(t, stackitem.Null{}, "removeChainsByPrefix", policy.Container, "cnr1", "ingress")
checkChains(t, c.policy, "mynamespace", "cnr1", "ingress", nil) checkChains(t, e, "mynamespace", "cnr1", "ingress", nil)
// Remove by prefix. // Remove by prefix.
c.policy.Invoke(t, stackitem.Null{}, "removeChainsByPrefix", policy.Container, "cnr1", "ingress") e.Invoke(t, stackitem.Null{}, "removeChainsByPrefix", policy.Container, "cnr1", "ingress")
checkChains(t, c.policy, "mynamespace", "cnr1", "ingress", nil) checkChains(t, e, "mynamespace", "cnr1", "ingress", nil)
checkTargets(t, c.policy, policy.Namespace, [][]byte{}) checkTargets(t, e, policy.Namespace, [][]byte{})
checkTargets(t, c.policy, policy.Container, [][]byte{}) checkTargets(t, e, policy.Container, [][]byte{})
}) })
t.Run("add again after removal", func(t *testing.T) { t.Run("add again after removal", func(t *testing.T) {
c.policy.Invoke(t, stackitem.Null{}, "addChain", policy.Namespace, "mynamespace", "ingress:123", p1) e.Invoke(t, stackitem.Null{}, "addChain", policy.Namespace, "mynamespace", "ingress:123", p1)
c.policy.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p33) e.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p33)
checkTargets(t, c.policy, policy.Namespace, [][]byte{[]byte("mynamespace")}) checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")})
checkTargets(t, c.policy, policy.Container, [][]byte{[]byte("cnr1")}) checkTargets(t, e, 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) {
c := newPolicyInvokers(t) e := newPolicyInvoker(t)
c.policy.Invoke(t, stackitem.Null{}, "getAdmin") e.Invoke(t, stackitem.Null{}, "getAdmin")
s := c.policy.NewAccount(t, 1_0000_0000) s := e.NewAccount(t, 1_0000_0000)
cs := c.policy.WithSigners(s) c := e.WithSigners(s)
args := []any{policy.Container, "cnr1", "ingress:myrule3", []byte("opaque")} args := []any{policy.Container, "cnr1", "ingress:myrule3", []byte("opaque")}
cs.InvokeFail(t, policy.ErrNotAuthorized, "addChain", args...) c.InvokeFail(t, policy.ErrNotAuthorized, "addChain", args...)
c.policy.Invoke(t, stackitem.Null{}, "setAdmin", s.ScriptHash()) e.Invoke(t, stackitem.Null{}, "setAdmin", s.ScriptHash())
c.policy.Invoke(t, stackitem.NewBuffer(s.ScriptHash().BytesBE()), "getAdmin") e.Invoke(t, stackitem.NewBuffer(s.ScriptHash().BytesBE()), "getAdmin")
cs.Invoke(t, stackitem.Null{}, "addChain", args...) c.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) {