Compare commits

..

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

28 changed files with 103 additions and 712 deletions

View file

@ -13,7 +13,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: '1.23'
go-version: '1.21'
- name: Run commit format checker
uses: https://git.frostfs.info/TrueCloudLab/dco-go@v3

View file

@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
go_versions: [ '1.22', '1.23' ]
go_versions: [ '1.21', '1.22' ]
fail-fast: false
steps:
- uses: actions/checkout@v3

1
.github/CODEOWNERS vendored Normal file
View file

@ -0,0 +1 @@
* @carpawell @fyrchik @cthulhu-rider

View file

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View file

@ -8,17 +8,7 @@ Changelog for FrostFS Contract
### Removed
### Updated
### Fixed
## [0.20.0]
### Added
- Add `ListFullSubjects` method to the frostfsid RPC client (#107)
- Add `ListChainNames` method to the policy contract (#105)
- Add `DeleteRecord` method to the nns contract (#114)
- Emit notification on record changes in nns contract (#109)
### Updated
- neo-go to v0.106.3
### Updating from v0.18.0
## [0.18.0] - 2023-09-14 - Academy of Sciences Glacier

View file

@ -1,5 +0,0 @@
.forgejo/.* @potyarkin
Makefile @potyarkin
frostfsid/client/.* @dkirillov
.* @TrueCloudLab/storage-core-developers @TrueCloudLab/storage-core-committers @TrueCloudLab/storage-service-developers @TrueCloudLab/storage-service-committers
tests/.* @fyrchik

View file

@ -1,5 +1,5 @@
<p align="center">
<img src="./.forgejo/logo.svg" width="500px" alt="FrostFS">
<img src="./.github/logo.svg" width="500px" alt="FrostFS">
</p>
<p align="center">
<a href="https://frostfs.info">FrostFS</a> related smart contracts.

View file

@ -1 +1 @@
v0.20.0
v0.19.1

View file

@ -4,15 +4,15 @@ import "github.com/nspcc-dev/neo-go/pkg/interop/native/std"
const (
major = 0
minor = 20
patch = 0
minor = 19
patch = 1
// 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
// any migration routines.
prevMajor = 0
prevMinor = 19
prevPatch = 3
prevMinor = 18
prevPatch = 0
Version = major*1_000_000 + minor*1_000 + patch

View file

@ -1,84 +0,0 @@
package commonclient
import (
"context"
"fmt"
"strings"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
)
const alreadyExistsError = "already exists"
type WaiterOptions struct {
// IgnoreAlreadyExistsError controls behavior for "already exists" error:
// - If set to true, it indicates that "already exists" error is not a problem, we should
// wait for transaction as usual (this is the behavior of neo-go [waiter.PollingBased]).
// - If set to false, it indicates that "already exists" should be reported as an error.
IgnoreAlreadyExistsError bool
// VerifyExecResults controls whether waiter should ensure that transaction successfully
// enters blockchain block.
VerifyExecResults bool
}
// Waiter is a decorator on top of the standard [waiter.Waiter].
// It provides additional behavior (controlled by [WaiterOptions]) on top of the standard
// functionality of awaiting transactions.
type Waiter struct {
waiter waiter.Waiter
options WaiterOptions
}
var _ waiter.Waiter = (*Waiter)(nil)
// NewWaiter decorates the specified waiter in a new [Waiter] instance.
func NewWaiter(waiter waiter.Waiter, options WaiterOptions) *Waiter {
return &Waiter{
waiter: waiter,
options: options,
}
}
// Wait allows to wait until transaction will be accepted to the chain.
func (w *Waiter) Wait(h util.Uint256, vub uint32, err error) (*state.AppExecResult, error) {
if !w.options.IgnoreAlreadyExistsError && errIsAlreadyExists(err) {
return nil, err
}
result, err := w.waiter.Wait(h, vub, err)
return w.examineExecResult(result, err)
}
// WaitAny waits until at least one of the specified transactions will be accepted
// to the chain.
func (w *Waiter) WaitAny(ctx context.Context, vub uint32, hashes ...util.Uint256) (*state.AppExecResult, error) {
result, err := w.waiter.WaitAny(ctx, vub, hashes...)
return w.examineExecResult(result, err)
}
func (w *Waiter) examineExecResult(result *state.AppExecResult, err error) (*state.AppExecResult, error) {
if !w.options.VerifyExecResults || err != nil {
return result, err
}
if result.Execution.VMState != vmstate.Fault {
// Transaction didn't fail, so we just return result "as is"
return result, nil
}
// Transaction failed, we extract VM exception from it and report as an error
if result.FaultException != "" {
return result, fmt.Errorf("%s", result.FaultException)
}
return result, fmt.Errorf("transaction failed, stack=%v", result.Stack)
}
func errIsAlreadyExists(err error) bool {
if err == nil {
return false
}
return strings.Contains(strings.ToLower(err.Error()), alreadyExistsError)
}

View file

@ -1,135 +0,0 @@
package commonclient
import (
"context"
"fmt"
"testing"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
type mockWaiter struct {
mock.Mock
}
func (w *mockWaiter) successfulResult(txHash util.Uint256) *state.AppExecResult {
return &state.AppExecResult{
Container: txHash,
Execution: state.Execution{
Trigger: trigger.Application,
VMState: vmstate.Halt,
GasConsumed: 100500,
Stack: nil,
Events: nil,
FaultException: "",
},
}
}
func (w *mockWaiter) failedResult(txHash util.Uint256, exception string) *state.AppExecResult {
return &state.AppExecResult{
Container: txHash,
Execution: state.Execution{
Trigger: trigger.Application,
VMState: vmstate.Fault,
GasConsumed: 100500,
Stack: nil,
Events: nil,
FaultException: exception,
},
}
}
func (m *mockWaiter) Wait(h util.Uint256, vub uint32, err error) (*state.AppExecResult, error) {
args := m.Called(h, vub, err)
result := args.Get(0)
if result == nil {
return nil, args.Error(1)
}
return result.(*state.AppExecResult), args.Error(1)
}
func (m *mockWaiter) WaitAny(ctx context.Context, vub uint32, hashes ...util.Uint256) (*state.AppExecResult, error) {
args := m.Called(ctx, vub, hashes)
result := args.Get(0)
if result == nil {
return nil, args.Error(1)
}
return result.(*state.AppExecResult), args.Error(1)
}
func TestWaiter(t *testing.T) {
txHash := util.Uint256{}
vub := uint32(100)
t.Run("ignore already exists error", func(t *testing.T) {
sendErr := fmt.Errorf("transaction already exists")
mw := &mockWaiter{}
mw.On("Wait", txHash, vub, mock.Anything).Return(mw.successfulResult(txHash), nil)
waiter := NewWaiter(mw, WaiterOptions{IgnoreAlreadyExistsError: true})
_, err := waiter.Wait(txHash, vub, sendErr)
require.NoError(t, err)
})
t.Run("report already exists error", func(t *testing.T) {
sendErr := fmt.Errorf("transaction already exists")
mw := &mockWaiter{}
mw.On("Wait", txHash, vub, mock.Anything).Return(mw.successfulResult(txHash), nil)
waiter := NewWaiter(mw, WaiterOptions{IgnoreAlreadyExistsError: false})
_, err := waiter.Wait(txHash, vub, sendErr)
require.Error(t, err)
})
t.Run("report wait error when transaction error is ignored", func(t *testing.T) {
waitErr := fmt.Errorf("mock error")
mw := &mockWaiter{}
mw.On("Wait", txHash, vub, nil).Return(nil, waitErr)
waiter := NewWaiter(mw, WaiterOptions{VerifyExecResults: false})
_, err := waiter.Wait(txHash, vub, nil)
require.ErrorIs(t, err, waitErr)
})
t.Run("report wait error when transaction error is verified", func(t *testing.T) {
waitErr := fmt.Errorf("mock error")
mw := &mockWaiter{}
mw.On("Wait", txHash, vub, nil).Return(nil, waitErr)
waiter := NewWaiter(mw, WaiterOptions{VerifyExecResults: true})
_, err := waiter.Wait(txHash, vub, nil)
require.ErrorIs(t, err, waitErr)
})
t.Run("ignore error from transaction", func(t *testing.T) {
txError := "mock error"
mw := &mockWaiter{}
mw.On("Wait", txHash, vub, nil).Return(mw.failedResult(txHash, txError), nil)
waiter := NewWaiter(mw, WaiterOptions{VerifyExecResults: false})
_, err := waiter.Wait(txHash, vub, nil)
require.NoError(t, err)
})
t.Run("examine error from transaction", func(t *testing.T) {
txError := "mock error"
mw := &mockWaiter{}
mw.On("Wait", txHash, vub, nil).Return(mw.failedResult(txHash, txError), nil)
waiter := NewWaiter(mw, WaiterOptions{VerifyExecResults: true})
_, err := waiter.Wait(txHash, vub, nil)
require.ErrorContains(t, err, txError)
})
}

View file

@ -23,13 +23,11 @@ import (
type (
Client struct {
act *actor.Actor
waiter waiter.Waiter
contract util.Uint160
}
Options struct {
ProxyContract util.Uint160
Waiter commonclient.WaiterOptions
}
)
@ -96,7 +94,6 @@ const (
createSubjectMethod = "createSubject"
getSubjectMethod = "getSubject"
getSubjectExtendedMethod = "getSubjectExtended"
getSubjectKVMethod = "getSubjectKV"
listSubjectsMethod = "listSubjects"
addSubjectKeyMethod = "addSubjectKey"
removeSubjectKeyMethod = "removeSubjectKey"
@ -154,21 +151,17 @@ func New(ra actor.RPCActor, acc *wallet.Account, contract util.Uint160, opt Opti
if err != nil {
return nil, fmt.Errorf("init actor: %w", err)
}
wtr := commonclient.NewWaiter(act, opt.Waiter)
return &Client{
act: act,
waiter: wtr,
contract: contract,
}, nil
}
// NewSimple creates a new Client using existing actor.Actor and default waiter options.
// NewSimple creates a new Client using exising actor.Actor.
func NewSimple(act *actor.Actor, contract util.Uint160) *Client {
wtr := commonclient.NewWaiter(act, commonclient.WaiterOptions{})
return &Client{
act: act,
waiter: wtr,
contract: contract,
}
}
@ -267,11 +260,6 @@ func (c Client) GetSubjectExtended(addr util.Uint160) (*SubjectExtended, error)
return ParseSubjectExtended(items)
}
// GetSubjectKV invokes `getSubjectKV` method of contract.
func (c Client) GetSubjectKV(addr util.Uint160, name string) (string, error) {
return unwrap.UTF8String(c.act.Call(c.contract, getSubjectKVMethod, addr, name))
}
// ListSubjects gets all subjects.
func (c Client) ListSubjects() ([]util.Uint160, error) {
return UnwrapArrayOfUint160(commonclient.ReadIteratorItems(c.act, iteratorBatchSize, c.contract, listSubjectsMethod))
@ -613,14 +601,18 @@ func (c Client) ListNonEmptyNamespaces() ([]string, error) {
return res, nil
}
// Wait waits until the specified transaction is accepted to the chain.
// Wait invokes underlying wait method on actor.Actor.
// Notice that "already exists" err value is treated as an error by this routine unlike actor.Waiter.
func (c Client) Wait(tx util.Uint256, vub uint32, err error) (*state.AppExecResult, error) {
return c.Waiter().Wait(tx, vub, err)
if err != nil {
return nil, err
}
return c.act.Wait(tx, vub, err)
}
// Waiter returns underlying waiter.Waiter.
func (c Client) Waiter() waiter.Waiter {
return c.waiter
return c.act
}
// ParseGroupID fetch groupID from stack after creating group method invocation.

View file

@ -7,7 +7,6 @@ safemethods:
- "getGroupByName"
- "getNamespace"
- "getNamespaceExtended"
- "getSubjectKV"
- "getSubject"
- "getSubjectExtended"
- "getSubjectByKey"

View file

@ -20,7 +20,6 @@ FrostFSID contract does not produce notifications to process.
| `G` + [ RIPEMD160 of namespace ] + [ 8 byte group id ] + [ subject address ] | []byte{1} | subject that belongs to the group |
| `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 |
*/

View file

@ -96,7 +96,6 @@ const (
groupSubjectsKeysPrefix = 'G'
groupCounterKey = 'c'
namespaceGroupsNamesPrefix = 'm'
addressPrefix = 'A'
)
func _deploy(data any, isUpdate bool) {
@ -113,27 +112,6 @@ func _deploy(data any, isUpdate bool) {
storage.Put(ctx, adminKey, args.admin)
}
if isUpdate {
it := storage.Find(ctx, subjectKeysPrefix, storage.ValuesOnly)
for iterator.Next(it) {
subjectRaw := iterator.Value(it)
subject := std.Deserialize(subjectRaw.([]byte)).(Subject)
address := addressKey(contract.CreateStandardAccount(subject.PrimaryKey))
if storage.Get(ctx, address) != nil {
panic("frostfsid contract contains duplicate keys")
}
storage.Put(ctx, address, true)
for i := 0; i < len(subject.AdditionalKeys); i++ {
address = addressKey(contract.CreateStandardAccount(subject.AdditionalKeys[i]))
if storage.Get(ctx, address) != nil {
panic("frostfsid contract contains duplicate keys")
}
storage.Put(ctx, address, true)
}
}
}
storage.Put(ctx, groupCounterKey, 0)
storage.Put(ctx, namespaceKey(""), std.Serialize(Namespace{}))
@ -204,11 +182,6 @@ func CreateSubject(ns string, key interop.PublicKey) {
panic("key is occupied")
}
allAddressKey := addressKey(addr)
if storage.Get(ctx, allAddressKey) != nil {
panic("key is occupied by another additional key")
}
nsKey := namespaceKey(ns)
data = storage.Get(ctx, nsKey).([]byte)
if data == nil {
@ -224,7 +197,6 @@ func CreateSubject(ns string, key interop.PublicKey) {
nsSubjKey := namespaceSubjectKey(ns, addr)
storage.Put(ctx, nsSubjKey, []byte{1})
storage.Put(ctx, allAddressKey, true)
runtime.Notify("CreateSubject", interop.Hash160(addr))
}
@ -241,11 +213,6 @@ func AddSubjectKey(addr interop.Hash160, key interop.PublicKey) {
panic("incorrect public key length")
}
addressKey := addressKey(contract.CreateStandardAccount(key))
if storage.Get(ctx, addressKey) != nil {
panic("key is occupied")
}
saKey := subjectAdditionalKey(key, addr)
data := storage.Get(ctx, saKey).([]byte)
if data != nil {
@ -263,7 +230,6 @@ func AddSubjectKey(addr interop.Hash160, key interop.PublicKey) {
subject.AdditionalKeys = append(subject.AdditionalKeys, key)
storage.Put(ctx, sKey, std.Serialize(subject))
storage.Put(ctx, addressKey, true)
runtime.Notify("AddSubjectKey", addr, key)
}
@ -303,7 +269,6 @@ func RemoveSubjectKey(addr interop.Hash160, key interop.PublicKey) {
subject.AdditionalKeys = additionalKeys
storage.Put(ctx, sKey, std.Serialize(subject))
storage.Delete(ctx, addressKey(contract.CreateStandardAccount(key)))
runtime.Notify("RemoveSubjectKey", addr, key)
}
@ -397,7 +362,6 @@ func DeleteSubject(addr interop.Hash160) {
for i := 0; i < len(subj.AdditionalKeys); i++ {
storage.Delete(ctx, subjectAdditionalKey(subj.AdditionalKeys[i], addr))
storage.Delete(ctx, addressKey(contract.CreateStandardAccount(subj.AdditionalKeys[i])))
}
storage.Delete(ctx, sKey)
@ -416,14 +380,9 @@ func GetSubject(addr interop.Hash160) Subject {
ctx := storage.GetReadOnlyContext()
sKey := subjectKeyFromAddr(addr)
data := storage.Get(ctx, sKey).([]byte)
if data == nil {
a := getPrimaryAddr(ctx, addr)
sKey = subjectKeyFromAddr(a)
data = storage.Get(ctx, sKey).([]byte)
if data == nil {
panic("subject not found")
}
}
return std.Deserialize(data).(Subject)
}
@ -474,22 +433,18 @@ func GetSubjectByKey(key interop.PublicKey) Subject {
return std.Deserialize(data).(Subject)
}
addr := getPrimaryAddr(ctx, contract.CreateStandardAccount(key))
saPrefix := subjectAdditionalPrefix(key)
it := storage.Find(ctx, saPrefix, storage.KeysOnly|storage.RemovePrefix)
for iterator.Next(it) {
addr := iterator.Value(it).([]byte)
sKey = subjectKeyFromAddr(addr)
data = storage.Get(ctx, sKey).([]byte)
if data != nil {
return std.Deserialize(data).(Subject)
}
panic("subject not found")
}
func getPrimaryAddr(ctx storage.Context, addr interop.Hash160) interop.Hash160 {
saPrefix := append([]byte{additionalKeysPrefix}, addr...)
it := storage.Find(ctx, saPrefix, storage.KeysOnly|storage.RemovePrefix)
if iterator.Next(it) {
return iterator.Value(it).([]byte)
break
}
panic("subject not found")
}
@ -517,34 +472,6 @@ func GetSubjectKeyByName(ns, name string) interop.PublicKey {
return subjKey
}
// GetSubjectKV GetSubjectKey returns the value associated with the key for the subject.
func GetSubjectKV(addr interop.Hash160, name string) string {
if len(addr) != interop.Hash160Len {
panic("incorrect address length")
}
ctx := storage.GetReadOnlyContext()
sKey := subjectKeyFromAddr(addr)
data := storage.Get(ctx, sKey).([]byte)
if data == nil {
return ""
}
sbj := std.Deserialize(data).(Subject)
if sbj.KV == nil {
return ""
}
for k, v := range sbj.KV {
if k == name {
return v
}
}
return ""
}
func ListSubjects() iterator.Iterator {
ctx := storage.GetReadOnlyContext()
return storage.Find(ctx, []byte{subjectKeysPrefix}, storage.KeysOnly|storage.RemovePrefix)
@ -1074,7 +1001,3 @@ func idToBytes(itemID int) []byte {
zeros := make([]byte, 8-ln)
return append(b, zeros...)
}
func addressKey(address []byte) []byte {
return append([]byte{addressPrefix}, address...)
}

3
go.mod
View file

@ -1,6 +1,6 @@
module git.frostfs.info/TrueCloudLab/frostfs-contract
go 1.22
go 1.20
require (
github.com/google/uuid v1.6.0
@ -36,7 +36,6 @@ require (
github.com/prometheus/procfs v0.12.0 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 // indirect
github.com/twmb/murmur3 v1.1.8 // indirect
github.com/urfave/cli/v2 v2.27.2 // indirect

22
go.sum
View file

@ -1,5 +1,4 @@
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12 h1:npHgfD4Tl2WJS3AJaMUi5ynGDPUBfkg3U3fCzDyXZ+4=
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c=
@ -7,7 +6,6 @@ github.com/bits-and-blooms/bitset v1.8.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ=
github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI=
github.com/consensys/gnark-crypto v0.12.2-0.20231013160410-1f65e75b6dfb h1:f0BMgIjhZy4lSRHCXFbQst85f5agZAjtDMixQqBWNpc=
@ -19,7 +17,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
@ -31,14 +28,12 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -50,13 +45,9 @@ github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY=
github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU=
github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU=
@ -67,19 +58,15 @@ github.com/nspcc-dev/dbft v0.2.0/go.mod h1:oFE6paSC/yfFh9mcNU6MheMGOYXK9+sPiRk3Y
github.com/nspcc-dev/go-ordered-json v0.0.0-20240301084351-0246b013f8b2 h1:mD9hU3v+zJcnHAVmHnZKt3I++tvn30gBj2rP2PocZMk=
github.com/nspcc-dev/go-ordered-json v0.0.0-20240301084351-0246b013f8b2/go.mod h1:U5VfmPNM88P4RORFb6KSUVBdJBDhlqggJZYGXGPxOcc=
github.com/nspcc-dev/hrw/v2 v2.0.1 h1:CxYUkBeJvNfMEn2lHhrV6FjY8pZPceSxXUtMVq0BUOU=
github.com/nspcc-dev/hrw/v2 v2.0.1/go.mod h1:iZAs5hT2q47EGq6AZ0FjaUI6ggntOi7vrY4utfzk5VA=
github.com/nspcc-dev/neo-go v0.106.3 h1:HEyhgkjQY+HfBzotMJ12xx2VuOUphkngZ4kEkjvXDtE=
github.com/nspcc-dev/neo-go v0.106.3/go.mod h1:3vEwJ2ld12N7HRGCaH/l/7EwopplC/+8XdIdPDNmD/M=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240727093519-1a48f1ce43ec h1:vDrbVXF2+2uP0RlkZmem3QYATcXCu9BzzGGCNsNcK7Q=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240727093519-1a48f1ce43ec/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
github.com/nspcc-dev/neofs-api-go/v2 v2.14.1-0.20240305074711-35bc78d84dc4 h1:arN0Ypn+jawZpu1BND7TGRn44InAVIqKygndsx0y2no=
github.com/nspcc-dev/neofs-api-go/v2 v2.14.1-0.20240305074711-35bc78d84dc4/go.mod h1:7Tm1NKEoUVVIUlkVwFrPh7GG5+Lmta2m7EGr4oVpBd8=
github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.12 h1:mdxtlSU2I4oVZ/7AXTLKyz8uUPbDWikZw4DM8gvrddA=
github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.12/go.mod h1:JdsEM1qgNukrWqgOBDChcYp8oY4XUzidcKaxY4hNJvQ=
github.com/nspcc-dev/rfc6979 v0.2.1 h1:8wWxkamHWFmO790GsewSoKUSJjVnL1fmdRpokU/RgRM=
github.com/nspcc-dev/rfc6979 v0.2.1/go.mod h1:Tk7h5kyUWkhjyO3zUgFFhy1v2vQv3BvQEntakdtqrWc=
github.com/nspcc-dev/tzhash v1.7.2 h1:iRXoa9TJqH/DQO7FFcqpq9BdruF9E7/xnFGlIghl5J4=
github.com/nspcc-dev/tzhash v1.7.2/go.mod h1:oHiH0qwmTsZkeVs7pvCS5cVXUaLhXxSFvnmnZ++ijm4=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@ -105,8 +92,6 @@ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDN
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 h1:xQdMZ1WLrgkkvOZ/LDQxjVxMLdby7osSh4ZEVa5sIjs=
@ -120,7 +105,6 @@ github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQut
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
@ -130,7 +114,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -141,7 +124,6 @@ golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -167,9 +149,7 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c h1:NUsgEN92SQQqzfA+YtqYNqYmB3DMMYLlIwUZAQFVFbo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=
google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -180,14 +160,12 @@ google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGm
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU=

View file

@ -44,32 +44,3 @@ The committee makes new tokens (domains), sets, and charges a fee for issuance.
## Globally Unique Domain Zone
For more information, see [here](../docs/globally-unique-domain-zone.md).
## NNS and Frostfsid
You can register a TLD domain without a committee signature using Frostfsid. To do this, create a new wallet
```
neo-go wallet init -w newwallet/wallet.json
```
Get wallet address:
```
neo-go wallet dump-keys -w newwallet/wallet.json
[subject-address]
[subject-key]
```
Create a subject in `FrostfsID`:
```
frostfs-adm morph frostfsid create-subject --subject-key="[subject-key]"
```
Grant permissions to the wallet:
```
frostfs-adm morph nns give-privilege --subject-address="[subject-address]"
```
Register domain:
```
neo-go contract invokefunction [NNS-hash] register "subdomain.domain" hash160:[subject-address] "email@frostfs.info" 10000 1000 1000 1000 -- [subject-address]:Global
```

View file

@ -2,7 +2,6 @@ name: "NameService"
supportedstandards: ["NEP-11"]
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf",
"tokens", "properties", "roots", "getPrice", "isAvailable", "getRecords",
"getAllRecords",
"resolve", "version"]
events:
- name: RegisterDomain

View file

@ -1,39 +0,0 @@
package nns
import (
"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/native/management"
"github.com/nspcc-dev/neo-go/pkg/interop/native/std"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
)
const FrostfsIDNNSName = "frostfsid.frostfs"
const (
FrostfsIDNNSTLDPermissionKey = "nns-allow-register-tld"
FrostfsIDTLDRegistrationAllowed = "allow"
)
func checkFrostfsID(ctx storage.Context, addr interop.Hash160) bool {
if len(addr) == 0 {
return false
}
frostfsIDAddress := getRecordsByType(ctx, []byte(tokenIDFromName(FrostfsIDNNSName)), FrostfsIDNNSName, TXT)
if len(frostfsIDAddress) < 2 {
return false
}
decodedBytes := std.Base58Decode([]byte(frostfsIDAddress[1]))
if len(decodedBytes) < 21 || management.GetContract(decodedBytes[1:21]) == nil {
return false
}
if res := contract.Call(decodedBytes[1:21], "getSubjectKV", contract.ReadOnly, addr, FrostfsIDNNSTLDPermissionKey).(string); res == FrostfsIDTLDRegistrationAllowed {
return true
}
return false
}

View file

@ -41,14 +41,9 @@ const (
// prefixRecord contains map from (token key + hash160(token name) + record type)
// to record.
prefixRecord byte = 0x22
// prefixGlobalDomain contains a flag indicating that this domain was created using GlobalDomain.
// This is necessary to distinguish it from regular CNAME records.
//prefixGlobalDomain contains a flag indicating that this domain was created using GlobalDomain.
//This is necessary to distinguish it from regular CNAME records.
prefixGlobalDomain byte = 0x23
// prefixCountSubDomains contains information about the number of domains in the zone.
// If it is nil, it will definitely be calculated on the first removal.
prefixCountSubDomains byte = 0x24
// prefixAutoCreated contains a flag indicating whether the TLD domain was created automatically.
prefixAutoCreated = 0x25
)
// Values constraints.
@ -79,7 +74,7 @@ const (
const (
// Cnametgt is a special TXT record ensuring all created subdomains point to the global domain - the value of this variable.
// It is guaranteed that two domains cannot point to the same global domain.
//It is guaranteed that two domains cannot point to the same global domain.
Cnametgt = "cnametgt"
)
@ -239,10 +234,13 @@ func IsAvailable(name string) bool {
ctx := storage.GetReadOnlyContext()
l := len(fragments)
if storage.Get(ctx, append([]byte{prefixRoot}, []byte(fragments[l-1])...)) == nil {
if l != 1 {
panic("TLD not found")
}
return true
}
checkParent(ctx, fragments)
checkParentExists(ctx, fragments)
checkAvailableGlobalDomain(ctx, name)
return storage.Get(ctx, append([]byte{prefixName}, getTokenKey([]byte(name))...)) == nil
}
@ -306,27 +304,33 @@ func extractCnametgt(ctx storage.Context, name, domain string) string {
return fragments[0] + "." + globalDomain
}
// checkParent returns parent domain or empty string if domain not found.
func checkParent(ctx storage.Context, fragments []string) string {
// checkParentExists panics if any domain from fragments doesn't exist or is expired.
func checkParentExists(ctx storage.Context, fragments []string) {
if dom := parentExpired(ctx, fragments); dom != "" {
panic("domain does not exist or is expired: " + dom)
}
}
// parentExpired returns domain from fragments that doesn't exist or is expired.
// first denotes the deepest subdomain to check.
func parentExpired(ctx storage.Context, fragments []string) string {
now := int64(runtime.GetTime())
last := len(fragments) - 1
name := fragments[last]
parent := ""
for i := last; i > 0; i-- {
if i != last {
name = fragments[i] + "." + name
}
nsBytes := storage.Get(ctx, append([]byte{prefixName}, getTokenKey([]byte(name))...))
if nsBytes == nil {
continue
return name
}
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
if now >= ns.Expiration {
panic("domain expired: " + name)
return name
}
parent = name
}
return parent
return ""
}
// Register registers a new domain with the specified owner and name if it's available.
@ -338,31 +342,24 @@ func Register(name string, owner interop.Hash160, email string, refresh, retry,
// Register registers a new domain with the specified owner and name if it's available.
func register(ctx storage.Context, name string, owner interop.Hash160, email string, refresh, retry, expire, ttl int) bool {
fragments := splitAndCheck(name)
countZone := len(fragments)
rootZone := []byte(fragments[countZone-1])
tldKey := append([]byte{prefixRoot}, rootZone...)
l := len(fragments)
tldKey := append([]byte{prefixRoot}, []byte(fragments[l-1])...)
tldBytes := storage.Get(ctx, tldKey)
if countZone == 1 {
checkCommitteeAndFrostfsID(ctx, owner)
if l == 1 {
checkCommittee()
if tldBytes != nil {
panic("TLD already exists")
}
storage.Put(ctx, tldKey, 0)
} else {
parent := checkParent(ctx, fragments)
if parent == "" {
parent = fragments[len(fragments)-1]
storage.Put(ctx, append([]byte{prefixAutoCreated}, rootZone...), true)
register(ctx, parent, owner, email, refresh, retry, expire, ttl)
if tldBytes == nil {
panic("TLD not found")
}
checkParentExists(ctx, fragments)
parentKey := getTokenKey([]byte(parent))
parentKey := getTokenKey([]byte(name[len(fragments[0])+1:]))
nsBytes := storage.Get(ctx, append([]byte{prefixName}, parentKey...))
if nsBytes == nil {
panic("parent does not exist:" + parent)
}
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
ns.checkAdmin()
@ -406,7 +403,6 @@ func register(ctx storage.Context, name string, owner interop.Hash160, email str
}
checkAvailableGlobalDomain(ctx, name)
updateSubdDomainCounter(ctx, rootZone, countZone)
putNameStateWithKey(ctx, tokenKey, ns)
putSoaRecord(ctx, name, email, refresh, retry, expire, ttl)
updateBalance(ctx, []byte(name), owner, +1)
@ -581,23 +577,14 @@ func DeleteDomain(name string) {
deleteDomain(ctx, name)
}
func countSubdomains(name string) int {
countSubDomains := 0
func deleteDomain(ctx storage.Context, name string) {
it := Tokens()
for iterator.Next(it) {
domain := iterator.Value(it)
if std.MemorySearch([]byte(domain.(string)), []byte(name)) > 0 {
countSubDomains = countSubDomains + 1
panic("can't delete a domain that has subdomains")
}
}
return countSubDomains
}
func deleteDomain(ctx storage.Context, name string) {
fragments := splitAndCheck(name)
parent := []byte(fragments[len(fragments)-1])
countSubDomainsKey := append([]byte{prefixCountSubDomains}, parent...)
autoCreatedPrefix := append([]byte{prefixAutoCreated}, parent...)
nsKey := append([]byte{prefixName}, getTokenKey([]byte(name))...)
nsRaw := storage.Get(ctx, nsKey)
@ -608,21 +595,6 @@ func deleteDomain(ctx storage.Context, name string) {
ns := std.Deserialize(nsRaw.([]byte)).(NameState)
ns.checkAdmin()
countSubDomain := 0
countSubDomainRaw := storage.Get(ctx, countSubDomainsKey)
if countSubDomainRaw != nil {
countSubDomain = common.FromFixedWidth64(countSubDomainRaw.([]byte))
} else {
countSubDomain = countSubdomains(fragments[len(fragments)-1])
}
if countSubDomain > 1 && len(fragments) == 1 {
panic("can't delete TLD domain that has subdomains")
}
countSubDomain = countSubDomain - 1
storage.Put(ctx, countSubDomainsKey, common.ToFixedWidth64(countSubDomain))
globalNSKey := append([]byte{prefixGlobalDomain}, getTokenKey([]byte(name))...)
globalDomainRaw := storage.Get(ctx, globalNSKey)
globalDomain := globalDomainRaw.(string)
@ -636,17 +608,6 @@ func deleteDomain(ctx storage.Context, name string) {
deleteRecords(ctx, name, AAAA)
storage.Delete(ctx, nsKey)
storage.Delete(ctx, append([]byte{prefixRoot}, []byte(name)...))
isAutoCreated := storage.Get(ctx, autoCreatedPrefix)
if countSubDomain == 1 && isAutoCreated != nil && isAutoCreated.(bool) {
deleteDomain(ctx, fragments[len(fragments)-1])
}
if len(fragments) == 1 {
storage.Delete(ctx, countSubDomainsKey)
storage.Delete(ctx, autoCreatedPrefix)
}
runtime.Notify("DeleteDomain", name)
}
@ -720,7 +681,7 @@ func getNameState(ctx storage.Context, tokenID []byte) NameState {
tokenKey := getTokenKey(tokenID)
ns := getNameStateWithKey(ctx, tokenKey)
fragments := std.StringSplit(string(tokenID), ".")
checkParent(ctx, fragments)
checkParentExists(ctx, fragments)
return ns
}
@ -924,14 +885,6 @@ func isValid(address interop.Hash160) bool {
return address != nil && len(address) == interop.Hash160Len
}
// checkCommitteeAndFrostfsID panics if the script container is not signed by the committee.
// or if the owner does not have permission in FrostfsID.
func checkCommitteeAndFrostfsID(ctx storage.Context, owner interop.Hash160) {
if !checkFrostfsID(ctx, owner) {
checkCommittee()
}
}
// checkCommittee panics if the script container is not signed by the committee.
func checkCommittee() {
committee := neo.GetCommittee()
@ -1174,16 +1127,3 @@ func getAllRecords(ctx storage.Context, name string) iterator.Iterator {
recordsKey := getRecordsKey(tokenID, name)
return storage.Find(ctx, recordsKey, storage.ValuesOnly|storage.DeserializeValues)
}
func updateSubdDomainCounter(ctx storage.Context, rootZone []byte, countZone int) {
countSubDomain := 0
delInfoRaw := storage.Get(ctx, append([]byte{prefixCountSubDomains}, rootZone...))
if delInfoRaw != nil {
countSubDomain = common.FromFixedWidth64(delInfoRaw.([]byte))
}
if delInfoRaw != nil || countZone == 1 {
countSubDomain = countSubDomain + 1
storage.Put(ctx, append([]byte{prefixCountSubDomains}, rootZone...), common.ToFixedWidth64(countSubDomain))
}
}

View file

@ -222,11 +222,6 @@ func (c *ContractReader) GetSubjectExtended(addr util.Uint160) ([]stackitem.Item
return unwrap.Array(c.invoker.Call(c.hash, "getSubjectExtended", addr))
}
// GetSubjectKV invokes `getSubjectKV` method of contract.
func (c *ContractReader) GetSubjectKV(addr util.Uint160, name string) (string, error) {
return unwrap.UTF8String(c.invoker.Call(c.hash, "getSubjectKV", addr, name))
}
// GetSubjectKeyByName invokes `getSubjectKeyByName` method of contract.
func (c *ContractReader) GetSubjectKeyByName(ns string, name string) (*keys.PublicKey, error) {
return unwrap.PublicKey(c.invoker.Call(c.hash, "getSubjectKeyByName", ns, name))

View file

@ -91,20 +91,6 @@ func New(actor Actor, hash util.Uint160) *Contract {
return &Contract{ContractReader{nep11ndt.NonDivisibleReader, actor, hash}, nep11ndt.BaseWriter, actor, hash}
}
// GetAllRecords invokes `getAllRecords` method of contract.
func (c *ContractReader) GetAllRecords(name string) (uuid.UUID, result.Iterator, error) {
return unwrap.SessionIterator(c.invoker.Call(c.hash, "getAllRecords", name))
}
// GetAllRecordsExpanded is similar to GetAllRecords (uses the same contract
// method), but can be useful if the server used doesn't support sessions and
// doesn't expand iterators. It creates a script that will get the specified
// number of result items from the iterator right in the VM and return them to
// you. It's only limited by VM stack and GAS available for RPC invocations.
func (c *ContractReader) GetAllRecordsExpanded(name string, _numOfIteratorItems int) ([]stackitem.Item, error) {
return unwrap.Array(c.invoker.CallAndExpandIterator(c.hash, "getAllRecords", _numOfIteratorItems, name))
}
// GetPrice invokes `getPrice` method of contract.
func (c *ContractReader) GetPrice() (*big.Int, error) {
return unwrap.BigInt(c.invoker.Call(c.hash, "getPrice"))
@ -248,6 +234,28 @@ func (c *Contract) DeleteRecordsUnsigned(name string, typ *big.Int) (*transactio
return c.actor.MakeUnsignedCall(c.hash, "deleteRecords", nil, name, typ)
}
// GetAllRecords creates a transaction invoking `getAllRecords` 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) GetAllRecords(name string) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "getAllRecords", name)
}
// GetAllRecordsTransaction creates a transaction invoking `getAllRecords` method of the contract.
// This transaction is signed, but not sent to the network, instead it's
// returned to the caller.
func (c *Contract) GetAllRecordsTransaction(name string) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "getAllRecords", name)
}
// GetAllRecordsUnsigned creates a transaction invoking `getAllRecords` 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) GetAllRecordsUnsigned(name string) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "getAllRecords", nil, name)
}
func (c *Contract) scriptForRegister(name string, owner util.Uint160, email string, refresh *big.Int, retry *big.Int, expire *big.Int, ttl *big.Int) ([]byte, error) {
return smartcontract.CreateCallWithAssertScript(c.hash, "register", name, owner, email, refresh, retry, expire, ttl)
}

View file

@ -238,13 +238,6 @@ func TestFrostFSID_SubjectManagement(t *testing.T) {
subj = parseSubject(t, s)
require.True(t, subjKey.PublicKey().Equal(&subj.PrimaryKey), "keys must be the same")
t.Run("with GetSubject", func(t *testing.T) {
s, err = anonInvoker.TestInvoke(t, getSubjectMethod, subjKey.PublicKey().GetScriptHash())
require.NoError(t, err)
subj = parseSubject(t, s)
require.True(t, subjKey.PublicKey().Equal(&subj.PrimaryKey), "keys must be the same")
})
t.Run("remove subject key", func(t *testing.T) {
anonInvoker.InvokeFail(t, notWitnessedError, removeSubjectKeyMethod, subjKeyAddr, subjNewKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, removeSubjectKeyMethod, subjKeyAddr, subjNewKey.PublicKey().Bytes())
@ -611,44 +604,6 @@ func TestFrostFSID_GroupManagement(t *testing.T) {
})
}
func TestAdditionalKeyFromPrimarySubject(t *testing.T) {
f := newFrostFSIDInvoker(t)
invoker := f.OwnerInvoker()
subjAPrimaryKey, err := keys.NewPrivateKey()
require.NoError(t, err)
subjAKeyAddr := subjAPrimaryKey.PublicKey().GetScriptHash()
subjBPrimaryKey, err := keys.NewPrivateKey()
require.NoError(t, err)
subjBKeyAddr := subjBPrimaryKey.PublicKey().GetScriptHash()
subjCPrimaryKey, err := keys.NewPrivateKey()
require.NoError(t, err)
subjDPrimaryKey, err := keys.NewPrivateKey()
require.NoError(t, err)
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, subjAPrimaryKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, subjBPrimaryKey.PublicKey().Bytes())
invoker.InvokeFail(t, "key is occupied", addSubjectKeyMethod, subjBKeyAddr, subjAPrimaryKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, addSubjectKeyMethod, subjAKeyAddr, subjCPrimaryKey.PublicKey().Bytes())
invoker.InvokeFail(t, "key is occupied", addSubjectKeyMethod, subjBKeyAddr, subjCPrimaryKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, addSubjectKeyMethod, subjAKeyAddr, subjDPrimaryKey.PublicKey().Bytes())
invoker.InvokeFail(t, "key is occupied", addSubjectKeyMethod, subjBKeyAddr, subjDPrimaryKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, removeSubjectKeyMethod, subjAKeyAddr, subjDPrimaryKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, addSubjectKeyMethod, subjBKeyAddr, subjDPrimaryKey.PublicKey().Bytes())
invoker.InvokeFail(t, "key is occupied", addSubjectKeyMethod, subjAKeyAddr, subjDPrimaryKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, deleteSubjectMethod, subjBKeyAddr)
invoker.Invoke(t, stackitem.Null{}, addSubjectKeyMethod, subjAKeyAddr, subjDPrimaryKey.PublicKey().Bytes())
}
func checkPublicKeyResult(t *testing.T, s *vm.Stack, err error, key *keys.PrivateKey) {
if key == nil {
require.ErrorContains(t, err, "not found")

View file

@ -12,12 +12,10 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require"
)
@ -25,7 +23,8 @@ const nnsPath = "../nns"
const msPerYear = 365 * 24 * time.Hour / time.Millisecond
func deployNNS(t *testing.T, e *neotest.Executor, addRoot bool) *neotest.ContractInvoker {
func newNNSInvoker(t *testing.T, addRoot bool) *neotest.ContractInvoker {
e := newExecutor(t)
ctr := neotest.CompileFile(t, e.CommitteeHash, nnsPath, path.Join(nnsPath, "config.yml"))
e.DeployContract(t, ctr, nil)
@ -40,28 +39,6 @@ func deployNNS(t *testing.T, e *neotest.Executor, addRoot bool) *neotest.Contrac
return c
}
func newNNSInvoker(t *testing.T, addRoot bool) *neotest.ContractInvoker {
e := newExecutor(t)
return deployNNS(t, e, addRoot)
}
func newNNSInvokerWithFrostfsID(t *testing.T, addRoot bool) (*neotest.ContractInvoker, *neotest.ContractInvoker) {
e := newExecutor(t)
c := deployNNS(t, e, addRoot)
frostfdID := deployFrostFSIDContract(t, e, e.CommitteeHash)
c.Invoke(t, true, "register",
nns.FrostfsIDNNSName, c.CommitteeHash,
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
c.Invoke(t, stackitem.Null{}, "addRecord",
nns.FrostfsIDNNSName, int64(nns.TXT), frostfdID.StringLE())
c.Invoke(t, stackitem.Null{}, "addRecord",
nns.FrostfsIDNNSName, int64(nns.TXT), address.Uint160ToString(frostfdID))
return e.NewInvoker(c.Hash), e.CommitteeInvoker(frostfdID)
}
func TestNNSGeneric(t *testing.T) {
c := newNNSInvoker(t, false)
@ -123,18 +100,6 @@ func TestNNSRegister(t *testing.T) {
"com", accTop.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl)
c1.Invoke(t, true, "register",
"aa.bb.zz", accTop.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl)
c1.InvokeFail(t, "TLD already exists", "register",
"zz", accTop.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl)
c1.Invoke(t, true, "register",
"xx.bb.zz", accTop.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl)
acc := c.NewAccount(t)
c2 := c.WithSigners(c.Committee, acc)
c2.InvokeFail(t, "not witnessed by admin", "register",
@ -177,10 +142,8 @@ func TestNNSRegister(t *testing.T) {
cAcc := c.WithSigners(acc)
expected = stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray([]byte("testdomain.com")),
stackitem.NewBigInteger(big.NewInt(int64(nns.TXT))),
})
expected = stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte("testdomain.com")),
stackitem.NewBigInteger(big.NewInt(int64(nns.TXT)))})
tx = cAcc.Invoke(t, stackitem.Null{}, "addRecord",
"testdomain.com", int64(nns.TXT), "first TXT record")
c.CheckTxNotificationEvent(t, tx, 0, state.NotificationEvent{ScriptHash: c.Hash, Name: "AddRecord", Item: expected})
@ -196,10 +159,8 @@ func TestNNSRegister(t *testing.T) {
})
c.Invoke(t, expected, "getRecords", "testdomain.com", int64(nns.TXT))
expected = stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray([]byte("testdomain.com")),
stackitem.NewBigInteger(big.NewInt(int64(nns.TXT))),
})
expected = stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte("testdomain.com")),
stackitem.NewBigInteger(big.NewInt(int64(nns.TXT)))})
tx = cAcc.Invoke(t, stackitem.Null{}, "setRecord",
"testdomain.com", int64(nns.TXT), int64(0), "replaced first")
c.CheckTxNotificationEvent(t, tx, 0, state.NotificationEvent{ScriptHash: c.Hash, Name: "AddRecord", Item: expected})
@ -211,10 +172,8 @@ func TestNNSRegister(t *testing.T) {
c.Invoke(t, expected, "getRecords", "testdomain.com", int64(nns.TXT))
tx = cAcc.Invoke(t, stackitem.Null{}, "deleteRecords", "testdomain.com", int64(nns.TXT))
expected = stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray([]byte("testdomain.com")),
stackitem.NewBigInteger(big.NewInt(int64(nns.TXT))),
})
expected = stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte("testdomain.com")),
stackitem.NewBigInteger(big.NewInt(int64(nns.TXT)))})
c.CheckTxNotificationEvent(t, tx, 0, state.NotificationEvent{ScriptHash: c.Hash, Name: "DeleteRecords", Item: expected})
c.Invoke(t, stackitem.Null{}, "getRecords", "testdomain.com", int64(nns.TXT))
@ -261,10 +220,8 @@ func TestNNSRegister(t *testing.T) {
c.Invoke(t, stackitem.Null{}, "getRecords", "testdomain.com", int64(nns.TXT))
tx = cAcc.Invoke(t, stackitem.Null{}, "deleteDomain", "testdomain.com")
expected = stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray([]byte("testdomain.com")),
stackitem.NewBigInteger(big.NewInt(int64(nns.CNAME))),
})
expected = stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte("testdomain.com")),
stackitem.NewBigInteger(big.NewInt(int64(nns.CNAME)))})
c.CheckTxNotificationEvent(t, tx, 0, state.NotificationEvent{ScriptHash: c.Hash, Name: "DeleteRecords", Item: expected})
expected = stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte("testdomain.com"))})
@ -294,23 +251,10 @@ func TestDeleteDomain(t *testing.T) {
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
c1.InvokeFail(t, "domain not found", "deleteDomain", "ru")
c1.InvokeFail(t, "can't delete TLD domain that has subdomains", "deleteDomain", "com")
c1.InvokeFail(t, "can't delete a domain that has subdomains", "deleteDomain", "testdomain.com")
c1.Invoke(t, stackitem.Null{}, "deleteDomain", "domik.testdomain.com")
c1.Invoke(t, stackitem.Null{}, "deleteDomain", "testdomain.com")
c1.Invoke(t, true, "register",
"aa.bb.zz", acc1.ScriptHash(),
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
c1.InvokeFail(t, "TLD already exists", "register",
"zz", acc1.ScriptHash(),
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
c1.Invoke(t, stackitem.Null{}, "deleteDomain", "aa.bb.zz")
c1.Invoke(t, true, "register",
"zz", acc1.ScriptHash(),
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
c1.Invoke(t, true, "register",
"cn", acc1.ScriptHash(),
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
@ -322,18 +266,6 @@ func TestDeleteDomain(t *testing.T) {
c2.Invoke(t, true, "register",
"cn", acc2.ScriptHash(),
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
c1.Invoke(t, true, "register",
"gu.bububu.bubu.bu", c.CommitteeHash,
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
c1.Invoke(t, true, "register",
"buu.gu.bububu.bubu.bu", c.CommitteeHash,
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
c1.Invoke(t, true, "register",
"bubu.bu", c.CommitteeHash,
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
}
func TestGlobalDomain(t *testing.T) {
@ -379,7 +311,6 @@ func TestGlobalDomain(t *testing.T) {
c.InvokeFail(t, "global domain is already taken", "isAvailable", "dom.testdomain.com")
}
func TestTLDRecord(t *testing.T) {
c := newNNSInvoker(t, true)
c.Invoke(t, stackitem.Null{}, "addRecord",
@ -404,6 +335,11 @@ func TestNNSRegisterMulti(t *testing.T) {
cBoth.Invoke(t, true, "register", args...)
c1 := c.WithSigners(acc)
t.Run("parent domain is missing", func(t *testing.T) {
msg := "domain does not exist or is expired: fs.neo.com"
args[0] = "testnet.fs.neo.com"
c1.InvokeFail(t, msg, "register", args...)
})
args[0] = "fs.neo.com"
c1.Invoke(t, true, "register", args...)
@ -559,44 +495,11 @@ func TestNNSSetAdmin(t *testing.T) {
"testdomain.com", int64(nns.TXT), "will be added")
}
func TestNNS_Frostfsid(t *testing.T) {
nnsInv, f := newNNSInvokerWithFrostfsID(t, false)
acc, err := wallet.NewAccount()
require.NoError(t, err)
nnsUserInv := nnsInv.NewInvoker(nnsInv.Hash, newSigner(t, nnsInv, acc))
nnsUserInv.InvokeFail(t, "not witnessed by committee", "register",
"ddd", acc.ScriptHash(),
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
nnsUserInv.InvokeFail(t, "not witnessed by committee", "register",
"testdomain.kz", acc.ScriptHash(),
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
f.Invoke(t, stackitem.Null{}, createSubjectMethod, "", acc.PrivateKey().PublicKey().Bytes())
nnsUserInv.InvokeFail(t, "not witnessed by committee", "register",
"testdomain.kz", acc.ScriptHash(),
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
f.Invoke(t, stackitem.Null{}, setSubjectKVMethod, acc.ScriptHash(), nns.FrostfsIDNNSTLDPermissionKey, nns.FrostfsIDTLDRegistrationAllowed)
nnsUserInv.Invoke(t, true, "register",
"testdomain.kz", acc.ScriptHash(),
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
f.Invoke(t, stackitem.Null{}, deleteSubjectKVMethod, acc.ScriptHash(), nns.FrostfsIDNNSTLDPermissionKey)
nnsUserInv.InvokeFail(t, "not witnessed by committee", "register",
"testdomain.uz", acc.ScriptHash(),
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
}
func TestNNSIsAvailable(t *testing.T) {
c := newNNSInvoker(t, false)
c.Invoke(t, true, "isAvailable", "com")
c.InvokeFail(t, "TLD not found", "isAvailable", "domain.com")
refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104)
c.Invoke(t, true, "register",
@ -609,6 +512,7 @@ func TestNNSIsAvailable(t *testing.T) {
acc := c.NewAccount(t)
c1 := c.WithSigners(c.Committee, acc)
c1.InvokeFail(t, "domain does not exist or is expired: domain.com", "isAvailable", "dom.domain.com")
c1.Invoke(t, true, "register",
"domain.com", acc.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl)
@ -620,6 +524,7 @@ func TestNNSIsAvailable(t *testing.T) {
c.Invoke(t, false, "isAvailable", "domain.com")
c.Invoke(t, true, "isAvailable", "dom.domain.com")
c.InvokeFail(t, "domain does not exist or is expired: dom.domain.com", "isAvailable", "dom.dom.domain.com")
c1.Invoke(t, true, "register",
"dom.domain.com", acc.ScriptHash(),