forked from TrueCloudLab/frostfs-contract
Compare commits
14 commits
fix/change
...
master
Author | SHA1 | Date | |
---|---|---|---|
fe7a767e8f | |||
60b81c4bf6 | |||
a2c2791146 | |||
7a8c64b966 | |||
8b586081eb | |||
4666a953b3 | |||
48f06df25a | |||
645b4cb3c8 | |||
ffd2763094 | |||
5f956751d4 | |||
3f4f8feca7 | |||
a90d54c332 | |||
81853bd242 | |||
d3a85dd028 |
28 changed files with 713 additions and 104 deletions
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
|
@ -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
|
||||||
|
|
|
@ -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
1
.github/CODEOWNERS
vendored
|
@ -1 +0,0 @@
|
||||||
* @carpawell @fyrchik @cthulhu-rider
|
|
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -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
5
CODEOWNERS
Normal 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
|
|
@ -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.
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
v0.19.1
|
v0.20.0
|
||||||
|
|
|
@ -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
84
commonclient/waiter.go
Normal 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
135
commonclient/waiter_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
|
@ -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.
|
||||||
|
|
|
@ -7,6 +7,7 @@ safemethods:
|
||||||
- "getGroupByName"
|
- "getGroupByName"
|
||||||
- "getNamespace"
|
- "getNamespace"
|
||||||
- "getNamespaceExtended"
|
- "getNamespaceExtended"
|
||||||
|
- "getSubjectKV"
|
||||||
- "getSubject"
|
- "getSubject"
|
||||||
- "getSubjectExtended"
|
- "getSubjectExtended"
|
||||||
- "getSubjectByKey"
|
- "getSubjectByKey"
|
||||||
|
|
|
@ -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 |
|
||||||
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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
3
go.mod
|
@ -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
22
go.sum
|
@ -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=
|
||||||
|
|
|
@ -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
|
||||||
|
```
|
|
@ -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
39
nns/frostfsid.go
Normal 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
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
Loading…
Reference in a new issue