Compare commits

...

14 commits

Author SHA1 Message Date
fe7a767e8f
[#131] nns: Declare getAllRecords() as safe
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-11-21 10:23:31 +03:00
60b81c4bf6
[#131] *: Reformat code
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-11-21 10:23:31 +03:00
a2c2791146
[#129] frostfsid: Return subject by an additional address
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-11-19 17:41:06 +03:00
7a8c64b966
[#118] frostfsid: Restrict keys to a single subject
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-11-13 21:35:15 +03:00
8b586081eb
[#125] CODEOWNERS: Refine ownership
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-11-07 09:24:36 +03:00
4666a953b3 [#124] Stop using obsolete .github directory
This commit is a part of multi-repo cleanup effort:
TrueCloudLab/frostfs-infra#136

Signed-off-by: Vitaliy Potyarkin <v.potyarkin@yadro.com>
2024-11-06 15:17:04 +03:00
48f06df25a
[#119] nns/docs: Integrate FrostfsID into NNS
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-11-02 11:25:09 +03:00
645b4cb3c8
[#119] nns: Integrate FrostfsID into NNS
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-11-02 11:24:49 +03:00
ffd2763094
[#119] frostfsid: Add 'getSubjectKV'
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-11-02 11:24:10 +03:00
5f956751d4 [#120] Add waiter to frostfsid client
Signed-off-by: Vladimir Domnich <v.domnich@yadro.com>
2024-10-22 12:40:40 +03:00
3f4f8feca7
[#115] nns: Allow register TLD from nested domains
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-10-15 17:08:18 +03:00
a90d54c332
[#115] nns: Allow register TLD from nested domains
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-10-11 18:44:43 +03:00
81853bd242 Release v0.20.0
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-09-30 12:37:12 +03:00
d3a85dd028 [#112] go.mod: Update go to 1.22
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-09-30 12:32:44 +03:00
28 changed files with 713 additions and 104 deletions

View file

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View file

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

View file

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

1
.github/CODEOWNERS vendored
View file

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

View file

@ -8,7 +8,17 @@ Changelog for FrostFS Contract
### Removed ### Removed
### Updated ### Updated
### Fixed ### Fixed
### Updating from v0.18.0
## [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
## [0.18.0] - 2023-09-14 - Academy of Sciences Glacier ## [0.18.0] - 2023-09-14 - Academy of Sciences Glacier

5
CODEOWNERS Normal file
View file

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

View file

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

View file

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

84
commonclient/waiter.go Normal file
View file

@ -0,0 +1,84 @@
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)
}

135
commonclient/waiter_test.go Normal file
View file

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

View file

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

View file

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

View file

@ -96,6 +96,7 @@ const (
groupSubjectsKeysPrefix = 'G' groupSubjectsKeysPrefix = 'G'
groupCounterKey = 'c' groupCounterKey = 'c'
namespaceGroupsNamesPrefix = 'm' namespaceGroupsNamesPrefix = 'm'
addressPrefix = 'A'
) )
func _deploy(data any, isUpdate bool) { func _deploy(data any, isUpdate bool) {
@ -112,6 +113,27 @@ func _deploy(data any, isUpdate bool) {
storage.Put(ctx, adminKey, args.admin) 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, groupCounterKey, 0)
storage.Put(ctx, namespaceKey(""), std.Serialize(Namespace{})) storage.Put(ctx, namespaceKey(""), std.Serialize(Namespace{}))
@ -182,6 +204,11 @@ func CreateSubject(ns string, key interop.PublicKey) {
panic("key is occupied") panic("key is occupied")
} }
allAddressKey := addressKey(addr)
if storage.Get(ctx, allAddressKey) != nil {
panic("key is occupied by another additional key")
}
nsKey := namespaceKey(ns) nsKey := namespaceKey(ns)
data = storage.Get(ctx, nsKey).([]byte) data = storage.Get(ctx, nsKey).([]byte)
if data == nil { if data == nil {
@ -197,6 +224,7 @@ func CreateSubject(ns string, key interop.PublicKey) {
nsSubjKey := namespaceSubjectKey(ns, addr) nsSubjKey := namespaceSubjectKey(ns, addr)
storage.Put(ctx, nsSubjKey, []byte{1}) storage.Put(ctx, nsSubjKey, []byte{1})
storage.Put(ctx, allAddressKey, true)
runtime.Notify("CreateSubject", interop.Hash160(addr)) runtime.Notify("CreateSubject", interop.Hash160(addr))
} }
@ -213,6 +241,11 @@ func AddSubjectKey(addr interop.Hash160, key interop.PublicKey) {
panic("incorrect public key length") panic("incorrect public key length")
} }
addressKey := addressKey(contract.CreateStandardAccount(key))
if storage.Get(ctx, addressKey) != nil {
panic("key is occupied")
}
saKey := subjectAdditionalKey(key, addr) saKey := subjectAdditionalKey(key, addr)
data := storage.Get(ctx, saKey).([]byte) data := storage.Get(ctx, saKey).([]byte)
if data != nil { if data != nil {
@ -230,6 +263,7 @@ func AddSubjectKey(addr interop.Hash160, key interop.PublicKey) {
subject.AdditionalKeys = append(subject.AdditionalKeys, key) subject.AdditionalKeys = append(subject.AdditionalKeys, key)
storage.Put(ctx, sKey, std.Serialize(subject)) storage.Put(ctx, sKey, std.Serialize(subject))
storage.Put(ctx, addressKey, true)
runtime.Notify("AddSubjectKey", addr, key) runtime.Notify("AddSubjectKey", addr, key)
} }
@ -269,6 +303,7 @@ func RemoveSubjectKey(addr interop.Hash160, key interop.PublicKey) {
subject.AdditionalKeys = additionalKeys subject.AdditionalKeys = additionalKeys
storage.Put(ctx, sKey, std.Serialize(subject)) storage.Put(ctx, sKey, std.Serialize(subject))
storage.Delete(ctx, addressKey(contract.CreateStandardAccount(key)))
runtime.Notify("RemoveSubjectKey", addr, key) runtime.Notify("RemoveSubjectKey", addr, key)
} }
@ -362,6 +397,7 @@ func DeleteSubject(addr interop.Hash160) {
for i := 0; i < len(subj.AdditionalKeys); i++ { for i := 0; i < len(subj.AdditionalKeys); i++ {
storage.Delete(ctx, subjectAdditionalKey(subj.AdditionalKeys[i], addr)) storage.Delete(ctx, subjectAdditionalKey(subj.AdditionalKeys[i], addr))
storage.Delete(ctx, addressKey(contract.CreateStandardAccount(subj.AdditionalKeys[i])))
} }
storage.Delete(ctx, sKey) storage.Delete(ctx, sKey)
@ -381,7 +417,12 @@ 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 {
panic("subject not found") 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) return std.Deserialize(data).(Subject)
@ -433,21 +474,25 @@ func GetSubjectByKey(key interop.PublicKey) Subject {
return std.Deserialize(data).(Subject) return std.Deserialize(data).(Subject)
} }
saPrefix := subjectAdditionalPrefix(key) addr := getPrimaryAddr(ctx, contract.CreateStandardAccount(key))
it := storage.Find(ctx, saPrefix, storage.KeysOnly|storage.RemovePrefix) sKey = subjectKeyFromAddr(addr)
for iterator.Next(it) { data = storage.Get(ctx, sKey).([]byte)
addr := iterator.Value(it).([]byte) if data != nil {
sKey = subjectKeyFromAddr(addr) return std.Deserialize(data).(Subject)
data = storage.Get(ctx, sKey).([]byte)
if data != nil {
return std.Deserialize(data).(Subject)
}
break
} }
panic("subject not found") 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)
}
panic("subject not found")
}
// 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)
@ -472,6 +517,34 @@ func GetSubjectKeyByName(ns, name string) interop.PublicKey {
return subjKey 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 { func ListSubjects() iterator.Iterator {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
return storage.Find(ctx, []byte{subjectKeysPrefix}, storage.KeysOnly|storage.RemovePrefix) return storage.Find(ctx, []byte{subjectKeysPrefix}, storage.KeysOnly|storage.RemovePrefix)
@ -1001,3 +1074,7 @@ func idToBytes(itemID int) []byte {
zeros := make([]byte, 8-ln) zeros := make([]byte, 8-ln)
return append(b, zeros...) 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 module git.frostfs.info/TrueCloudLab/frostfs-contract
go 1.20 go 1.22
require ( require (
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
@ -36,6 +36,7 @@ require (
github.com/prometheus/procfs v0.12.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/russross/blackfriday/v2 v2.1.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/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 // indirect
github.com/twmb/murmur3 v1.1.8 // indirect github.com/twmb/murmur3 v1.1.8 // indirect
github.com/urfave/cli/v2 v2.27.2 // indirect github.com/urfave/cli/v2 v2.27.2 // indirect

22
go.sum
View file

@ -1,4 +1,5 @@
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 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 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 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= github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c=
@ -6,6 +7,7 @@ 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 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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 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 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ=
github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= 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= github.com/consensys/gnark-crypto v0.12.2-0.20231013160410-1f65e75b6dfb h1:f0BMgIjhZy4lSRHCXFbQst85f5agZAjtDMixQqBWNpc=
@ -17,6 +19,7 @@ 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 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= 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 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.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 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
@ -28,12 +31,14 @@ 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.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 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 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 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 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.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.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.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 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/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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -45,9 +50,13 @@ github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= 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/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 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 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 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 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 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY=
github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= 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= github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU=
@ -58,15 +67,19 @@ 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 h1:mD9hU3v+zJcnHAVmHnZKt3I++tvn30gBj2rP2PocZMk=
github.com/nspcc-dev/go-ordered-json v0.0.0-20240301084351-0246b013f8b2/go.mod h1:U5VfmPNM88P4RORFb6KSUVBdJBDhlqggJZYGXGPxOcc= 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 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 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 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 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/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 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 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 h1:8wWxkamHWFmO790GsewSoKUSJjVnL1fmdRpokU/RgRM=
github.com/nspcc-dev/rfc6979 v0.2.1/go.mod h1:Tk7h5kyUWkhjyO3zUgFFhy1v2vQv3BvQEntakdtqrWc= 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 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 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@ -92,6 +105,8 @@ 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/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 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 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 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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= github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 h1:xQdMZ1WLrgkkvOZ/LDQxjVxMLdby7osSh4ZEVa5sIjs=
@ -105,6 +120,7 @@ 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 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= 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 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 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
@ -114,6 +130,7 @@ 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 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 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 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 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -124,6 +141,7 @@ golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 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.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 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-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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -149,7 +167,9 @@ 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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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 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 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-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-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -160,12 +180,14 @@ google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGm
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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 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 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/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 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 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.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.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 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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU=

View file

@ -44,3 +44,32 @@ The committee makes new tokens (domains), sets, and charges a fee for issuance.
## Globally Unique Domain Zone ## Globally Unique Domain Zone
For more information, see [here](../docs/globally-unique-domain-zone.md). 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,6 +2,7 @@ name: "NameService"
supportedstandards: ["NEP-11"] supportedstandards: ["NEP-11"]
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf", safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf",
"tokens", "properties", "roots", "getPrice", "isAvailable", "getRecords", "tokens", "properties", "roots", "getPrice", "isAvailable", "getRecords",
"getAllRecords",
"resolve", "version"] "resolve", "version"]
events: events:
- name: RegisterDomain - name: RegisterDomain

39
nns/frostfsid.go Normal file
View file

@ -0,0 +1,39 @@
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,9 +41,14 @@ const (
// prefixRecord contains map from (token key + hash160(token name) + record type) // prefixRecord contains map from (token key + hash160(token name) + record type)
// to record. // to record.
prefixRecord byte = 0x22 prefixRecord byte = 0x22
//prefixGlobalDomain contains a flag indicating that this domain was created using GlobalDomain. // prefixGlobalDomain contains a flag indicating that this domain was created using GlobalDomain.
//This is necessary to distinguish it from regular CNAME records. // This is necessary to distinguish it from regular CNAME records.
prefixGlobalDomain byte = 0x23 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. // Values constraints.
@ -74,7 +79,7 @@ const (
const ( const (
// Cnametgt is a special TXT record ensuring all created subdomains point to the global domain - the value of this variable. // 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" Cnametgt = "cnametgt"
) )
@ -234,13 +239,10 @@ func IsAvailable(name string) bool {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
l := len(fragments) l := len(fragments)
if storage.Get(ctx, append([]byte{prefixRoot}, []byte(fragments[l-1])...)) == nil { if storage.Get(ctx, append([]byte{prefixRoot}, []byte(fragments[l-1])...)) == nil {
if l != 1 {
panic("TLD not found")
}
return true return true
} }
checkParentExists(ctx, fragments) checkParent(ctx, fragments)
checkAvailableGlobalDomain(ctx, name) checkAvailableGlobalDomain(ctx, name)
return storage.Get(ctx, append([]byte{prefixName}, getTokenKey([]byte(name))...)) == nil return storage.Get(ctx, append([]byte{prefixName}, getTokenKey([]byte(name))...)) == nil
} }
@ -304,33 +306,27 @@ func extractCnametgt(ctx storage.Context, name, domain string) string {
return fragments[0] + "." + globalDomain return fragments[0] + "." + globalDomain
} }
// checkParentExists panics if any domain from fragments doesn't exist or is expired. // checkParent returns parent domain or empty string if domain not found.
func checkParentExists(ctx storage.Context, fragments []string) { func checkParent(ctx storage.Context, fragments []string) 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()) now := int64(runtime.GetTime())
last := len(fragments) - 1 last := len(fragments) - 1
name := fragments[last] name := fragments[last]
parent := ""
for i := last; i > 0; i-- { for i := last; i > 0; i-- {
if i != last { if i != last {
name = fragments[i] + "." + name name = fragments[i] + "." + name
} }
nsBytes := storage.Get(ctx, append([]byte{prefixName}, getTokenKey([]byte(name))...)) nsBytes := storage.Get(ctx, append([]byte{prefixName}, getTokenKey([]byte(name))...))
if nsBytes == nil { if nsBytes == nil {
return name continue
} }
ns := std.Deserialize(nsBytes.([]byte)).(NameState) ns := std.Deserialize(nsBytes.([]byte)).(NameState)
if now >= ns.Expiration { if now >= ns.Expiration {
return name panic("domain expired: " + name)
} }
parent = name
} }
return "" return parent
} }
// Register registers a new domain with the specified owner and name if it's available. // Register registers a new domain with the specified owner and name if it's available.
@ -342,24 +338,31 @@ 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. // 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 { func register(ctx storage.Context, name string, owner interop.Hash160, email string, refresh, retry, expire, ttl int) bool {
fragments := splitAndCheck(name) fragments := splitAndCheck(name)
l := len(fragments) countZone := len(fragments)
tldKey := append([]byte{prefixRoot}, []byte(fragments[l-1])...) rootZone := []byte(fragments[countZone-1])
tldKey := append([]byte{prefixRoot}, rootZone...)
tldBytes := storage.Get(ctx, tldKey) tldBytes := storage.Get(ctx, tldKey)
if l == 1 { if countZone == 1 {
checkCommittee() checkCommitteeAndFrostfsID(ctx, owner)
if tldBytes != nil { if tldBytes != nil {
panic("TLD already exists") panic("TLD already exists")
} }
storage.Put(ctx, tldKey, 0) storage.Put(ctx, tldKey, 0)
} else { } else {
if tldBytes == nil { parent := checkParent(ctx, fragments)
panic("TLD not found") if parent == "" {
} parent = fragments[len(fragments)-1]
checkParentExists(ctx, fragments)
parentKey := getTokenKey([]byte(name[len(fragments[0])+1:])) storage.Put(ctx, append([]byte{prefixAutoCreated}, rootZone...), true)
register(ctx, parent, owner, email, refresh, retry, expire, ttl)
}
parentKey := getTokenKey([]byte(parent))
nsBytes := storage.Get(ctx, append([]byte{prefixName}, parentKey...)) nsBytes := storage.Get(ctx, append([]byte{prefixName}, parentKey...))
if nsBytes == nil {
panic("parent does not exist:" + parent)
}
ns := std.Deserialize(nsBytes.([]byte)).(NameState) ns := std.Deserialize(nsBytes.([]byte)).(NameState)
ns.checkAdmin() ns.checkAdmin()
@ -403,6 +406,7 @@ func register(ctx storage.Context, name string, owner interop.Hash160, email str
} }
checkAvailableGlobalDomain(ctx, name) checkAvailableGlobalDomain(ctx, name)
updateSubdDomainCounter(ctx, rootZone, countZone)
putNameStateWithKey(ctx, tokenKey, ns) putNameStateWithKey(ctx, tokenKey, ns)
putSoaRecord(ctx, name, email, refresh, retry, expire, ttl) putSoaRecord(ctx, name, email, refresh, retry, expire, ttl)
updateBalance(ctx, []byte(name), owner, +1) updateBalance(ctx, []byte(name), owner, +1)
@ -577,14 +581,23 @@ func DeleteDomain(name string) {
deleteDomain(ctx, name) deleteDomain(ctx, name)
} }
func deleteDomain(ctx storage.Context, name string) { func countSubdomains(name string) int {
countSubDomains := 0
it := Tokens() it := Tokens()
for iterator.Next(it) { for iterator.Next(it) {
domain := iterator.Value(it) domain := iterator.Value(it)
if std.MemorySearch([]byte(domain.(string)), []byte(name)) > 0 { if std.MemorySearch([]byte(domain.(string)), []byte(name)) > 0 {
panic("can't delete a domain that has subdomains") countSubDomains = countSubDomains + 1
} }
} }
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))...) nsKey := append([]byte{prefixName}, getTokenKey([]byte(name))...)
nsRaw := storage.Get(ctx, nsKey) nsRaw := storage.Get(ctx, nsKey)
@ -595,6 +608,21 @@ func deleteDomain(ctx storage.Context, name string) {
ns := std.Deserialize(nsRaw.([]byte)).(NameState) ns := std.Deserialize(nsRaw.([]byte)).(NameState)
ns.checkAdmin() 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))...) globalNSKey := append([]byte{prefixGlobalDomain}, getTokenKey([]byte(name))...)
globalDomainRaw := storage.Get(ctx, globalNSKey) globalDomainRaw := storage.Get(ctx, globalNSKey)
globalDomain := globalDomainRaw.(string) globalDomain := globalDomainRaw.(string)
@ -608,6 +636,17 @@ func deleteDomain(ctx storage.Context, name string) {
deleteRecords(ctx, name, AAAA) deleteRecords(ctx, name, AAAA)
storage.Delete(ctx, nsKey) storage.Delete(ctx, nsKey)
storage.Delete(ctx, append([]byte{prefixRoot}, []byte(name)...)) 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) runtime.Notify("DeleteDomain", name)
} }
@ -681,7 +720,7 @@ func getNameState(ctx storage.Context, tokenID []byte) NameState {
tokenKey := getTokenKey(tokenID) tokenKey := getTokenKey(tokenID)
ns := getNameStateWithKey(ctx, tokenKey) ns := getNameStateWithKey(ctx, tokenKey)
fragments := std.StringSplit(string(tokenID), ".") fragments := std.StringSplit(string(tokenID), ".")
checkParentExists(ctx, fragments) checkParent(ctx, fragments)
return ns return ns
} }
@ -885,6 +924,14 @@ func isValid(address interop.Hash160) bool {
return address != nil && len(address) == interop.Hash160Len 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. // checkCommittee panics if the script container is not signed by the committee.
func checkCommittee() { func checkCommittee() {
committee := neo.GetCommittee() committee := neo.GetCommittee()
@ -1127,3 +1174,16 @@ func getAllRecords(ctx storage.Context, name string) iterator.Iterator {
recordsKey := getRecordsKey(tokenID, name) recordsKey := getRecordsKey(tokenID, name)
return storage.Find(ctx, recordsKey, storage.ValuesOnly|storage.DeserializeValues) 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,6 +222,11 @@ func (c *ContractReader) GetSubjectExtended(addr util.Uint160) ([]stackitem.Item
return unwrap.Array(c.invoker.Call(c.hash, "getSubjectExtended", addr)) 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. // GetSubjectKeyByName invokes `getSubjectKeyByName` method of contract.
func (c *ContractReader) GetSubjectKeyByName(ns string, name string) (*keys.PublicKey, error) { func (c *ContractReader) GetSubjectKeyByName(ns string, name string) (*keys.PublicKey, error) {
return unwrap.PublicKey(c.invoker.Call(c.hash, "getSubjectKeyByName", ns, name)) return unwrap.PublicKey(c.invoker.Call(c.hash, "getSubjectKeyByName", ns, name))

View file

@ -91,6 +91,20 @@ func New(actor Actor, hash util.Uint160) *Contract {
return &Contract{ContractReader{nep11ndt.NonDivisibleReader, actor, hash}, nep11ndt.BaseWriter, actor, hash} 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. // GetPrice invokes `getPrice` method of contract.
func (c *ContractReader) GetPrice() (*big.Int, error) { func (c *ContractReader) GetPrice() (*big.Int, error) {
return unwrap.BigInt(c.invoker.Call(c.hash, "getPrice")) return unwrap.BigInt(c.invoker.Call(c.hash, "getPrice"))
@ -234,28 +248,6 @@ func (c *Contract) DeleteRecordsUnsigned(name string, typ *big.Int) (*transactio
return c.actor.MakeUnsignedCall(c.hash, "deleteRecords", nil, name, typ) 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) { 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) return smartcontract.CreateCallWithAssertScript(c.hash, "register", name, owner, email, refresh, retry, expire, ttl)
} }

View file

@ -238,6 +238,13 @@ func TestFrostFSID_SubjectManagement(t *testing.T) {
subj = parseSubject(t, s) subj = parseSubject(t, s)
require.True(t, subjKey.PublicKey().Equal(&subj.PrimaryKey), "keys must be the same") 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) { t.Run("remove subject key", func(t *testing.T) {
anonInvoker.InvokeFail(t, notWitnessedError, removeSubjectKeyMethod, subjKeyAddr, subjNewKey.PublicKey().Bytes()) anonInvoker.InvokeFail(t, notWitnessedError, removeSubjectKeyMethod, subjKeyAddr, subjNewKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, removeSubjectKeyMethod, subjKeyAddr, subjNewKey.PublicKey().Bytes()) invoker.Invoke(t, stackitem.Null{}, removeSubjectKeyMethod, subjKeyAddr, subjNewKey.PublicKey().Bytes())
@ -604,6 +611,44 @@ 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) { func checkPublicKeyResult(t *testing.T, s *vm.Stack, err error, key *keys.PrivateKey) {
if key == nil { if key == nil {
require.ErrorContains(t, err, "not found") require.ErrorContains(t, err, "not found")

View file

@ -12,10 +12,12 @@ import (
"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/core/state" "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/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/neotest"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas" "github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"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"
) )
@ -23,8 +25,7 @@ const nnsPath = "../nns"
const msPerYear = 365 * 24 * time.Hour / time.Millisecond const msPerYear = 365 * 24 * time.Hour / time.Millisecond
func newNNSInvoker(t *testing.T, addRoot bool) *neotest.ContractInvoker { func deployNNS(t *testing.T, e *neotest.Executor, addRoot bool) *neotest.ContractInvoker {
e := newExecutor(t)
ctr := neotest.CompileFile(t, e.CommitteeHash, nnsPath, path.Join(nnsPath, "config.yml")) ctr := neotest.CompileFile(t, e.CommitteeHash, nnsPath, path.Join(nnsPath, "config.yml"))
e.DeployContract(t, ctr, nil) e.DeployContract(t, ctr, nil)
@ -39,6 +40,28 @@ func newNNSInvoker(t *testing.T, addRoot bool) *neotest.ContractInvoker {
return c 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) { func TestNNSGeneric(t *testing.T) {
c := newNNSInvoker(t, false) c := newNNSInvoker(t, false)
@ -100,6 +123,18 @@ func TestNNSRegister(t *testing.T) {
"com", accTop.ScriptHash(), "com", accTop.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl) "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) acc := c.NewAccount(t)
c2 := c.WithSigners(c.Committee, acc) c2 := c.WithSigners(c.Committee, acc)
c2.InvokeFail(t, "not witnessed by admin", "register", c2.InvokeFail(t, "not witnessed by admin", "register",
@ -142,8 +177,10 @@ func TestNNSRegister(t *testing.T) {
cAcc := c.WithSigners(acc) cAcc := c.WithSigners(acc)
expected = stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte("testdomain.com")), expected = stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(nns.TXT)))}) stackitem.NewByteArray([]byte("testdomain.com")),
stackitem.NewBigInteger(big.NewInt(int64(nns.TXT))),
})
tx = cAcc.Invoke(t, stackitem.Null{}, "addRecord", tx = cAcc.Invoke(t, stackitem.Null{}, "addRecord",
"testdomain.com", int64(nns.TXT), "first TXT record") "testdomain.com", int64(nns.TXT), "first TXT record")
c.CheckTxNotificationEvent(t, tx, 0, state.NotificationEvent{ScriptHash: c.Hash, Name: "AddRecord", Item: expected}) c.CheckTxNotificationEvent(t, tx, 0, state.NotificationEvent{ScriptHash: c.Hash, Name: "AddRecord", Item: expected})
@ -159,8 +196,10 @@ func TestNNSRegister(t *testing.T) {
}) })
c.Invoke(t, expected, "getRecords", "testdomain.com", int64(nns.TXT)) c.Invoke(t, expected, "getRecords", "testdomain.com", int64(nns.TXT))
expected = stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte("testdomain.com")), expected = stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(nns.TXT)))}) stackitem.NewByteArray([]byte("testdomain.com")),
stackitem.NewBigInteger(big.NewInt(int64(nns.TXT))),
})
tx = cAcc.Invoke(t, stackitem.Null{}, "setRecord", tx = cAcc.Invoke(t, stackitem.Null{}, "setRecord",
"testdomain.com", int64(nns.TXT), int64(0), "replaced first") "testdomain.com", int64(nns.TXT), int64(0), "replaced first")
c.CheckTxNotificationEvent(t, tx, 0, state.NotificationEvent{ScriptHash: c.Hash, Name: "AddRecord", Item: expected}) c.CheckTxNotificationEvent(t, tx, 0, state.NotificationEvent{ScriptHash: c.Hash, Name: "AddRecord", Item: expected})
@ -172,8 +211,10 @@ func TestNNSRegister(t *testing.T) {
c.Invoke(t, expected, "getRecords", "testdomain.com", int64(nns.TXT)) c.Invoke(t, expected, "getRecords", "testdomain.com", int64(nns.TXT))
tx = cAcc.Invoke(t, stackitem.Null{}, "deleteRecords", "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")), expected = stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(nns.TXT)))}) 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.CheckTxNotificationEvent(t, tx, 0, state.NotificationEvent{ScriptHash: c.Hash, Name: "DeleteRecords", Item: expected})
c.Invoke(t, stackitem.Null{}, "getRecords", "testdomain.com", int64(nns.TXT)) c.Invoke(t, stackitem.Null{}, "getRecords", "testdomain.com", int64(nns.TXT))
@ -220,8 +261,10 @@ func TestNNSRegister(t *testing.T) {
c.Invoke(t, stackitem.Null{}, "getRecords", "testdomain.com", int64(nns.TXT)) c.Invoke(t, stackitem.Null{}, "getRecords", "testdomain.com", int64(nns.TXT))
tx = cAcc.Invoke(t, stackitem.Null{}, "deleteDomain", "testdomain.com") tx = cAcc.Invoke(t, stackitem.Null{}, "deleteDomain", "testdomain.com")
expected = stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte("testdomain.com")), expected = stackitem.NewArray([]stackitem.Item{
stackitem.NewBigInteger(big.NewInt(int64(nns.CNAME)))}) 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}) c.CheckTxNotificationEvent(t, tx, 0, state.NotificationEvent{ScriptHash: c.Hash, Name: "DeleteRecords", Item: expected})
expected = stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte("testdomain.com"))}) expected = stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte("testdomain.com"))})
@ -251,10 +294,23 @@ func TestDeleteDomain(t *testing.T) {
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL) "myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
c1.InvokeFail(t, "domain not found", "deleteDomain", "ru") c1.InvokeFail(t, "domain not found", "deleteDomain", "ru")
c1.InvokeFail(t, "can't delete a domain that has subdomains", "deleteDomain", "testdomain.com") c1.InvokeFail(t, "can't delete TLD domain that has subdomains", "deleteDomain", "com")
c1.Invoke(t, stackitem.Null{}, "deleteDomain", "domik.testdomain.com")
c1.Invoke(t, stackitem.Null{}, "deleteDomain", "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", c1.Invoke(t, true, "register",
"cn", acc1.ScriptHash(), "cn", acc1.ScriptHash(),
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL) "myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
@ -266,6 +322,18 @@ func TestDeleteDomain(t *testing.T) {
c2.Invoke(t, true, "register", c2.Invoke(t, true, "register",
"cn", acc2.ScriptHash(), "cn", acc2.ScriptHash(),
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL) "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) { func TestGlobalDomain(t *testing.T) {
@ -311,6 +379,7 @@ func TestGlobalDomain(t *testing.T) {
c.InvokeFail(t, "global domain is already taken", "isAvailable", "dom.testdomain.com") c.InvokeFail(t, "global domain is already taken", "isAvailable", "dom.testdomain.com")
} }
func TestTLDRecord(t *testing.T) { func TestTLDRecord(t *testing.T) {
c := newNNSInvoker(t, true) c := newNNSInvoker(t, true)
c.Invoke(t, stackitem.Null{}, "addRecord", c.Invoke(t, stackitem.Null{}, "addRecord",
@ -335,11 +404,6 @@ func TestNNSRegisterMulti(t *testing.T) {
cBoth.Invoke(t, true, "register", args...) cBoth.Invoke(t, true, "register", args...)
c1 := c.WithSigners(acc) 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" args[0] = "fs.neo.com"
c1.Invoke(t, true, "register", args...) c1.Invoke(t, true, "register", args...)
@ -495,11 +559,44 @@ func TestNNSSetAdmin(t *testing.T) {
"testdomain.com", int64(nns.TXT), "will be added") "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) { func TestNNSIsAvailable(t *testing.T) {
c := newNNSInvoker(t, false) c := newNNSInvoker(t, false)
c.Invoke(t, true, "isAvailable", "com") 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) refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104)
c.Invoke(t, true, "register", c.Invoke(t, true, "register",
@ -512,7 +609,6 @@ func TestNNSIsAvailable(t *testing.T) {
acc := c.NewAccount(t) acc := c.NewAccount(t)
c1 := c.WithSigners(c.Committee, acc) 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", c1.Invoke(t, true, "register",
"domain.com", acc.ScriptHash(), "domain.com", acc.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl) "myemail@frostfs.info", refresh, retry, expire, ttl)
@ -524,7 +620,6 @@ func TestNNSIsAvailable(t *testing.T) {
c.Invoke(t, false, "isAvailable", "domain.com") c.Invoke(t, false, "isAvailable", "domain.com")
c.Invoke(t, true, "isAvailable", "dom.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", c1.Invoke(t, true, "register",
"dom.domain.com", acc.ScriptHash(), "dom.domain.com", acc.ScriptHash(),