Compare commits

..

76 commits

Author SHA1 Message Date
201db45bd7
[#142] common: Update version
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-12-12 15:25:14 +03:00
e002a1fa80
[#142] common: Downgrade previous version
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-12-12 15:25:02 +03:00
31709a3d95
[#141] frostfsid: Remove address index in DeleteSubject()
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-12-12 14:42:03 +03:00
3c7a9cb743
[#136] container/test: Move container domain to constant
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-12-12 13:19:34 +03:00
780b48cedf
[#136] container: Add SetAdmin
SetAdmin will allow to manage the root domain of the container.

Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-12-12 13:19:34 +03:00
a3021f18cf [#135] frostfsid: Make migration idempotent
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-12-12 06:28:19 +00:00
891e268170
[#138] frostfs: Remove AlphabetUpdate event from the configuration
It does not exist in code.
Refs #7.

Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-12-11 14:36:55 +03:00
762d7f9f9f
[#134] container: Use Hash256 for container ID in Delete()
This is what we use inside Put(). It leads to a better auto-generated
code.

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

Signed-off-by: Vitaliy Potyarkin <v.potyarkin@yadro.com>
2024-11-06 15:17:04 +03:00
48f06df25a
[#119] nns/docs: Integrate FrostfsID into NNS
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-11-02 11:25:09 +03:00
645b4cb3c8
[#119] nns: Integrate FrostfsID into NNS
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-11-02 11:24:49 +03:00
ffd2763094
[#119] frostfsid: Add 'getSubjectKV'
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-11-02 11:24:10 +03:00
5f956751d4 [#120] Add waiter to frostfsid client
Signed-off-by: Vladimir Domnich <v.domnich@yadro.com>
2024-10-22 12:40:40 +03:00
3f4f8feca7
[#115] nns: Allow register TLD from nested domains
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-10-15 17:08:18 +03:00
a90d54c332
[#115] nns: Allow register TLD from nested domains
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-10-11 18:44:43 +03:00
81853bd242 Release v0.20.0
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-09-30 12:37:12 +03:00
d3a85dd028 [#112] go.mod: Update go to 1.22
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-09-30 12:32:44 +03:00
ff2d165c28 [#114] nns: Add docs
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-09-27 17:51:29 +03:00
9b532320b1 [#114] nns: Restrict 'DeleteDomain'
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-09-27 17:51:13 +03:00
8b7f44adef [#114] nns: Add 'DeleteRecord'
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-09-27 17:50:26 +03:00
3e221b973a [#113] Regenerate wrappers
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-09-09 10:27:09 +03:00
d36120014d [#113] .forgejo: Add generate-wrappers action
Make it easier to maintain auto-generated code in the actual state.

Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-09-09 10:26:56 +03:00
2a1bf77d74 [#113] go.mod: Update neo-go
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-09-09 09:35:31 +03:00
82641e2e99 [#109] nns: Add notification sending
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-09-09 06:24:55 +00:00
6e244fac04 [#110] nns: Remove unused config
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-09-06 12:06:19 +03:00
c142971bfd [#107] Add client method ListFullSubjects
Signed-off-by: d.zverev <d.zverev@yadro.com>
2024-08-27 13:18:00 +00:00
cee957e85a [#105] policy: Add ListChainNames method
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2024-08-27 12:03:05 +03:00
0befe361fe [#104] rpcclient: Regenerate wrappers
Introduced in #102.

Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-08-19 18:08:51 +03:00
ba7329c3a7 [#103] common: Disallow downgrading contracts
`PrevVersion` marks suitable version that we can upgrade from.
However, we can have multiple minor versions, so, currently an upgrade
from v0.19.3 to v0.19.1 is possible. Prevent this with an additional
check.

Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-08-19 13:57:03 +03:00
82e04b6c32 [#102] nns: Support global domain
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-08-16 16:38:54 +03:00
b2eb585bb6 [#102] nns: Support global domain
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-08-16 16:37:17 +03:00
49e5270f67 [#92] nns: Mention domain in panic messages
Signed-off-by: Ekaterina Lebedeva <ekaterina.lebedeva@yadro.com>
2024-06-21 16:12:49 +03:00
2aba66806c [#93] frostfsid: Move struct parsers to separate file
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-06-21 14:33:04 +03:00
ba4ef7bd22 [#89] frostfsid: Add description for FrostFS ID
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-05-24 21:32:20 +03:00
db36131800 [#90] policy: Add constants for more targets
Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2024-04-17 11:01:07 +03:00
a3d5e02f20 [#81] Add domain in the error message in NNS
Signed-off-by: Ekaterina Lebedeva <ekaterina.lebedeva@yadro.com>
2024-04-16 18:05:46 +03:00
6eb492025b [#86] .forgejo: Update go version
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-04-09 14:57:29 +03:00
1addbfef2d [#86] .forgejo: Update DCO action version
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-04-09 14:57:29 +03:00
e7a05a49ff [#XX] client: Terminate session in ReadIteratorItems
* Make an invoker terminate session by its ID before return, otherwise,
  it may lead to `max session capacity reached error`.

Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
2024-04-09 14:15:39 +03:00
694daebb19 [#84] policy: Fix IteratorChainsByPrefix method
* If numeric mapping does not exists, then assign id to 0.
* Add check to unit-test.

Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
2024-04-04 15:43:19 +03:00
2574b2840e [#84] rpcclient: Regenerate interface for Policy
Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
2024-04-04 10:26:28 +03:00
42344eaa69 [#83] container: Remove outdated migration code
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-03-14 10:03:23 +03:00
e27b8ad306 [#83] policy: Allow to update contract
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-03-14 10:03:23 +03:00
5119f655fe [#82] common: Update version
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-03-13 20:57:09 +03:00
c9c53bb9ec [#17] frostfs: Remove method alphabetAddress
Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
2024-03-05 12:59:03 +03:00
43c90af97d [#78] policy: Fix counter key prefix name
* Fix counterKey: "counter" may conflict with 'c' prefix.

Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
2024-02-20 11:58:33 +03:00
d9f523ee07 [#78] policy: Introduce ListTargets method for Policy contract
* Introduce a new method ListTargets that lists targets by kind.
* Slightly fix key mapping - also concatenate kind to prefix.
* Write unit-tests.
* Regenerate rpcclient.

Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
2024-02-20 11:57:34 +03:00
55a4163951 [#76] frostfsid: Add GetGroupByName method
Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
2024-02-01 16:06:17 +03:00
6e72d0b3b4 [#75] policy: Fix compiler error
Refs #71

Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-01-30 16:37:37 +03:00
e0f6fe3bc9 [#61] policy: Shorten policy contract keys
* Map long entity names to 8-bytes long numbers.
* Add desctiption to docs.

Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
2024-01-30 12:27:57 +00:00
074a241272 [#74] proxy: Allow to own NNS domains
Because `Verify` is flexible enough, any funds transferred to the proxy
contract can be moved out with the help of the committee. Thus,
implementing onNEP11Payment() is enough.

Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-01-29 14:03:06 +03:00
2efebf4206 [#74] tests: Do not compile proxy contract twice
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-01-29 14:02:18 +03:00
60e8abbf49 [#74] tests: Actualize proxy contract deploy parameters
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-01-29 14:02:18 +03:00
93781f1149 [#66] policy: Support IteratorChainsByPrefix method
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-01-26 17:41:04 +03:00
da8ec5b447 [#73] nns: Allow 2-byte domain names
frostfs-node uses `.ns` domain, must be supported.

Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-01-25 16:21:58 +03:00
f2a82aa635 [#69] frostfsid: Add new client init function
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2024-01-15 11:29:15 +03:00
a7c45fdd0d [#68] policy: Add Version() method
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-01-11 15:39:19 +03:00
5363aaf16a [#68] rpcclient: Regenerate wrappers after neo-go update
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-01-11 15:39:19 +03:00
86e6d4d334 [#64] commonclient: Use partial unwrap in ReadIteratorItems()
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-12-29 14:06:46 +00:00
8c7925d3c0 [#64] go.mod: Update neo-go
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-12-29 14:06:46 +00:00
bce7ef18c8 [#63] frostfsid/client: Support proxy
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-12-18 11:43:46 +03:00
23e85d11c4 [#53] proxy: Allow using proxy by trusted accounts
It was reverted because `Verify` with arguments was not well supported
by the client software. Thanks to recent `System.Runtime.CurrentSigners`
call from neo-go v0.104.0 we can take the best of both worlds.

Verify with argument still looks better (less overhead), but this
implementation should work too. Sadly, `overloads` are not of much use
here because verification routines take the _first_ method from the
manifest, albeit with arbitrary number of arguments.

This reverts commit a0b73150c6 with some
changes on-top.

Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-12-15 14:34:26 +03:00
4dcb575caa [#56] Add multi-level domain name support
Signed-off-by: Ekaterina Lebedeva <ekaterina.lebedeva@yadro.com>
2023-12-13 15:22:12 +03:00
897f538a3c [#48] frostfsid: add GetSubjectByName method
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-12-11 09:36:47 +00:00
3fb511ac15 [#48] frostfsid: Support empty namespaces
Require ns to create subject.
Since we don't allow move subject from one ns to another -
drop add/remove subject to/from namespace

Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-12-11 09:36:47 +00:00
a0b73150c6 Revert "[#53] proxy: Allow using proxy by trusted accounts"
This reverts commit bc3186575f.
2023-12-08 11:10:00 +03:00
bc3186575f [#53] proxy: Allow using proxy by trusted accounts
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-12-07 10:22:47 +03:00
d7cb550a5e [#53] common: Use interop.Hash160 in address producing functions
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-12-06 19:47:43 +03:00
91b36a7eb3 [#53] go.mod: Update neo-go to the laster master
Pickup new neotest features.

Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-12-06 19:47:43 +03:00
94cd3dca83 [#53] go.mod: Resolve ambiguous import
There was a problem with `go mod tidy`.

Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-12-06 19:47:15 +03:00
61 changed files with 3498 additions and 1621 deletions

View file

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View file

@ -0,0 +1,22 @@
name: Code generation
on: [pull_request]
jobs:
wrappers:
name: Generate wrappers
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: '1.23'
- name: Generate wrappers
run: make generate-wrappers
# The command seems to be non-deterministic.
# However, with >20 runs I haven't been able to reproduce the issue.
# This `git diff` is here to print diff in case we catch the behaviour again.
- name: Print diff
run: git diff HEAD
- name: Check that nothing has changed
run: git diff-index --exit-code HEAD

View file

@ -13,9 +13,9 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: '1.20'
go-version: '1.23'
- name: Run commit format checker
uses: https://git.frostfs.info/TrueCloudLab/dco-go@v2
uses: https://git.frostfs.info/TrueCloudLab/dco-go@v3
with:
from: 'origin/${{ github.event.pull_request.base.ref }}'

View file

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

1
.github/CODEOWNERS vendored
View file

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

View file

@ -1,6 +1,7 @@
# Changelog
Changelog for FrostFS Contract
## [Unreleased]
### Added
@ -8,7 +9,32 @@ Changelog for FrostFS Contract
### Removed
### Updated
### Fixed
### Updating from v0.18.0
## [0.21.0]
### Added
- Mention domain name in error messages in the nns contract (#92)
- Emit DeleteRecord event on record deletion in the nns contract (#114)
### Changed
- Allow to register TLD automatically (#114)
- Use frostfsid claims as a permission to create TLD (#115)
- Ensure subject keys are unique (#118, #129)
### Fixed
- Terminate session in `ReadIteratorItems()` (#85)
- Declare `nns.getAllRecords` as safe (#131)
## [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

5
CODEOWNERS Normal file
View file

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

View file

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

View file

@ -1 +1 @@
v0.18.0
v0.21.1

View file

@ -25,20 +25,20 @@ func AlphabetNodes() []interop.PublicKey {
}
// AlphabetAddress returns multi address of alphabet public keys.
func AlphabetAddress() []byte {
func AlphabetAddress() interop.Hash160 {
alphabet := neo.GetCommittee()
return Multiaddress(alphabet, false)
}
// CommitteeAddress returns multi address of committee.
func CommitteeAddress() []byte {
func CommitteeAddress() interop.Hash160 {
committee := neo.GetCommittee()
return Multiaddress(committee, true)
}
// Multiaddress returns default multisignature account address for N keys.
// If committee set to true, it is `M = N/2+1` committee account.
func Multiaddress(n []interop.PublicKey, committee bool) []byte {
func Multiaddress(n []interop.PublicKey, committee bool) interop.Hash160 {
threshold := len(n)*2/3 + 1
if committee {
threshold = len(n)/2 + 1

View file

@ -4,15 +4,15 @@ import "github.com/nspcc-dev/neo-go/pkg/interop/native/std"
const (
major = 0
minor = 18
patch = 0
minor = 21
patch = 1
// Versions from which an update should be performed.
// These should be used in a group (so prevMinor can be equal to minor if there are
// any migration routines.
prevMajor = 0
prevMinor = 16
prevPatch = 0
prevMinor = 19
prevPatch = 4
Version = major*1_000_000 + minor*1_000 + patch
@ -32,7 +32,7 @@ func CheckVersion(from int) {
if from < PrevVersion {
panic(ErrVersionMismatch + ": expected >=" + std.Itoa(PrevVersion, 10))
}
if from == Version {
if Version <= from {
panic(ErrAlreadyUpdated + ": " + std.Itoa(Version, 10))
}
}

View file

@ -11,6 +11,7 @@ import (
// Invoker is a subset of methods provided by struct invoker.Invoker. The subset contains only those
// methods that are used by ActorWrapper and clients of the contracts.
type Invoker interface {
Run([]byte) (*result.Invoke, error)
Call(contract util.Uint160, method string, params ...any) (*result.Invoke, error)
TraverseIterator(sessionID uuid.UUID, iterator *result.Iterator, num int) ([]stackitem.Item, error)
TerminateSession(sessionID uuid.UUID) error

View file

@ -3,7 +3,9 @@ package commonclient
import (
"fmt"
"github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
@ -14,13 +16,25 @@ func ReadIteratorItems(inv Invoker, batchSize int, contract util.Uint160, method
panic("batch size must be positive")
}
sessionID, iter, err := unwrap.SessionIterator(inv.Call(contract, method, params...))
script, err := smartcontract.CreateCallAndPrefetchIteratorScript(contract, method, batchSize, params...)
if err != nil {
return nil, fmt.Errorf("couldn't create unwrap script: %w", err)
}
arr, sessionID, iter, err := unwrap.ArrayAndSessionIterator(inv.Run(script))
if err != nil {
return nil, fmt.Errorf("unwrap session iterator: %w", err)
}
if (sessionID == uuid.UUID{}) {
return arr, nil
}
defer func() {
_ = inv.TerminateSession(sessionID)
}()
var shouldStop bool
res := make([]stackitem.Item, 0, len(iter.Values))
res := arr
for !shouldStop {
items, err := inv.TraverseIterator(sessionID, &iter, batchSize)
if err != nil {

84
commonclient/waiter.go Normal file
View file

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

135
commonclient/waiter_test.go Normal file
View file

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

View file

@ -18,6 +18,7 @@ permissions:
- "register"
- "transferX"
- "update"
- "setAdmin"
events:
- name: PutSuccess
@ -29,7 +30,7 @@ events:
- name: DeleteSuccess
parameters:
- name: containerID
type: ByteArray
type: Hash256
- name: SetEACLSuccess
parameters:
- name: containerID

View file

@ -94,26 +94,6 @@ func _deploy(data any, isUpdate bool) {
if isUpdate {
args := data.([]any)
common.CheckVersion(args[len(args)-1].(int))
it := storage.Find(ctx, []byte{}, storage.None)
for iterator.Next(it) {
item := iterator.Value(it).(struct {
key []byte
value []byte
})
// Migrate container.
if len(item.key) == containerIDSize {
storage.Delete(ctx, item.key)
storage.Put(ctx, append([]byte{containerKeyPrefix}, item.key...), item.value)
}
// Migrate owner-cid map.
if len(item.key) == 25 /* owner id size */ +containerIDSize {
storage.Delete(ctx, item.key)
storage.Put(ctx, append([]byte{ownerKeyPrefix}, item.key...), item.value)
}
}
return
}
@ -299,7 +279,7 @@ func checkNiceNameAvailable(nnsContractAddr interop.Hash160, domain string) bool
// API.
//
// If the container doesn't exist, it panics with NotFoundError.
func Delete(containerID []byte, signature interop.Signature, publicKey interop.PublicKey, token []byte) {
func Delete(containerID interop.Hash256, signature interop.Signature, publicKey interop.PublicKey, token []byte) {
ctx := storage.GetContext()
ownerID := getOwnerByID(ctx, containerID)
@ -585,6 +565,21 @@ func NewEpoch(epochNum int) {
cleanupContainers(ctx, epochNum)
}
// SetAdmin sets admin for root container domain.
func SetAdmin(admin interop.Hash160) {
ctx := storage.GetReadOnlyContext()
if !runtime.CheckWitness(common.CommitteeAddress()) {
panic("only committee can set admin")
}
addrNNS := storage.Get(ctx, nnsContractKey).(interop.Hash160)
rootContainerDomain := storage.Get(ctx, nnsRootKey).(string)
contract.Call(addrNNS, "setAdmin", contract.All,
rootContainerDomain, admin)
}
// StartContainerEstimation method produces StartEstimation notification.
// It can be invoked only by Alphabet nodes of the Inner Ring.
func StartContainerEstimation(epoch int) {

View file

@ -0,0 +1,160 @@
# Globally unique domain zone
**Make sure you understand the [basic concepts](../nns/README.md) of `NNS`.**
`Globally Unique Domains Zone` (`GUDZ`) is an extension of `NNS` that ensures unique names across multiple domain zones. When this option is enabled, all newly created domains will automatically receive a corresponding alias in the designated global zone. Deleting a domain will also remove its alias from the global zone.
It's important to note that this feature is not retroactive: domains created before this option is enabled will not receive a global alias. Likewise, if the option is later disabled, domains that already have a `GUDZ` alias will retain their records. To fully disable `GUDZ`, all domains must be recreated with the option turned off.
To enable `GUDZ`, add a `cnametgt=$(global domain)` `TXT` record that specifies the global zone.
**Example:**
Domains:
- `poland`
- `sweden`
- `animals.org`
It is necessary to associate the domain zones `.poland` and `.sweden` into the global zone `.animals`.
![](img/GUDZ.png)
Create domains:
```
frostfs-adm morph nns register --name="poland" --email="email@email.email"
frostfs-adm morph nns register --name="sweden" --email="email@email.email"
frostfs-adm morph nns register --name="org" --email="email@email.email"
frostfs-adm morph nns register --name="animals.org" --email="email@email.email"
```
Add the `cnametgt` records:
```
frostfs-adm morph nns add-record --name="poland" --data="cnametgt=animals.org" --type="txt"
frostfs-adm morph nns add-record --name="sweden" --data="cnametgt=animals.org" --type="txt"
```
Create a domain with mapping to the global zone:
```
frostfs-adm morph nns register --name="bober.poland" --email="email@email.email"
```
Add any `TXT` record
```
frostfs-adm morph nns add-record --name="bober.poland" --data="CID" --type="txt"
```
Verify that the created domain has alias in the global zone
```
frostfs-adm morph nns tokens -v
balance.frostfs
animals.org
group.frostfs
container
org
container.frostfs
proxy.frostfs
policy.frostfs
alphabet0.frostfs
sweden
frostfsid.frostfs
bober.animals.org (CNAME: bober.poland)
netmap.frostfs
frostfs
poland
bober.poland
```
Create of a conflicting domain
```
frostfs-adm morph nns register --name="bober.sweden" --email="email@email.email"
unable to register domain: script failed (FAULT state) due to an error: at instruction 1263 (THROW): unhandled exception: "global domain is already taken: bober.animals.org. Domain: bober.poland
```
**Disable GUDZ**
Delete `cnametgt` records
```
frostfs-adm morph nns delete-records --type=txt --name=poland
```
Create `hamster.poland` and `hamster.sweden`
```
frostfs-adm morph nns register --name="hamster.poland" --email="email@email.email"
frostfs-adm morph nns register --name="hamster.sweden" --email="email@email.email"
```
`hamster.poland` and `hamster.sweden` does not have alias
```
frostfs-adm morph nns tokens -v
balance.frostfs
animals.org
group.frostfs
container
org
container.frostfs
proxy.frostfs
policy.frostfs
alphabet0.frostfs
sweden
frostfsid.frostfs
bober.animals.org (CNAME: bober.poland)
netmap.frostfs
frostfs
poland
bober.poland
hamster.poland
```
Delete global alias of `bober.poland`
```
frostfs-adm morph nns delete-records --name="bober.poland" --type="txt"
```
```
frostfs-adm morph nns tokens -v
balance.frostfs
animals.org
group.frostfs
container
org
container.frostfs
proxy.frostfs
policy.frostfs
alphabet0.frostfs
sweden
frostfsid.frostfs
netmap.frostfs
frostfs
poland
bober.poland
hamster.poland
```
Delete `bober.poland`
```
frostfs-adm morph nns delete --name="bober.poland"
```
```
frostfs-adm morph nns tokens -v
balance.frostfs
animals.org
group.frostfs
container
org
container.frostfs
proxy.frostfs
policy.frostfs
alphabet0.frostfs
sweden
frostfsid.frostfs
netmap.frostfs
frostfs
poland
hamster.poland
```

139
docs/img/GUDZ.drawio Normal file
View file

@ -0,0 +1,139 @@
<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:129.0) Gecko/20100101 Firefox/129.0" version="24.7.14">
<diagram name="Page-1" id="N1NjK5oQ_tQiBXsDL3WT">
<mxGraphModel dx="989" dy="917" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="0" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="l_qLPBR5nCWAv8Wbn5Wl-1" value=".ns" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="130" y="160" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="l_qLPBR5nCWAv8Wbn5Wl-2" value=".org" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="440" y="160" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="l_qLPBR5nCWAv8Wbn5Wl-3" value=".sweden" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="250" y="280" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="l_qLPBR5nCWAv8Wbn5Wl-4" value=".poland" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="10" y="280" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="l_qLPBR5nCWAv8Wbn5Wl-5" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="l_qLPBR5nCWAv8Wbn5Wl-1" target="l_qLPBR5nCWAv8Wbn5Wl-4">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="260" y="480" as="sourcePoint" />
<mxPoint x="310" y="430" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="l_qLPBR5nCWAv8Wbn5Wl-6" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="l_qLPBR5nCWAv8Wbn5Wl-1" target="l_qLPBR5nCWAv8Wbn5Wl-3">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="200" y="230" as="sourcePoint" />
<mxPoint x="80" y="290" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="l_qLPBR5nCWAv8Wbn5Wl-7" value="bober" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="10" y="400" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="l_qLPBR5nCWAv8Wbn5Wl-8" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="l_qLPBR5nCWAv8Wbn5Wl-4" target="l_qLPBR5nCWAv8Wbn5Wl-7">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="190" y="450" as="sourcePoint" />
<mxPoint x="70" y="390" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="l_qLPBR5nCWAv8Wbn5Wl-9" value="bober" style="rounded=0;whiteSpace=wrap;html=1;dashed=1;strokeColor=#FF6666;" vertex="1" parent="1">
<mxGeometry x="250" y="400" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="l_qLPBR5nCWAv8Wbn5Wl-10" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;dashed=1;fillColor=#f8cecc;strokeColor=#FF6666;" edge="1" parent="1" target="l_qLPBR5nCWAv8Wbn5Wl-9">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="310" y="340" as="sourcePoint" />
<mxPoint x="310" y="390" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="l_qLPBR5nCWAv8Wbn5Wl-11" value="bober" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="440" y="400" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="l_qLPBR5nCWAv8Wbn5Wl-12" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" target="l_qLPBR5nCWAv8Wbn5Wl-11">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="500" y="340" as="sourcePoint" />
<mxPoint x="500" y="390" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="l_qLPBR5nCWAv8Wbn5Wl-13" value="&lt;font style=&quot;font-size: 5px;&quot;&gt;TXT cnametgt=animals.org&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="1">
<mxGeometry x="30" y="310" width="80" height="30" as="geometry" />
</mxCell>
<mxCell id="l_qLPBR5nCWAv8Wbn5Wl-14" value=".&lt;span lang=&quot;en&quot; class=&quot;Y2IQFc&quot;&gt;animals&lt;/span&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="440" y="280" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="l_qLPBR5nCWAv8Wbn5Wl-15" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="499.76" y="220" as="sourcePoint" />
<mxPoint x="499.76" y="280" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="l_qLPBR5nCWAv8Wbn5Wl-16" value="&lt;font style=&quot;font-size: 5px;&quot;&gt;TXT cnametgt=&lt;/font&gt;&lt;font style=&quot;font-size: 5px;&quot;&gt;animals.org&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="1">
<mxGeometry x="270" y="310" width="80" height="30" as="geometry" />
</mxCell>
<mxCell id="l_qLPBR5nCWAv8Wbn5Wl-17" value="" style="shape=umlDestroy;whiteSpace=wrap;html=1;strokeWidth=3;targetShapes=umlLifeline;strokeColor=#FF6666;" vertex="1" parent="1">
<mxGeometry x="292.5" y="350" width="35" height="40" as="geometry" />
</mxCell>
<mxCell id="l_qLPBR5nCWAv8Wbn5Wl-18" value="&lt;span style=&quot;white-space: pre-wrap;&quot; data-src-align=&quot;0:10&quot; class=&quot;EzKURWReUAB5oZgtQNkl&quot;&gt;[ global&lt;/span&gt;&lt;span style=&quot;white-space: pre-wrap;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;white-space: pre-wrap;&quot; data-src-align=&quot;11:8&quot; class=&quot;EzKURWReUAB5oZgtQNkl&quot;&gt;domain&lt;/span&gt;&lt;span style=&quot;white-space: pre-wrap;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;white-space: pre-wrap;&quot; data-src-align=&quot;20:4&quot; class=&quot;EzKURWReUAB5oZgtQNkl&quot;&gt;zone ]&lt;/span&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="425" y="100" width="150" height="30" as="geometry" />
</mxCell>
<mxCell id="l_qLPBR5nCWAv8Wbn5Wl-19" value="&lt;font style=&quot;font-size: 5px;&quot;&gt;CNAME bober&lt;/font&gt;&lt;font style=&quot;font-size: 5px;&quot;&gt;.&lt;/font&gt;&lt;font style=&quot;font-size: 5px;&quot;&gt;poland&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="1">
<mxGeometry x="465" y="430" width="70" height="30" as="geometry" />
</mxCell>
<mxCell id="Z9nLDmrU-mAa8Hye4rHX-1" value=".ns" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="130" y="-240" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Z9nLDmrU-mAa8Hye4rHX-2" value=".org" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="440" y="-240" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Z9nLDmrU-mAa8Hye4rHX-3" value=".sweden" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="250" y="-120" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Z9nLDmrU-mAa8Hye4rHX-4" value=".poland" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="10" y="-120" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Z9nLDmrU-mAa8Hye4rHX-5" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="Z9nLDmrU-mAa8Hye4rHX-1" target="Z9nLDmrU-mAa8Hye4rHX-4">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="260" y="80" as="sourcePoint" />
<mxPoint x="310" y="30" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="Z9nLDmrU-mAa8Hye4rHX-6" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="Z9nLDmrU-mAa8Hye4rHX-1" target="Z9nLDmrU-mAa8Hye4rHX-3">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="200" y="-170" as="sourcePoint" />
<mxPoint x="80" y="-110" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="Z9nLDmrU-mAa8Hye4rHX-7" value="bober" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="10" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Z9nLDmrU-mAa8Hye4rHX-8" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="Z9nLDmrU-mAa8Hye4rHX-4" target="Z9nLDmrU-mAa8Hye4rHX-7">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="190" y="50" as="sourcePoint" />
<mxPoint x="70" y="-10" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="Z9nLDmrU-mAa8Hye4rHX-9" value=".&lt;span lang=&quot;en&quot; class=&quot;Y2IQFc&quot;&gt;animals&lt;/span&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="440" y="-120" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Z9nLDmrU-mAa8Hye4rHX-10" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="499.76" y="-180" as="sourcePoint" />
<mxPoint x="499.76" y="-120" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="Z9nLDmrU-mAa8Hye4rHX-11" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="309.71000000000004" y="-60" as="sourcePoint" />
<mxPoint x="309.71000000000004" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="Z9nLDmrU-mAa8Hye4rHX-12" value="bober" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="250" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Z9nLDmrU-mAa8Hye4rHX-13" value="&lt;span style=&quot;white-space: pre-wrap;&quot; data-src-align=&quot;0:10&quot; class=&quot;EzKURWReUAB5oZgtQNkl&quot;&gt;[ without global&lt;/span&gt;&lt;span style=&quot;white-space: pre-wrap;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;white-space: pre-wrap;&quot; data-src-align=&quot;11:8&quot; class=&quot;EzKURWReUAB5oZgtQNkl&quot;&gt;domain&lt;/span&gt;&lt;span style=&quot;white-space: pre-wrap;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;white-space: pre-wrap;&quot; data-src-align=&quot;20:4&quot; class=&quot;EzKURWReUAB5oZgtQNkl&quot;&gt;zone ]&lt;/span&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="390" y="-300" width="180" height="30" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

BIN
docs/img/GUDZ.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View file

@ -1,6 +1,5 @@
name: "FrostFS"
safemethods:
- "alphabetAddress"
- "config"
- "innerRingCandidates"
- "listConfig"
@ -48,12 +47,6 @@ events:
type: ByteArray
- name: keys
type: Array
- name: AlphabetUpdate
parameters:
- name: id
type: ByteArray
- name: alphabet
type: Array
- name: SetConfig
parameters:
- name: id

View file

@ -9,7 +9,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/interop/native/ledger"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
"github.com/nspcc-dev/neo-go/pkg/interop/native/roles"
"github.com/nspcc-dev/neo-go/pkg/interop/native/std"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
)
@ -26,7 +25,6 @@ const (
CandidateFeeConfigKey = "InnerRingCandidateFee"
withdrawFeeConfigKey = "WithdrawFee"
alphabetKey = "alphabet"
candidatesKey = "candidates"
processingContractKey = "processingScriptHash"
@ -71,9 +69,6 @@ func _deploy(data any, isUpdate bool) {
}
}
// initialize all storage slices
common.SetSerialized(ctx, alphabetKey, args.keys)
storage.Put(ctx, processingContractKey, args.addrProc)
ln := len(args.config)
@ -106,13 +101,6 @@ func Update(script []byte, manifest []byte, data any) {
runtime.Log("frostfs contract updated")
}
// AlphabetAddress returns 2\3n+1 multisignature address of alphabet nodes.
// It is used in sidechain notary disabled environment.
func AlphabetAddress() interop.Hash160 {
ctx := storage.GetReadOnlyContext()
return multiaddress(getAlphabetNodes(ctx))
}
// InnerRingCandidates returns an array of structures that contain an Inner Ring
// candidate node key.
func InnerRingCandidates() []common.IRNode {
@ -137,8 +125,7 @@ func InnerRingCandidateRemove(key interop.PublicKey) {
keyOwner := runtime.CheckWitness(key)
if !keyOwner {
multiaddr := AlphabetAddress()
if !runtime.CheckWitness(multiaddr) {
if !runtime.CheckWitness(common.AlphabetAddress()) {
panic("this method must be invoked by candidate or alphabet")
}
}
@ -358,16 +345,6 @@ func Version() int {
return common.Version
}
// getAlphabetNodes returns a deserialized slice of nodes from storage.
func getAlphabetNodes(ctx storage.Context) []interop.PublicKey {
data := storage.Get(ctx, alphabetKey)
if data != nil {
return std.Deserialize(data.([]byte)).([]interop.PublicKey)
}
return []interop.PublicKey{}
}
// getConfig returns the installed frostfs configuration value or nil if it is not set.
func getConfig(ctx storage.Context, key any) interface{} {
postfix := key.([]byte)

View file

@ -1,16 +1,20 @@
package client
import (
"errors"
"fmt"
"git.frostfs.info/TrueCloudLab/frostfs-contract/commonclient"
"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/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/notary"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/nspcc-dev/neo-go/pkg/wallet"
@ -19,12 +23,13 @@ import (
type (
Client struct {
act *actor.Actor
acc *wallet.Account
waiter waiter.Waiter
contract util.Uint160
}
Options struct {
// todo add proxy params
ProxyContract util.Uint160
Waiter commonclient.WaiterOptions
}
)
@ -91,28 +96,29 @@ const (
createSubjectMethod = "createSubject"
getSubjectMethod = "getSubject"
getSubjectExtendedMethod = "getSubjectExtended"
getSubjectKVMethod = "getSubjectKV"
listSubjectsMethod = "listSubjects"
addSubjectKeyMethod = "addSubjectKey"
removeSubjectKeyMethod = "removeSubjectKey"
getSubjectByKeyMethod = "getSubjectByKey"
getSubjectByNameMethod = "getSubjectByName"
getSubjectKeyByNameMethod = "getSubjectKeyByName"
setSubjectKVMethod = "setSubjectKV"
setSubjectNameMethod = "setSubjectName"
deleteSubjectKVMethod = "deleteSubjectKV"
deleteSubjectMethod = "deleteSubject"
createNamespaceMethod = "createNamespace"
getNamespaceMethod = "getNamespace"
getNamespaceExtendedMethod = "getNamespaceExtended"
listNamespacesMethod = "listNamespaces"
addSubjectToNamespaceMethod = "addSubjectToNamespace"
removeSubjectFromNamespaceMethod = "removeSubjectFromNamespace"
listNamespaceSubjectsMethod = "listNamespaceSubjects"
createNamespaceMethod = "createNamespace"
getNamespaceMethod = "getNamespace"
getNamespaceExtendedMethod = "getNamespaceExtended"
listNamespacesMethod = "listNamespaces"
listNamespaceSubjectsMethod = "listNamespaceSubjects"
createGroupMethod = "createGroup"
getGroupMethod = "getGroup"
getGroupExtendedMethod = "getGroupExtended"
getGroupIDByNameMethod = "getGroupIDByName"
getGroupByNameMethod = "getGroupByName"
setGroupNameMethod = "setGroupName"
setGroupKVMethod = "setGroupKV"
deleteGroupKVMethod = "deleteGroupKV"
@ -123,20 +129,50 @@ const (
deleteGroupMethod = "deleteGroup"
)
// New creates a new Client. Options can be nil.
func New(ra actor.RPCActor, acc *wallet.Account, contract util.Uint160, _ *Options) (*Client, error) {
act, err := actor.NewSimple(ra, acc)
// New creates a new Client. Options can be empty.
func New(ra actor.RPCActor, acc *wallet.Account, contract util.Uint160, opt Options) (*Client, error) {
signers := []actor.SignerAccount{{
Signer: transaction.Signer{
Account: acc.Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: acc,
}}
if !opt.ProxyContract.Equals(util.Uint160{}) {
signers = append([]actor.SignerAccount{{
Signer: transaction.Signer{
Account: opt.ProxyContract,
Scopes: transaction.CustomContracts,
AllowedContracts: []util.Uint160{contract},
},
Account: notary.FakeContractAccount(opt.ProxyContract),
}}, signers...)
}
act, err := actor.New(ra, signers)
if err != nil {
return nil, fmt.Errorf("init actor: %w", err)
}
wtr := commonclient.NewWaiter(act, opt.Waiter)
return &Client{
act: act,
acc: acc,
waiter: wtr,
contract: contract,
}, nil
}
// NewSimple creates a new Client using existing actor.Actor and default waiter options.
func NewSimple(act *actor.Actor, contract util.Uint160) *Client {
wtr := commonclient.NewWaiter(act, commonclient.WaiterOptions{})
return &Client{
act: act,
waiter: wtr,
contract: contract,
}
}
// StartTx inits transaction.
func (c Client) StartTx() *commonclient.Transaction {
return commonclient.NewTransaction(c.contract)
@ -199,16 +235,16 @@ func (c Client) GetAdmin() (util.Uint160, bool, error) {
return u, true, err
}
// CreateSubject creates new subject using public key.
// CreateSubject creates new subject using public key and namespace.
// Must be invoked by contract owner.
func (c Client) CreateSubject(key *keys.PublicKey) (tx util.Uint256, vub uint32, err error) {
method, args := c.CreateSubjectCall(key)
func (c Client) CreateSubject(ns string, key *keys.PublicKey) (tx util.Uint256, vub uint32, err error) {
method, args := c.CreateSubjectCall(ns, key)
return c.act.SendCall(c.contract, method, args...)
}
// CreateSubjectCall provides args for CreateSubject to use in commonclient.Transaction.
func (c Client) CreateSubjectCall(key *keys.PublicKey) (method string, args []any) {
return createSubjectMethod, []any{key.Bytes()}
func (c Client) CreateSubjectCall(ns string, key *keys.PublicKey) (method string, args []any) {
return createSubjectMethod, []any{ns, key.Bytes()}
}
// GetSubject gets subject by address.
@ -218,7 +254,7 @@ func (c Client) GetSubject(addr util.Uint160) (*Subject, error) {
return nil, err
}
return parseSubject(items)
return ParseSubject(items)
}
// GetSubjectExtended gets extended subject by address.
@ -228,12 +264,50 @@ func (c Client) GetSubjectExtended(addr util.Uint160) (*SubjectExtended, error)
return nil, err
}
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.
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))
}
// ListFullSubjects gets list of subjects.
func (c Client) ListFullSubjects(hashes []util.Uint160) ([]*Subject, error) {
w := io.NewBufBinWriter()
for _, hash := range hashes {
emit.AppCall(w.BinWriter, c.contract, getSubjectMethod, callflag.All, hash)
}
invoker, err := c.act.Run(w.Bytes())
if err != nil {
return nil, err
}
if invoker.State != vmstate.Halt.String() {
return nil, fmt.Errorf("invocation failed: %s", invoker.FaultException)
}
subjects := make([]*Subject, 0, len(invoker.Stack))
for _, item := range invoker.Stack {
arr, ok := item.Value().([]stackitem.Item)
if !ok {
return nil, fmt.Errorf("invalid subject")
}
subject, err := ParseSubject(arr)
if err != nil {
return nil, err
}
subjects = append(subjects, subject)
}
return subjects, nil
}
// AddSubjectKey adds extra public key to subject.
@ -304,7 +378,17 @@ func (c Client) GetSubjectByKey(key *keys.PublicKey) (*Subject, error) {
return nil, err
}
return parseSubject(items)
return ParseSubject(items)
}
// GetSubjectByName gets subject by its name (namespace scope).
func (c Client) GetSubjectByName(namespace, subjectName string) (*Subject, error) {
items, err := unwrap.Array(c.act.Call(c.contract, getSubjectByNameMethod, namespace, subjectName))
if err != nil {
return nil, err
}
return ParseSubject(items)
}
// GetSubjectKeyByName gets subject public key by its name (namespace scope).
@ -343,7 +427,7 @@ func (c Client) GetNamespace(namespace string) (*Namespace, error) {
return nil, err
}
return parseNamespace(items)
return ParseNamespace(items)
}
// GetNamespaceExtended gets extended namespace.
@ -353,7 +437,7 @@ func (c Client) GetNamespaceExtended(namespace string) (*NamespaceExtended, erro
return nil, err
}
return parseNamespaceExtended(items)
return ParseNamespaceExtended(items)
}
// ListNamespaces gets all namespaces.
@ -363,36 +447,12 @@ func (c Client) ListNamespaces() ([]*Namespace, error) {
return nil, err
}
return parseNamespaces(items)
}
// AddSubjectToNamespace adds a subject to namespace.
// Must be invoked by contract owner.
func (c Client) AddSubjectToNamespace(addr util.Uint160, namespace string) (tx util.Uint256, vub uint32, err error) {
method, args := c.AddSubjectToNamespaceCall(addr, namespace)
return c.act.SendCall(c.contract, method, args...)
}
// AddSubjectToNamespaceCall provides args for AddSubjectToNamespace to use in commonclient.Transaction.
func (c Client) AddSubjectToNamespaceCall(addr util.Uint160, namespace string) (method string, args []any) {
return addSubjectToNamespaceMethod, []any{addr, namespace}
}
// RemoveSubjectFromNamespace removes a subject from namespace.
// Must be invoked by contract owner.
func (c Client) RemoveSubjectFromNamespace(addr util.Uint160) (tx util.Uint256, vub uint32, err error) {
method, args := c.RemoveSubjectFromNamespaceCall(addr)
return c.act.SendCall(c.contract, method, args...)
}
// RemoveSubjectFromNamespaceCall provides args for RemoveSubjectFromNamespace to use in commonclient.Transaction.
func (c Client) RemoveSubjectFromNamespaceCall(addr util.Uint160) (method string, args []any) {
return removeSubjectFromNamespaceMethod, []any{addr}
return ParseNamespaces(items)
}
// ListNamespaceSubjects gets all subjects from namespace.
func (c Client) ListNamespaceSubjects(namespace string) ([]util.Uint160, error) {
return unwrapArrayOfUint160(commonclient.ReadIteratorItems(c.act, iteratorBatchSize, c.contract, listNamespaceSubjectsMethod, namespace))
return UnwrapArrayOfUint160(commonclient.ReadIteratorItems(c.act, iteratorBatchSize, c.contract, listNamespaceSubjectsMethod, namespace))
}
// CreateGroup creates a new group in specific namespace.
@ -414,7 +474,7 @@ func (c Client) GetGroup(namespace string, groupID int64) (*Group, error) {
return nil, err
}
return parseGroup(items)
return ParseGroup(items)
}
// GetGroupExtended gets extended group.
@ -424,7 +484,7 @@ func (c Client) GetGroupExtended(namespace string, groupID int64) (*GroupExtende
return nil, err
}
return parseGroupExtended(items)
return ParseGroupExtended(items)
}
// SetGroupName updates subject name.
@ -469,6 +529,16 @@ func (c Client) GetGroupIDByName(namespace, groupName string) (int64, error) {
return unwrap.Int64(c.act.Call(c.contract, getGroupIDByNameMethod, namespace, groupName))
}
// GetGroupByName gets group by its name (namespace scope).
func (c Client) GetGroupByName(namespace, groupName string) (*Group, error) {
items, err := unwrap.Array(c.act.Call(c.contract, getGroupByNameMethod, namespace, groupName))
if err != nil {
return nil, err
}
return ParseGroup(items)
}
// ListGroups gets all groups in specific namespace.
func (c Client) ListGroups(namespace string) ([]*Group, error) {
items, err := commonclient.ReadIteratorItems(c.act, iteratorBatchSize, c.contract, listGroupsMethod, namespace)
@ -476,7 +546,7 @@ func (c Client) ListGroups(namespace string) ([]*Group, error) {
return nil, err
}
return parseGroups(items)
return ParseGroups(items)
}
// AddSubjectToGroup adds a new subject to group.
@ -505,7 +575,7 @@ func (c Client) RemoveSubjectFromGroupCall(addr util.Uint160, groupID int64) (me
// ListGroupSubjects gets all subjects in specific group.
func (c Client) ListGroupSubjects(namespace string, groupID int64) ([]util.Uint160, error) {
return unwrapArrayOfUint160(commonclient.ReadIteratorItems(c.act, iteratorBatchSize, c.contract, listGroupSubjectsMethod, namespace, groupID))
return UnwrapArrayOfUint160(commonclient.ReadIteratorItems(c.act, iteratorBatchSize, c.contract, listGroupSubjectsMethod, namespace, groupID))
}
// DeleteGroup deletes group.
@ -543,9 +613,14 @@ func (c Client) ListNonEmptyNamespaces() ([]string, error) {
return res, nil
}
// Wait invokes underlying wait method on actor.Actor.
// Wait waits until the specified transaction is accepted to the chain.
func (c Client) Wait(tx util.Uint256, vub uint32, err error) (*state.AppExecResult, error) {
return c.act.Wait(tx, vub, err)
return c.Waiter().Wait(tx, vub, err)
}
// Waiter returns underlying waiter.Waiter.
func (c Client) Waiter() waiter.Waiter {
return c.waiter
}
// ParseGroupID fetch groupID from stack after creating group method invocation.
@ -579,302 +654,3 @@ func (c Client) ListNonEmptyGroups(namespace string) ([]string, error) {
return res, nil
}
func unwrapArrayOfUint160(items []stackitem.Item, err error) ([]util.Uint160, error) {
if err != nil {
return nil, err
}
return unwrap.ArrayOfUint160(makeValidRes(stackitem.NewArray(items)))
}
func makeValidRes(item stackitem.Item) (*result.Invoke, error) {
return &result.Invoke{
Stack: []stackitem.Item{item},
State: vmstate.Halt.String(),
}, nil
}
func makeResFromAppExec(res *state.AppExecResult) (*result.Invoke, error) {
return &result.Invoke{
Stack: res.Stack,
State: res.VMState.String(),
}, nil
}
func parseSubject(structArr []stackitem.Item) (*Subject, error) {
if len(structArr) < 5 {
return nil, errors.New("invalid response subject struct")
}
var (
err error
subj Subject
)
subj.PrimaryKey, err = unwrap.PublicKey(makeValidRes(structArr[0]))
if err != nil {
return nil, err
}
if !structArr[1].Equals(stackitem.Null{}) {
subj.AdditionalKeys, err = unwrap.ArrayOfPublicKeys(makeValidRes(structArr[1]))
if err != nil {
return nil, err
}
}
if !structArr[2].Equals(stackitem.Null{}) {
subj.Namespace, err = unwrap.UTF8String(makeValidRes(structArr[2]))
if err != nil {
return nil, err
}
}
if !structArr[2].Equals(stackitem.Null{}) {
subj.Name, err = unwrap.UTF8String(makeValidRes(structArr[3]))
if err != nil {
return nil, err
}
}
subj.KV, err = parseMap(structArr[4])
if err != nil {
return nil, err
}
return &subj, nil
}
func parseSubjectExtended(structArr []stackitem.Item) (*SubjectExtended, error) {
if len(structArr) < 6 {
return nil, errors.New("invalid response subject extended struct")
}
var (
err error
subj SubjectExtended
)
subj.PrimaryKey, err = unwrap.PublicKey(makeValidRes(structArr[0]))
if err != nil {
return nil, err
}
if !structArr[1].Equals(stackitem.Null{}) {
subj.AdditionalKeys, err = unwrap.ArrayOfPublicKeys(makeValidRes(structArr[1]))
if err != nil {
return nil, err
}
}
nsBytes, err := structArr[2].TryBytes()
if err != nil {
return nil, err
}
subj.Namespace = string(nsBytes)
nameBytes, err := structArr[3].TryBytes()
if err != nil {
return nil, err
}
subj.Name = string(nameBytes)
subj.KV, err = parseMap(structArr[4])
if err != nil {
return nil, err
}
if !structArr[5].Equals(stackitem.Null{}) {
groupItems, ok := structArr[5].Value().([]stackitem.Item)
if !ok {
return nil, fmt.Errorf("invalid groups field")
}
subj.Groups, err = parseGroups(groupItems)
if err != nil {
return nil, err
}
}
return &subj, nil
}
func parseMap(item stackitem.Item) (map[string]string, error) {
if item.Equals(stackitem.Null{}) {
return nil, nil
}
metaMap, err := unwrap.Map(makeValidRes(item))
if err != nil {
return nil, err
}
meta, ok := metaMap.Value().([]stackitem.MapElement)
if !ok {
return nil, errors.New("invalid map type")
}
res := make(map[string]string, len(meta))
for _, element := range meta {
key, err := element.Key.TryBytes()
if err != nil {
return nil, err
}
val, err := element.Value.TryBytes()
if err != nil {
return nil, err
}
res[string(key)] = string(val)
}
return res, nil
}
func parseNamespace(structArr []stackitem.Item) (*Namespace, error) {
if len(structArr) < 1 {
return nil, errors.New("invalid response namespace struct")
}
name, err := structArr[0].TryBytes()
if err != nil {
return nil, err
}
return &Namespace{Name: string(name)}, nil
}
func parseNamespaceExtended(structArr []stackitem.Item) (*NamespaceExtended, error) {
if len(structArr) < 3 {
return nil, errors.New("invalid response namespace extended struct")
}
name, err := structArr[0].TryBytes()
if err != nil {
return nil, err
}
groupCount, err := structArr[1].TryInteger()
if err != nil {
return nil, err
}
subjectCount, err := structArr[2].TryInteger()
if err != nil {
return nil, err
}
return &NamespaceExtended{
Name: string(name),
GroupsCount: groupCount.Int64(),
SubjectsCount: subjectCount.Int64(),
}, nil
}
func parseNamespaces(items []stackitem.Item) ([]*Namespace, error) {
var err error
res := make([]*Namespace, len(items))
for i := 0; i < len(items); i++ {
arr, ok := items[i].Value().([]stackitem.Item)
if !ok {
return nil, fmt.Errorf("invalid namespace type")
}
res[i], err = parseNamespace(arr)
if err != nil {
return nil, err
}
}
return res, nil
}
func parseGroup(structArr []stackitem.Item) (*Group, error) {
if len(structArr) < 4 {
return nil, errors.New("invalid response group struct")
}
groupID, err := structArr[0].TryInteger()
if err != nil {
return nil, err
}
name, err := structArr[1].TryBytes()
if err != nil {
return nil, err
}
namespace, err := structArr[2].TryBytes()
if err != nil {
return nil, err
}
kvs, err := parseMap(structArr[3])
if err != nil {
return nil, err
}
return &Group{
ID: groupID.Int64(),
Name: string(name),
Namespace: string(namespace),
KV: kvs,
}, nil
}
func parseGroupExtended(structArr []stackitem.Item) (*GroupExtended, error) {
if len(structArr) < 5 {
return nil, errors.New("invalid response group extended struct")
}
groupID, err := structArr[0].TryInteger()
if err != nil {
return nil, err
}
name, err := structArr[1].TryBytes()
if err != nil {
return nil, err
}
namespace, err := structArr[2].TryBytes()
if err != nil {
return nil, err
}
kvs, err := parseMap(structArr[3])
if err != nil {
return nil, err
}
subjectCount, err := structArr[4].TryInteger()
if err != nil {
return nil, err
}
return &GroupExtended{
ID: groupID.Int64(),
Name: string(name),
Namespace: string(namespace),
KV: kvs,
SubjectsCount: subjectCount.Int64(),
}, nil
}
func parseGroups(items []stackitem.Item) ([]*Group, error) {
var err error
res := make([]*Group, len(items))
for i := 0; i < len(items); i++ {
arr, ok := items[i].Value().([]stackitem.Item)
if !ok {
return nil, fmt.Errorf("invalid group type")
}
res[i], err = parseGroup(arr)
if err != nil {
return nil, err
}
}
return res, nil
}

312
frostfsid/client/utils.go Normal file
View file

@ -0,0 +1,312 @@
package client
import (
"errors"
"fmt"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
"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/vmstate"
)
func UnwrapArrayOfUint160(items []stackitem.Item, err error) ([]util.Uint160, error) {
if err != nil {
return nil, err
}
return unwrap.ArrayOfUint160(makeValidRes(stackitem.NewArray(items)))
}
func ParseSubject(structArr []stackitem.Item) (*Subject, error) {
if len(structArr) < 5 {
return nil, errors.New("invalid response subject struct")
}
var (
err error
subj Subject
)
subj.PrimaryKey, err = unwrap.PublicKey(makeValidRes(structArr[0]))
if err != nil {
return nil, err
}
if !structArr[1].Equals(stackitem.Null{}) {
subj.AdditionalKeys, err = unwrap.ArrayOfPublicKeys(makeValidRes(structArr[1]))
if err != nil {
return nil, err
}
}
if !structArr[2].Equals(stackitem.Null{}) {
subj.Namespace, err = unwrap.UTF8String(makeValidRes(structArr[2]))
if err != nil {
return nil, err
}
}
if !structArr[2].Equals(stackitem.Null{}) {
subj.Name, err = unwrap.UTF8String(makeValidRes(structArr[3]))
if err != nil {
return nil, err
}
}
subj.KV, err = ParseMap(structArr[4])
if err != nil {
return nil, err
}
return &subj, nil
}
func ParseSubjectExtended(structArr []stackitem.Item) (*SubjectExtended, error) {
if len(structArr) < 6 {
return nil, errors.New("invalid response subject extended struct")
}
var (
err error
subj SubjectExtended
)
subj.PrimaryKey, err = unwrap.PublicKey(makeValidRes(structArr[0]))
if err != nil {
return nil, err
}
if !structArr[1].Equals(stackitem.Null{}) {
subj.AdditionalKeys, err = unwrap.ArrayOfPublicKeys(makeValidRes(structArr[1]))
if err != nil {
return nil, err
}
}
nsBytes, err := structArr[2].TryBytes()
if err != nil {
return nil, err
}
subj.Namespace = string(nsBytes)
nameBytes, err := structArr[3].TryBytes()
if err != nil {
return nil, err
}
subj.Name = string(nameBytes)
subj.KV, err = ParseMap(structArr[4])
if err != nil {
return nil, err
}
if !structArr[5].Equals(stackitem.Null{}) {
groupItems, ok := structArr[5].Value().([]stackitem.Item)
if !ok {
return nil, fmt.Errorf("invalid groups field")
}
subj.Groups, err = ParseGroups(groupItems)
if err != nil {
return nil, err
}
}
return &subj, nil
}
func ParseMap(item stackitem.Item) (map[string]string, error) {
if item.Equals(stackitem.Null{}) {
return nil, nil
}
metaMap, err := unwrap.Map(makeValidRes(item))
if err != nil {
return nil, err
}
meta, ok := metaMap.Value().([]stackitem.MapElement)
if !ok {
return nil, errors.New("invalid map type")
}
res := make(map[string]string, len(meta))
for _, element := range meta {
key, err := element.Key.TryBytes()
if err != nil {
return nil, err
}
val, err := element.Value.TryBytes()
if err != nil {
return nil, err
}
res[string(key)] = string(val)
}
return res, nil
}
func ParseNamespace(structArr []stackitem.Item) (*Namespace, error) {
if len(structArr) < 1 {
return nil, errors.New("invalid response namespace struct")
}
name, err := structArr[0].TryBytes()
if err != nil {
return nil, err
}
return &Namespace{Name: string(name)}, nil
}
func ParseNamespaceExtended(structArr []stackitem.Item) (*NamespaceExtended, error) {
if len(structArr) < 3 {
return nil, errors.New("invalid response namespace extended struct")
}
name, err := structArr[0].TryBytes()
if err != nil {
return nil, err
}
groupCount, err := structArr[1].TryInteger()
if err != nil {
return nil, err
}
subjectCount, err := structArr[2].TryInteger()
if err != nil {
return nil, err
}
return &NamespaceExtended{
Name: string(name),
GroupsCount: groupCount.Int64(),
SubjectsCount: subjectCount.Int64(),
}, nil
}
func ParseNamespaces(items []stackitem.Item) ([]*Namespace, error) {
var err error
res := make([]*Namespace, len(items))
for i := 0; i < len(items); i++ {
arr, ok := items[i].Value().([]stackitem.Item)
if !ok {
return nil, fmt.Errorf("invalid namespace type")
}
res[i], err = ParseNamespace(arr)
if err != nil {
return nil, err
}
}
return res, nil
}
func ParseGroup(structArr []stackitem.Item) (*Group, error) {
if len(structArr) < 4 {
return nil, errors.New("invalid response group struct")
}
groupID, err := structArr[0].TryInteger()
if err != nil {
return nil, err
}
name, err := structArr[1].TryBytes()
if err != nil {
return nil, err
}
namespace, err := structArr[2].TryBytes()
if err != nil {
return nil, err
}
kvs, err := ParseMap(structArr[3])
if err != nil {
return nil, err
}
return &Group{
ID: groupID.Int64(),
Name: string(name),
Namespace: string(namespace),
KV: kvs,
}, nil
}
func ParseGroupExtended(structArr []stackitem.Item) (*GroupExtended, error) {
if len(structArr) < 5 {
return nil, errors.New("invalid response group extended struct")
}
groupID, err := structArr[0].TryInteger()
if err != nil {
return nil, err
}
name, err := structArr[1].TryBytes()
if err != nil {
return nil, err
}
namespace, err := structArr[2].TryBytes()
if err != nil {
return nil, err
}
kvs, err := ParseMap(structArr[3])
if err != nil {
return nil, err
}
subjectCount, err := structArr[4].TryInteger()
if err != nil {
return nil, err
}
return &GroupExtended{
ID: groupID.Int64(),
Name: string(name),
Namespace: string(namespace),
KV: kvs,
SubjectsCount: subjectCount.Int64(),
}, nil
}
func ParseGroups(items []stackitem.Item) ([]*Group, error) {
var err error
res := make([]*Group, len(items))
for i := 0; i < len(items); i++ {
arr, ok := items[i].Value().([]stackitem.Item)
if !ok {
return nil, fmt.Errorf("invalid group type")
}
res[i], err = ParseGroup(arr)
if err != nil {
return nil, err
}
}
return res, nil
}
func makeValidRes(item stackitem.Item) (*result.Invoke, error) {
return &result.Invoke{
Stack: []stackitem.Item{item},
State: vmstate.Halt.String(),
}, nil
}
func makeResFromAppExec(res *state.AppExecResult) (*result.Invoke, error) {
return &result.Invoke{
Stack: res.Stack,
State: res.VMState.String(),
}, nil
}

View file

@ -4,11 +4,14 @@ safemethods:
- "getGroup"
- "getGroupExtended"
- "getGroupIDByName"
- "getGroupByName"
- "getNamespace"
- "getNamespaceExtended"
- "getSubjectKV"
- "getSubject"
- "getSubjectExtended"
- "getSubjectByKey"
- "getSubjectByName"
- "getSubjectKeyByName"
- "listGroups"
- "listGroupSubjects"

View file

@ -20,6 +20,7 @@ FrostFSID contract does not produce notifications to process.
| `G` + [ RIPEMD160 of namespace ] + [ 8 byte group id ] + [ subject address ] | []byte{1} | subject that belongs to the group |
| `c` | Int | group id counter |
| `m` + [ RIPEMD160 of namespace ] + [ RIPEMD160 of subject name ] | Serialized group id int | group name to group id index |
| `A` + [ subject address ] | bool | means that the wallet has been used |
*/

View file

@ -14,6 +14,13 @@ import (
)
type (
// Subject represents a subject entity.
//
// Fields:
// - Namespace: a string representing the namespace of the subject.
// - Name: a string representing the name of the subject.
// The name must match the following regex pattern: ^[\w+=,.@-]{1,64}$
// The Subject is stored in the storage as a hash(namespace) + hash(name).
Subject struct {
PrimaryKey interop.PublicKey
AdditionalKeys []interop.PublicKey
@ -31,6 +38,15 @@ type (
Groups []Group
}
// Namespace represents a namespace.
//
// Fields:
// - Name: a string representing the name of the namespace.
// The custom name must match the following regex pattern:
// (^$)|(^[a-z0-9]{1,2}$)|(^[a-z0-9][a-z0-9-]{1,48}[a-z0-9]$)
// An empty Name is considered the <root> namespace.
//
// The Namespace is stored in the storage as a hash(name).
Namespace struct {
Name string
}
@ -41,6 +57,18 @@ type (
SubjectsCount int
}
// Group represents a group entity.
//
// Fields:
// - ID: an integer representing the unique identifier of the group.
// The ID is generated upon creation and is the sequential number of the group within the namespace.
// It cannot be changed once set.
// - Name: a string representing the name of the group.
// The name must match the following regex pattern: [\w+=,.@-]{1,128}$
// - Namespace: a string representing the namespace of the group.
// A group exists only within one namespace.
//
// The group is stored in the storage as a hash(namespace) + hash(name).
Group struct {
ID int
Name string
@ -68,13 +96,15 @@ const (
groupSubjectsKeysPrefix = 'G'
groupCounterKey = 'c'
namespaceGroupsNamesPrefix = 'm'
addressPrefix = 'A'
)
func _deploy(data any, isUpdate bool) {
ctx := storage.GetContext()
args := data.(struct {
admin interop.Hash160
admin interop.Hash160
version int
})
if args.admin != nil {
@ -84,11 +114,35 @@ func _deploy(data any, isUpdate bool) {
storage.Put(ctx, adminKey, args.admin)
}
if isUpdate {
common.CheckVersion(args.version)
it := storage.Find(ctx, subjectKeysPrefix, storage.ValuesOnly)
for iterator.Next(it) {
subjectRaw := iterator.Value(it)
subject := std.Deserialize(subjectRaw.([]byte)).(Subject)
address := addressKey(contract.CreateStandardAccount(subject.PrimaryKey))
if storage.Get(ctx, address) != nil {
panic("frostfsid contract contains duplicate keys")
}
storage.Put(ctx, address, true)
for i := 0; i < len(subject.AdditionalKeys); i++ {
address = addressKey(contract.CreateStandardAccount(subject.AdditionalKeys[i]))
if storage.Get(ctx, address) != nil {
panic("frostfsid contract contains duplicate keys")
}
storage.Put(ctx, address, true)
}
}
}
storage.Put(ctx, groupCounterKey, 0)
storage.Put(ctx, namespaceKey(""), std.Serialize(Namespace{}))
runtime.Log("frostfsid contract initialized")
}
// SetAdmin sets the admin address for the contract.
func SetAdmin(addr interop.Hash160) {
ctx := storage.GetContext()
if !common.HasUpdateAccess() {
@ -98,6 +152,7 @@ func SetAdmin(addr interop.Hash160) {
storage.Put(ctx, adminKey, addr)
}
// ClearAdmin removes the admin address from the contract storage.
func ClearAdmin() {
ctx := storage.GetContext()
if !common.HasUpdateAccess() {
@ -107,6 +162,7 @@ func ClearAdmin() {
storage.Delete(ctx, adminKey)
}
// GetAdmin retrieves the admin address from the contract storage.
func GetAdmin() interop.Hash160 {
ctx := storage.GetReadOnlyContext()
return storage.Get(ctx, adminKey).(interop.Hash160)
@ -128,7 +184,8 @@ func Version() int {
return common.Version
}
func CreateSubject(key interop.PublicKey) {
// CreateSubject creates a new subject in the specified namespace with the provided public key.
func CreateSubject(ns string, key interop.PublicKey) {
ctx := storage.GetContext()
checkContractOwner(ctx)
@ -149,14 +206,32 @@ func CreateSubject(key interop.PublicKey) {
panic("key is occupied")
}
allAddressKey := addressKey(addr)
if storage.Get(ctx, allAddressKey) != nil {
panic("key is occupied by another additional key")
}
nsKey := namespaceKey(ns)
data = storage.Get(ctx, nsKey).([]byte)
if data == nil {
panic("namespace not found")
}
subj := Subject{
PrimaryKey: key,
Namespace: ns,
}
storage.Put(ctx, sKey, std.Serialize(subj))
nsSubjKey := namespaceSubjectKey(ns, addr)
storage.Put(ctx, nsSubjKey, []byte{1})
storage.Put(ctx, allAddressKey, true)
runtime.Notify("CreateSubject", interop.Hash160(addr))
}
// AddSubjectKey adds an additional public key to a subject with the specified address.
func AddSubjectKey(addr interop.Hash160, key interop.PublicKey) {
ctx := storage.GetContext()
checkContractOwner(ctx)
@ -168,6 +243,11 @@ func AddSubjectKey(addr interop.Hash160, key interop.PublicKey) {
panic("incorrect public key length")
}
addressKey := addressKey(contract.CreateStandardAccount(key))
if storage.Get(ctx, addressKey) != nil {
panic("key is occupied")
}
saKey := subjectAdditionalKey(key, addr)
data := storage.Get(ctx, saKey).([]byte)
if data != nil {
@ -185,9 +265,11 @@ func AddSubjectKey(addr interop.Hash160, key interop.PublicKey) {
subject.AdditionalKeys = append(subject.AdditionalKeys, key)
storage.Put(ctx, sKey, std.Serialize(subject))
storage.Put(ctx, addressKey, true)
runtime.Notify("AddSubjectKey", addr, key)
}
// RemoveSubjectKey removes an additional public key from the subject with the specified address.
func RemoveSubjectKey(addr interop.Hash160, key interop.PublicKey) {
ctx := storage.GetContext()
checkContractOwner(ctx)
@ -223,9 +305,11 @@ func RemoveSubjectKey(addr interop.Hash160, key interop.PublicKey) {
subject.AdditionalKeys = additionalKeys
storage.Put(ctx, sKey, std.Serialize(subject))
storage.Delete(ctx, addressKey(contract.CreateStandardAccount(key)))
runtime.Notify("RemoveSubjectKey", addr, key)
}
// SetSubjectName sets a new name for the subject with the specified address.
func SetSubjectName(addr interop.Hash160, name string) {
ctx := storage.GetContext()
checkContractOwner(ctx)
@ -250,6 +334,7 @@ func SetSubjectName(addr interop.Hash160, name string) {
runtime.Notify("SetSubjectName", addr, name)
}
// SetSubjectKV sets a key-value pair for the subject with the specified address.
func SetSubjectKV(addr interop.Hash160, key, val string) {
ctx := storage.GetContext()
checkContractOwner(ctx)
@ -274,6 +359,7 @@ func SetSubjectKV(addr interop.Hash160, key, val string) {
runtime.Notify("SetSubjectKV", addr, key, val)
}
// DeleteSubjectKV deletes a key-value pair from the subject with the specified address.
func DeleteSubjectKV(addr interop.Hash160, key string) {
ctx := storage.GetContext()
checkContractOwner(ctx)
@ -295,6 +381,7 @@ func DeleteSubjectKV(addr interop.Hash160, key string) {
runtime.Notify("DeleteSubjectKV", addr, key)
}
// DeleteSubject deletes the subject with the specified address.
func DeleteSubject(addr interop.Hash160) {
ctx := storage.GetContext()
checkContractOwner(ctx)
@ -312,17 +399,18 @@ func DeleteSubject(addr interop.Hash160) {
for i := 0; i < len(subj.AdditionalKeys); i++ {
storage.Delete(ctx, subjectAdditionalKey(subj.AdditionalKeys[i], addr))
storage.Delete(ctx, addressKey(contract.CreateStandardAccount(subj.AdditionalKeys[i])))
}
storage.Delete(ctx, addressKey(addr))
storage.Delete(ctx, sKey)
if subj.Namespace != "" {
removeSubjectFromNamespace(ctx, subj.Namespace, addr)
}
removeSubjectFromNamespace(ctx, subj.Namespace, addr)
deleteNamespaceSubjectName(ctx, subj.Namespace, subj.Name)
runtime.Notify("DeleteSubject", addr)
}
// GetSubject retrieves the subject with the specified address.
func GetSubject(addr interop.Hash160) Subject {
if len(addr) != interop.Hash160Len {
panic("incorrect address length")
@ -332,40 +420,34 @@ func GetSubject(addr interop.Hash160) Subject {
sKey := subjectKeyFromAddr(addr)
data := storage.Get(ctx, sKey).([]byte)
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)
}
// GetSubjectExtended retrieves the extended information of the subject with the specified address.
func GetSubjectExtended(addr interop.Hash160) SubjectExtended {
if len(addr) != interop.Hash160Len {
panic("incorrect address length")
}
subj := GetSubject(addr)
ctx := storage.GetReadOnlyContext()
sKey := subjectKeyFromAddr(addr)
data := storage.Get(ctx, sKey).([]byte)
if data == nil {
panic("subject not found")
}
subj := std.Deserialize(data).(Subject)
var groups []Group
if subj.Namespace != "" {
nsHash := ripemd160Hash(subj.Namespace)
it := storage.Find(ctx, groupPrefixFromHash(nsHash), storage.KeysOnly|storage.RemovePrefix)
for iterator.Next(it) {
groupHash := iterator.Value(it).([]byte)
data = storage.Get(ctx, groupSubjectKeyFromHashes(nsHash, groupHash, addr)).([]byte)
if data != nil {
data = storage.Get(ctx, groupKeyFromHashes(nsHash, groupHash)).([]byte)
if data == nil {
panic("group not found")
}
groups = append(groups, std.Deserialize(data).(Group))
nsHash := ripemd160Hash(subj.Namespace)
it := storage.Find(ctx, groupPrefixFromHash(nsHash), storage.KeysOnly|storage.RemovePrefix)
for iterator.Next(it) {
groupHash := iterator.Value(it).([]byte)
data := storage.Get(ctx, groupSubjectKeyFromHashes(nsHash, groupHash, addr)).([]byte)
if data != nil {
data = storage.Get(ctx, groupKeyFromHashes(nsHash, groupHash)).([]byte)
if data == nil {
panic("group not found")
}
groups = append(groups, std.Deserialize(data).(Group))
}
}
@ -381,6 +463,7 @@ func GetSubjectExtended(addr interop.Hash160) SubjectExtended {
return subjExt
}
// GetSubjectByKey retrieves the subject associated with the provided public key.
func GetSubjectByKey(key interop.PublicKey) Subject {
if len(key) != interop.PublicKeyCompressedLen {
panic("incorrect key length")
@ -394,24 +477,36 @@ func GetSubjectByKey(key interop.PublicKey) Subject {
return std.Deserialize(data).(Subject)
}
saPrefix := subjectAdditionalPrefix(key)
it := storage.Find(ctx, saPrefix, storage.KeysOnly|storage.RemovePrefix)
for iterator.Next(it) {
addr := iterator.Value(it).([]byte)
sKey = subjectKeyFromAddr(addr)
data = storage.Get(ctx, sKey).([]byte)
if data != nil {
return std.Deserialize(data).(Subject)
}
break
addr := getPrimaryAddr(ctx, contract.CreateStandardAccount(key))
sKey = subjectKeyFromAddr(addr)
data = storage.Get(ctx, sKey).([]byte)
if data != nil {
return std.Deserialize(data).(Subject)
}
panic("subject not found")
}
func getPrimaryAddr(ctx storage.Context, addr interop.Hash160) interop.Hash160 {
saPrefix := append([]byte{additionalKeysPrefix}, addr...)
it := storage.Find(ctx, saPrefix, storage.KeysOnly|storage.RemovePrefix)
if iterator.Next(it) {
return iterator.Value(it).([]byte)
}
panic("subject not found")
}
// GetSubjectByName retrieves the subject with the specified name within the given namespace.
func GetSubjectByName(ns, name string) Subject {
key := GetSubjectKeyByName(ns, name)
addr := contract.CreateStandardAccount(key)
return GetSubject(addr)
}
// GetSubjectKeyByName retrieves the public key of the subject with the specified namespace and name.
func GetSubjectKeyByName(ns, name string) interop.PublicKey {
if ns == "" || name == "" {
panic("invalid namespace or name")
if name == "" {
panic("invalid or name")
}
ctx := storage.GetReadOnlyContext()
@ -425,19 +520,44 @@ func GetSubjectKeyByName(ns, name string) interop.PublicKey {
return subjKey
}
// GetSubjectKV GetSubjectKey returns the value associated with the key for the subject.
func GetSubjectKV(addr interop.Hash160, name string) string {
if len(addr) != interop.Hash160Len {
panic("incorrect address length")
}
ctx := storage.GetReadOnlyContext()
sKey := subjectKeyFromAddr(addr)
data := storage.Get(ctx, sKey).([]byte)
if data == nil {
return ""
}
sbj := std.Deserialize(data).(Subject)
if sbj.KV == nil {
return ""
}
for k, v := range sbj.KV {
if k == name {
return v
}
}
return ""
}
func ListSubjects() iterator.Iterator {
ctx := storage.GetReadOnlyContext()
return storage.Find(ctx, []byte{subjectKeysPrefix}, storage.KeysOnly|storage.RemovePrefix)
}
// CreateNamespace creates a new namespace with the specified name.
func CreateNamespace(ns string) {
ctx := storage.GetContext()
checkContractOwner(ctx)
if ns == "" {
panic("invalid namespace name")
}
nsKey := namespaceKey(ns)
data := storage.Get(ctx, nsKey).([]byte)
if data != nil {
@ -452,6 +572,7 @@ func CreateNamespace(ns string) {
runtime.Notify("CreateNamespace", ns)
}
// GetNamespace retrieves the namespace with the specified name.
func GetNamespace(ns string) Namespace {
ctx := storage.GetReadOnlyContext()
nsKey := namespaceKey(ns)
@ -463,6 +584,7 @@ func GetNamespace(ns string) Namespace {
return std.Deserialize(data).(Namespace)
}
// GetNamespaceExtended retrieves extended information about the namespace.
func GetNamespaceExtended(ns string) NamespaceExtended {
ctx := storage.GetReadOnlyContext()
nsKey := namespaceKey(ns)
@ -494,88 +616,16 @@ func ListNamespaces() iterator.Iterator {
return storage.Find(ctx, []byte{namespaceKeysPrefix}, storage.ValuesOnly|storage.DeserializeValues)
}
func AddSubjectToNamespace(addr interop.Hash160, ns string) {
ctx := storage.GetContext()
checkContractOwner(ctx)
if ns == "" {
panic("invalid namespace name")
}
if len(addr) != interop.Hash160Len {
panic("invalid address length")
}
sKey := subjectKeyFromAddr(addr)
data := storage.Get(ctx, sKey).([]byte)
if data == nil {
panic("subject not found")
}
subject := std.Deserialize(data).(Subject)
if subject.Namespace == ns {
panic("subject already added")
}
if subject.Namespace != "" {
panic("subject cannot be moved to another namespace")
}
subject.Namespace = ns
storage.Put(ctx, sKey, std.Serialize(subject))
data = storage.Get(ctx, namespaceKey(ns)).([]byte)
if data == nil {
panic("namespace not found")
}
nsSubjKey := namespaceSubjectKey(ns, addr)
storage.Put(ctx, nsSubjKey, []byte{1})
setNamespaceSubjectName(ctx, subject)
runtime.Notify("AddSubjectToNamespace", addr, ns)
}
func RemoveSubjectFromNamespace(addr interop.Hash160) {
ctx := storage.GetContext()
checkContractOwner(ctx)
if len(addr) != interop.Hash160Len {
panic("invalid address length")
}
sKey := subjectKeyFromAddr(addr)
data := storage.Get(ctx, sKey).([]byte)
if data == nil {
panic("subject not found")
}
subject := std.Deserialize(data).(Subject)
if subject.Namespace == "" {
panic("subject does not belong to any namespace")
}
removeSubjectFromNamespace(ctx, subject.Namespace, addr)
deleteNamespaceSubjectName(ctx, subject.Namespace, subject.Name)
subject.Namespace = ""
storage.Put(ctx, sKey, std.Serialize(subject))
runtime.Notify("RemoveSubjectFromNamespace", addr, subject.Namespace)
}
func ListNamespaceSubjects(ns string) iterator.Iterator {
ctx := storage.GetReadOnlyContext()
return storage.Find(ctx, namespaceSubjectPrefix(ns), storage.KeysOnly|storage.RemovePrefix)
}
// CreateGroup creates a new group within the specified namespace.
func CreateGroup(ns, group string) int {
ctx := storage.GetContext()
checkContractOwner(ctx)
if ns == "" {
panic("invalid namespace name")
}
if group == "" {
panic("invalid group name")
}
@ -606,6 +656,7 @@ func CreateGroup(ns, group string) int {
return groupCountID
}
// GetGroup retrieves the group with the specified ID within the given namespace.
func GetGroup(ns string, groupID int) Group {
ctx := storage.GetReadOnlyContext()
gKey := groupKey(ns, groupID)
@ -617,6 +668,7 @@ func GetGroup(ns string, groupID int) Group {
return std.Deserialize(data).(Group)
}
// GetGroupExtended retrieves extended information about the group, including the count of subjects in the group.
func GetGroupExtended(ns string, groupID int) GroupExtended {
ctx := storage.GetReadOnlyContext()
gKey := groupKey(ns, groupID)
@ -642,9 +694,10 @@ func GetGroupExtended(ns string, groupID int) GroupExtended {
return grExtended
}
// GetGroupIDByName retrieves the ID of the group with the specified name within the given namespace.
func GetGroupIDByName(ns, name string) int {
if ns == "" || name == "" {
panic("invalid namespace or name")
if name == "" {
panic("invalid name")
}
ctx := storage.GetReadOnlyContext()
@ -658,6 +711,21 @@ func GetGroupIDByName(ns, name string) int {
return std.Deserialize(groupIDRaw).(int)
}
// GetGroupByName retrieves the group with the specified name within the given namespace.
func GetGroupByName(ns, name string) Group {
groupID := GetGroupIDByName(ns, name)
gKey := groupKey(ns, groupID)
ctx := storage.GetReadOnlyContext()
data := storage.Get(ctx, gKey).([]byte)
if data == nil {
panic("group not found")
}
return std.Deserialize(data).(Group)
}
// SetGroupName sets a new name for the group with the specified ID within the given namespace.
func SetGroupName(ns string, groupID int, name string) {
ctx := storage.GetContext()
checkContractOwner(ctx)
@ -678,6 +746,7 @@ func SetGroupName(ns string, groupID int, name string) {
runtime.Notify("SetGroupName", ns, groupID, name)
}
// SetGroupKV sets a key-value pair for the group with the specified ID within the given namespace.
func SetGroupKV(ns string, groupID int, key, val string) {
ctx := storage.GetContext()
checkContractOwner(ctx)
@ -698,6 +767,7 @@ func SetGroupKV(ns string, groupID int, key, val string) {
runtime.Notify("SetGroupKV", ns, groupID, key, val)
}
// DeleteGroupKV deletes a key-value pair from the group with the specified ID within the given namespace.
func DeleteGroupKV(ns string, groupID int, key string) {
ctx := storage.GetContext()
checkContractOwner(ctx)
@ -720,6 +790,7 @@ func ListGroups(ns string) iterator.Iterator {
return storage.Find(ctx, groupPrefix(ns), storage.ValuesOnly|storage.DeserializeValues)
}
// AddSubjectToGroup adds a subject to a group with the specified ID.
func AddSubjectToGroup(addr interop.Hash160, groupID int) {
ctx := storage.GetContext()
checkContractOwner(ctx)
@ -735,18 +806,16 @@ func AddSubjectToGroup(addr interop.Hash160, groupID int) {
}
subject := std.Deserialize(data).(Subject)
if subject.Namespace == "" {
panic("subject does not belong to any namespace")
}
nSubjKey := namespaceSubjectKey(subject.Namespace, addr)
storage.Put(ctx, nSubjKey, []byte{1})
gKey := groupKey(subject.Namespace, groupID)
data = storage.Get(ctx, gKey).([]byte)
if data == nil {
panic("group not found")
}
group := std.Deserialize(data).(Group)
if group.Namespace != subject.Namespace {
panic("subject and group must be in the same namespace")
}
gsKey := groupSubjectKey(subject.Namespace, groupID, addr)
storage.Put(ctx, gsKey, []byte{1})
@ -754,6 +823,7 @@ func AddSubjectToGroup(addr interop.Hash160, groupID int) {
runtime.Notify("AddSubjectToGroup", addr, subject.Namespace, groupID)
}
// RemoveSubjectFromGroup removes a subject from a group with the specified ID.
func RemoveSubjectFromGroup(addr interop.Hash160, groupID int) {
ctx := storage.GetContext()
checkContractOwner(ctx)
@ -769,15 +839,17 @@ func RemoveSubjectFromGroup(addr interop.Hash160, groupID int) {
}
subject := std.Deserialize(data).(Subject)
if subject.Namespace == "" {
panic("subject doesn't belong to any namespace")
}
gKey := groupKey(subject.Namespace, groupID)
data = storage.Get(ctx, gKey).([]byte)
if data == nil {
panic("group not found")
}
group := std.Deserialize(data).(Group)
if group.Namespace != subject.Namespace {
panic("subject and group must be in the same namespace")
}
gsKey := groupSubjectKey(subject.Namespace, groupID, addr)
storage.Delete(ctx, gsKey)
@ -790,6 +862,7 @@ func ListGroupSubjects(ns string, groupID int) iterator.Iterator {
return storage.Find(ctx, groupSubjectPrefix(ns, groupID), storage.KeysOnly|storage.RemovePrefix)
}
// DeleteGroup deletes the group with the specified ID within the given namespace.
func DeleteGroup(ns string, groupID int) {
ctx := storage.GetContext()
checkContractOwner(ctx)
@ -1004,3 +1077,7 @@ func idToBytes(itemID int) []byte {
zeros := make([]byte, 8-ln)
return append(b, zeros...)
}
func addressKey(address []byte) []byte {
return append([]byte{addressPrefix}, address...)
}

70
go.mod
View file

@ -1,14 +1,14 @@
module git.frostfs.info/TrueCloudLab/frostfs-contract
go 1.20
go 1.22
require (
github.com/google/uuid v1.3.0
github.com/google/uuid v1.6.0
github.com/mr-tron/base58 v1.2.0
github.com/nspcc-dev/neo-go v0.103.0
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20231020160724-c3955f87d1b5
github.com/stretchr/testify v1.8.4
go.uber.org/zap v1.26.0
github.com/nspcc-dev/neo-go v0.106.3
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240727093519-1a48f1ce43ec
github.com/stretchr/testify v1.9.0
go.uber.org/zap v1.27.0
)
require (
@ -17,44 +17,40 @@ require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/consensys/bavard v0.1.13 // indirect
github.com/consensys/gnark-crypto v0.12.2-0.20231013160410-1f65e75b6dfb // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/hashicorp/golang-lru v0.6.0 // indirect
github.com/holiman/uint256 v1.2.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/holiman/uint256 v1.2.4 // indirect
github.com/mmcloughlin/addchain v0.4.0 // indirect
github.com/nspcc-dev/dbft v0.0.0-20230515113611-25db6ba61d5c // indirect
github.com/nspcc-dev/go-ordered-json v0.0.0-20220111165707-25110be27d22 // indirect
github.com/nspcc-dev/neofs-crypto v0.4.0 // indirect
github.com/nspcc-dev/rfc6979 v0.2.0 // indirect
github.com/nspcc-dev/dbft v0.2.0 // indirect
github.com/nspcc-dev/go-ordered-json v0.0.0-20240301084351-0246b013f8b2 // indirect
github.com/nspcc-dev/rfc6979 v0.2.1 // indirect
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.13.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/prometheus/client_golang v1.19.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/russross/blackfriday/v2 v2.0.1 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 // indirect
github.com/twmb/murmur3 v1.1.5 // indirect
github.com/urfave/cli v1.22.5 // indirect
go.etcd.io/bbolt v1.3.7 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect
google.golang.org/protobuf v1.31.0 // indirect
github.com/twmb/murmur3 v1.1.8 // indirect
github.com/urfave/cli/v2 v2.27.2 // indirect
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
go.etcd.io/bbolt v1.3.9 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/mod v0.16.0 // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/term v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.19.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
rsc.io/tmplfunc v0.0.3 // indirect
)

599
go.sum
View file

@ -1,214 +1,85 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12 h1:npHgfD4Tl2WJS3AJaMUi5ynGDPUBfkg3U3fCzDyXZ+4=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c=
github.com/bits-and-blooms/bitset v1.8.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ=
github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI=
github.com/consensys/gnark-crypto v0.12.2-0.20231013160410-1f65e75b6dfb h1:f0BMgIjhZy4lSRHCXFbQst85f5agZAjtDMixQqBWNpc=
github.com/consensys/gnark-crypto v0.12.2-0.20231013160410-1f65e75b6dfb/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o8jga4=
github.com/hashicorp/golang-lru v0.6.0/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM=
github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
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/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU=
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
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/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY=
github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU=
github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nspcc-dev/dbft v0.0.0-20230515113611-25db6ba61d5c h1:uyK5aLbAhrnZtnvobJLN24gGUrlxIJAAFqiWl+liZuo=
github.com/nspcc-dev/dbft v0.0.0-20230515113611-25db6ba61d5c/go.mod h1:kjBC9F8L25GR+kIHy/1KgG/KfcoGnVwIiyovgq1uszk=
github.com/nspcc-dev/go-ordered-json v0.0.0-20220111165707-25110be27d22 h1:n4ZaFCKt1pQJd7PXoMJabZWK9ejjbLOVrkl/lOUmshg=
github.com/nspcc-dev/go-ordered-json v0.0.0-20220111165707-25110be27d22/go.mod h1:79bEUDEviBHJMFV6Iq6in57FEOCMcRhfQnfaf0ETA5U=
github.com/nspcc-dev/hrw v1.0.9 h1:17VcAuTtrstmFppBjfRiia4K2wA/ukXZhLFS8Y8rz5Y=
github.com/nspcc-dev/neo-go v0.103.0 h1:UVyWPhzZdfYFG35ORP3FRDLh8J/raRQ6m8SptDdlgfM=
github.com/nspcc-dev/neo-go v0.103.0/go.mod h1:x+wmcYqpZYJwLp1l/pHZrqNp3RSWlkMymWGDij3/OPo=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20231020160724-c3955f87d1b5 h1:09CpI5uwsxb1EeFPIKQRwwWlfCmDD/Dwwh01lPiQScM=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20231020160724-c3955f87d1b5/go.mod h1:J/Mk6+nKeKSW4wygkZQFLQ6SkLOSGX5Ga0RuuuktEag=
github.com/nspcc-dev/neofs-api-go/v2 v2.14.0 h1:jhuN8Ldqz7WApvUJRFY0bjRXE1R3iCkboMX5QVZhHVk=
github.com/nspcc-dev/neofs-crypto v0.4.0 h1:5LlrUAM5O0k1+sH/sktBtrgfWtq1pgpDs09fZo+KYi4=
github.com/nspcc-dev/neofs-crypto v0.4.0/go.mod h1:6XJ8kbXgOfevbI2WMruOtI+qUJXNwSGM/E9eClXxPHs=
github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.11 h1:QOc8ZRN5DXlAeRPh5QG9u8rMLgoeRNiZF5/vL7QupWg=
github.com/nspcc-dev/rfc6979 v0.2.0 h1:3e1WNxrN60/6N0DW7+UYisLeZJyfqZTNOjeV/toYvOE=
github.com/nspcc-dev/rfc6979 v0.2.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso=
github.com/nspcc-dev/tzhash v1.7.0 h1:/+aL33NC7y5OIGnY2kYgjZt8mg7LVGFMdj/KAJLndnk=
github.com/nspcc-dev/dbft v0.2.0 h1:sDwsQES600OSIMncV176t2SX5OvB14lzeOAyKFOkbMI=
github.com/nspcc-dev/dbft v0.2.0/go.mod h1:oFE6paSC/yfFh9mcNU6MheMGOYXK9+sPiRk3YMoz49o=
github.com/nspcc-dev/go-ordered-json v0.0.0-20240301084351-0246b013f8b2 h1:mD9hU3v+zJcnHAVmHnZKt3I++tvn30gBj2rP2PocZMk=
github.com/nspcc-dev/go-ordered-json v0.0.0-20240301084351-0246b013f8b2/go.mod h1:U5VfmPNM88P4RORFb6KSUVBdJBDhlqggJZYGXGPxOcc=
github.com/nspcc-dev/hrw/v2 v2.0.1 h1:CxYUkBeJvNfMEn2lHhrV6FjY8pZPceSxXUtMVq0BUOU=
github.com/nspcc-dev/hrw/v2 v2.0.1/go.mod h1:iZAs5hT2q47EGq6AZ0FjaUI6ggntOi7vrY4utfzk5VA=
github.com/nspcc-dev/neo-go v0.106.3 h1:HEyhgkjQY+HfBzotMJ12xx2VuOUphkngZ4kEkjvXDtE=
github.com/nspcc-dev/neo-go v0.106.3/go.mod h1:3vEwJ2ld12N7HRGCaH/l/7EwopplC/+8XdIdPDNmD/M=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240727093519-1a48f1ce43ec h1:vDrbVXF2+2uP0RlkZmem3QYATcXCu9BzzGGCNsNcK7Q=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240727093519-1a48f1ce43ec/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
github.com/nspcc-dev/neofs-api-go/v2 v2.14.1-0.20240305074711-35bc78d84dc4 h1:arN0Ypn+jawZpu1BND7TGRn44InAVIqKygndsx0y2no=
github.com/nspcc-dev/neofs-api-go/v2 v2.14.1-0.20240305074711-35bc78d84dc4/go.mod h1:7Tm1NKEoUVVIUlkVwFrPh7GG5+Lmta2m7EGr4oVpBd8=
github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.12 h1:mdxtlSU2I4oVZ/7AXTLKyz8uUPbDWikZw4DM8gvrddA=
github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.12/go.mod h1:JdsEM1qgNukrWqgOBDChcYp8oY4XUzidcKaxY4hNJvQ=
github.com/nspcc-dev/rfc6979 v0.2.1 h1:8wWxkamHWFmO790GsewSoKUSJjVnL1fmdRpokU/RgRM=
github.com/nspcc-dev/rfc6979 v0.2.1/go.mod h1:Tk7h5kyUWkhjyO3zUgFFhy1v2vQv3BvQEntakdtqrWc=
github.com/nspcc-dev/tzhash v1.7.2 h1:iRXoa9TJqH/DQO7FFcqpq9BdruF9E7/xnFGlIghl5J4=
github.com/nspcc-dev/tzhash v1.7.2/go.mod h1:oHiH0qwmTsZkeVs7pvCS5cVXUaLhXxSFvnmnZ++ijm4=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@ -220,390 +91,104 @@ github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 h1:xQdMZ1WLrgkkvOZ/LDQxjVxMLdby7osSh4ZEVa5sIjs=
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM=
github.com/twmb/murmur3 v1.1.5 h1:i9OLS9fkuLzBXjt6dptlAEyk58fJsSTXbRg3SgVyqgk=
github.com/twmb/murmur3 v1.1.5/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo=
github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:RmMWU37GKR2s6pgrIEB4ixgpVCt/cf7dnJv3fuH1J1c=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg=
github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
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-20181116152217-5ac8a444bdc5/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E=
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 h1:eSaPbMR4T7WfH9FvABk36NBMacoTUKdWCvV0dx+KfOg=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c h1:NUsgEN92SQQqzfA+YtqYNqYmB3DMMYLlIwUZAQFVFbo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=
google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU=
rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA=

75
nns/README.md Normal file
View file

@ -0,0 +1,75 @@
# NNS
NNS - Neo Name Service is a service that allows manage a domain name as a digital asset (NFT). It has an interface similar to `DNS` but has significant differences in its internal structure.
## Entities:
- Domain
- Record
- Owner
- Committee
### Domain
Domain is string that satisfies the following requirements:
- Length from 2 to 255 characters.
- Root domain must start with a letter.
- All other fragments must start and end with a letter or digit.
Domain has owner, a registration period, and may optionally have records.
A fee established by the committee is charged upon domain registration. After registration, the owner can manage this asset (add/delete records, transfer ownership to another owner) until the end of the domain registration period.
### Record
A record is a pair of values `<type, string>`.
Supported record types:
| Type | Description |
|-------|-------------------------------------------|
| A | Represents address record type |
| AAA | Represents IPv6 address record type |
| TXT | Represents text record type |
| CNAME | Represents canonical name record type |
| SOA | Represents start of authority record type |
### Owner
An owner is a wallet that has the right to manage this NFT (domain).
### Committee
The committee makes new tokens (domains), sets, and charges a fee for issuance.
## Globally Unique Domain Zone
For more information, see [here](../docs/globally-unique-domain-zone.md).
## NNS and Frostfsid
You can register a TLD domain without a committee signature using Frostfsid. To do this, create a new wallet
```
neo-go wallet init -w newwallet/wallet.json
```
Get wallet address:
```
neo-go wallet dump-keys -w newwallet/wallet.json
[subject-address]
[subject-key]
```
Create a subject in `FrostfsID`:
```
frostfs-adm morph frostfsid create-subject --subject-key="[subject-key]"
```
Grant permissions to the wallet:
```
frostfs-adm morph nns give-privilege --subject-address="[subject-address]"
```
Register domain:
```
neo-go contract invokefunction [NNS-hash] register "subdomain.domain" hash160:[subject-address] "email@frostfs.info" 10000 1000 1000 1000 -- [subject-address]:Global
```

View file

@ -2,8 +2,35 @@ name: "NameService"
supportedstandards: ["NEP-11"]
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf",
"tokens", "properties", "roots", "getPrice", "isAvailable", "getRecords",
"getAllRecords",
"resolve", "version"]
events:
- name: RegisterDomain
parameters:
- name: name
type: String
- name: AddRecord
parameters:
- name: name
type: String
- name: type
type: Integer
- name: DeleteRecord
parameters:
- name: name
type: String
- name: type
type: Integer
- name: DeleteRecords
parameters:
- name: name
type: String
- name: type
type: Integer
- name: DeleteDomain
parameters:
- name: name
type: String
- name: Transfer
parameters:
- name: from

View file

@ -11,6 +11,7 @@
| 0x20 | int | set of roots |
| 0x21 + tokenKey | ByteArray | serialized NameState struct |
| 0x22 + tokenKey + Hash160(tokenName) | Hash160 | container contract hash |
| 0x23 + tokenKey + Hash160(tokenName) | string | global domain flag |
*/

39
nns/frostfsid.go Normal file
View file

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

View file

@ -1,32 +0,0 @@
name: "NameService"
supportedstandards: ["NEP-11"]
safemethods:
- "balanceOf"
- "decimals"
- "getAllRecords"
- "getPrice"
- "getRecord"
- "isAvailable"
- "ownerOf"
- "properties"
- "symbol"
- "totalSupply"
- "tokensOf"
- "tokens"
- "resolve"
- "roots"
events:
- name: Transfer
parameters:
- name: from
type: Hash160
- name: to
type: Hash160
- name: amount
type: Integer
- name: tokenId
type: ByteArray
permissions:
- hash: fffdc93764dbaddd97c48f252a53ea4643faa3fd
methods: ["update"]
- methods: ["onNEP11Payment"]

View file

@ -41,6 +41,14 @@ const (
// prefixRecord contains map from (token key + hash160(token name) + record type)
// to record.
prefixRecord byte = 0x22
// prefixGlobalDomain contains a flag indicating that this domain was created using GlobalDomain.
// This is necessary to distinguish it from regular CNAME records.
prefixGlobalDomain 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.
@ -52,7 +60,7 @@ const (
// maxDomainNameFragmentLength is the maximum length of the domain name fragment.
maxDomainNameFragmentLength = 63
// minDomainNameLength is minimum domain length.
minDomainNameLength = 3
minDomainNameLength = 2
// maxDomainNameLength is maximum domain length.
maxDomainNameLength = 255
// maxTXTRecordLength is the maximum length of the TXT domain record.
@ -65,6 +73,14 @@ const (
defaultRegisterPrice = 10_0000_0000
// millisecondsInYear is amount of milliseconds per year.
millisecondsInYear = int64(365 * 24 * 3600 * 1000)
// errInvalidDomainName is an error message for invalid domain name format.
errInvalidDomainName = "invalid domain name format"
)
const (
// Cnametgt is a special TXT record ensuring all created subdomains point to the global domain - the value of this variable.
// It is guaranteed that two domains cannot point to the same global domain.
Cnametgt = "cnametgt"
)
// RecordState is a type that registered entities are saved to.
@ -219,69 +235,134 @@ func GetPrice() int {
// IsAvailable checks whether the provided domain name is available.
func IsAvailable(name string) bool {
fragments := splitAndCheck(name, false)
if fragments == nil {
panic("invalid domain name format")
}
fragments := splitAndCheck(name)
ctx := storage.GetReadOnlyContext()
l := len(fragments)
if storage.Get(ctx, append([]byte{prefixRoot}, []byte(fragments[l-1])...)) == nil {
if l != 1 {
panic("TLD not found")
}
return true
}
return parentExpired(ctx, 0, fragments)
checkParent(ctx, fragments)
checkAvailableGlobalDomain(ctx, name)
return storage.Get(ctx, append([]byte{prefixName}, getTokenKey([]byte(name))...)) == nil
}
// parentExpired returns true if any domain from fragments doesn't exist or is expired.
// first denotes the deepest subdomain to check.
func parentExpired(ctx storage.Context, first int, fragments []string) bool {
// checkAvailableGlobalDomain - triggers a panic if the global domain name is occupied.
func checkAvailableGlobalDomain(ctx storage.Context, domain string) {
globalDomain := getGlobalDomain(ctx, domain)
if globalDomain == "" {
return
}
nsBytes := storage.Get(ctx, append([]byte{prefixName}, getTokenKey([]byte(globalDomain))...))
if nsBytes != nil {
panic("global domain is already taken: " + globalDomain + ". Domain: " + domain)
}
}
// getGlobalDomain returns the global domain.
func getGlobalDomain(ctx storage.Context, domain string) string {
index := std.MemorySearch([]byte(domain), []byte("."))
if index == -1 {
return ""
}
name := domain[index+1:]
if name == "" {
return ""
}
return extractCnametgt(ctx, name, domain)
}
// extractCnametgt returns the value of the Cnametgt TXT record.
func extractCnametgt(ctx storage.Context, name, domain string) string {
fragments := splitAndCheck(domain)
tokenID := []byte(tokenIDFromName(name))
records := getRecordsByType(ctx, tokenID, name, TXT)
if records == nil {
return ""
}
globalDomain := ""
for _, name := range records {
fragments := std.StringSplit(name, "=")
if len(fragments) != 2 {
continue
}
if fragments[0] == Cnametgt {
globalDomain = fragments[1]
break
}
}
if globalDomain == "" {
return ""
}
return fragments[0] + "." + globalDomain
}
// checkParent returns parent domain or empty string if domain not found.
func checkParent(ctx storage.Context, fragments []string) string {
now := int64(runtime.GetTime())
last := len(fragments) - 1
name := fragments[last]
for i := last; i >= first; i-- {
parent := ""
for i := last; i > 0; i-- {
if i != last {
name = fragments[i] + "." + name
}
nsBytes := storage.Get(ctx, append([]byte{prefixName}, getTokenKey([]byte(name))...))
if nsBytes == nil {
return true
continue
}
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
if now >= ns.Expiration {
return true
panic("domain expired: " + name)
}
parent = name
}
return false
return parent
}
// Register registers a new domain with the specified owner and name if it's available.
func Register(name string, owner interop.Hash160, email string, refresh, retry, expire, ttl int) bool {
fragments := splitAndCheck(name, true)
if fragments == nil {
panic("invalid domain name format")
}
l := len(fragments)
tldKey := append([]byte{prefixRoot}, []byte(fragments[l-1])...)
ctx := storage.GetContext()
return register(ctx, name, owner, email, refresh, retry, expire, ttl)
}
// Register registers a new domain with the specified owner and name if it's available.
func register(ctx storage.Context, name string, owner interop.Hash160, email string, refresh, retry, expire, ttl int) bool {
fragments := splitAndCheck(name)
countZone := len(fragments)
rootZone := []byte(fragments[countZone-1])
tldKey := append([]byte{prefixRoot}, rootZone...)
tldBytes := storage.Get(ctx, tldKey)
if l == 1 {
checkCommittee()
if countZone == 1 {
checkCommitteeAndFrostfsID(ctx, owner)
if tldBytes != nil {
panic("TLD already exists")
}
storage.Put(ctx, tldKey, 0)
} else {
if tldBytes == nil {
panic("TLD not found")
parent := checkParent(ctx, fragments)
if parent == "" {
parent = fragments[len(fragments)-1]
storage.Put(ctx, append([]byte{prefixAutoCreated}, rootZone...), true)
register(ctx, parent, owner, email, refresh, retry, expire, ttl)
}
if parentExpired(ctx, 1, fragments) {
panic("one of the parent domains is not registered")
}
parentKey := getTokenKey([]byte(name[len(fragments[0])+1:]))
parentKey := getTokenKey([]byte(parent))
nsBytes := storage.Get(ctx, append([]byte{prefixName}, parentKey...))
if nsBytes == nil {
panic("parent does not exist:" + parent)
}
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
ns.checkAdmin()
@ -323,18 +404,20 @@ func Register(name string, owner interop.Hash160, email string, refresh, retry,
// NNS expiration is in milliseconds
Expiration: int64(runtime.GetTime() + expire*1000),
}
checkAvailableGlobalDomain(ctx, name)
updateSubdDomainCounter(ctx, rootZone, countZone)
putNameStateWithKey(ctx, tokenKey, ns)
putSoaRecord(ctx, name, email, refresh, retry, expire, ttl)
updateBalance(ctx, []byte(name), owner, +1)
postTransfer(oldOwner, owner, []byte(name), nil)
runtime.Notify("RegisterDomain", name)
return true
}
// Renew increases domain expiration date.
func Renew(name string) int64 {
if len(name) > maxDomainNameLength {
panic("invalid domain name format")
}
checkDomainNameLength(name)
runtime.BurnGas(GetPrice())
ctx := storage.GetContext()
ns := getNameState(ctx, []byte(name))
@ -346,9 +429,7 @@ func Renew(name string) int64 {
// UpdateSOA updates soa record.
func UpdateSOA(name, email string, refresh, retry, expire, ttl int) {
if len(name) > maxDomainNameLength {
panic("invalid domain name format")
}
checkDomainNameLength(name)
ctx := storage.GetContext()
ns := getNameState(ctx, []byte(name))
ns.checkAdmin()
@ -357,9 +438,7 @@ func UpdateSOA(name, email string, refresh, retry, expire, ttl int) {
// SetAdmin updates domain admin.
func SetAdmin(name string, admin interop.Hash160) {
if len(name) > maxDomainNameLength {
panic("invalid domain name format")
}
checkDomainNameLength(name)
if admin != nil && !runtime.CheckWitness(admin) {
panic("not witnessed by admin")
}
@ -388,7 +467,7 @@ func checkBaseRecords(typ RecordType, data string) bool {
case A:
return checkIPv4(data)
case CNAME:
return splitAndCheck(data, true) != nil
return splitAndCheck(data) != nil
case TXT:
return len(data) <= maxTXTRecordLength
case AAAA:
@ -422,13 +501,26 @@ func GetRecords(name string, typ RecordType) []string {
// DeleteRecords removes domain records with the specified type.
func DeleteRecords(name string, typ RecordType) {
ctx := storage.GetContext()
deleteRecords(ctx, name, typ)
}
// DeleteRecords removes domain records with the specified type.
func deleteRecords(ctx storage.Context, name string, typ RecordType) {
if typ == SOA {
panic("you cannot delete soa record")
}
tokenID := []byte(tokenIDFromName(name))
ctx := storage.GetContext()
ns := getNameState(ctx, tokenID)
ns.checkAdmin()
globalDomainStorage := append([]byte{prefixGlobalDomain}, getTokenKey([]byte(name))...)
globalDomainRaw := storage.Get(ctx, globalDomainStorage)
globalDomain := globalDomainRaw.(string)
if globalDomainRaw != nil && globalDomain != "" {
deleteDomain(ctx, globalDomain)
}
recordsKey := getRecordsKeyByType(tokenID, name, typ)
records := storage.Find(ctx, recordsKey, storage.KeysOnly)
for iterator.Next(records) {
@ -436,6 +528,126 @@ func DeleteRecords(name string, typ RecordType) {
storage.Delete(ctx, r)
}
updateSoaSerial(ctx, tokenID)
runtime.Notify("DeleteRecords", name, typ)
}
// DeleteRecord delete a record of the specified type by data in the provided domain.
// Returns false if the record was not found.
func DeleteRecord(name string, typ RecordType, data string) bool {
tokenID := []byte(tokenIDFromName(name))
if !checkBaseRecords(typ, data) {
panic("invalid record data")
}
ctx := storage.GetContext()
ns := getNameState(ctx, tokenID)
ns.checkAdmin()
return deleteRecord(ctx, tokenID, name, typ, data)
}
func deleteRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType, data string) bool {
recordsKey := getRecordsKeyByType(tokenId, name, typ)
var previousKey any
it := storage.Find(ctx, recordsKey, storage.KeysOnly)
for iterator.Next(it) {
key := iterator.Value(it).([]byte)
ss := storage.Get(ctx, key).([]byte)
ns := std.Deserialize(ss).(RecordState)
if ns.Name == name && ns.Type == typ && ns.Data == data {
previousKey = key
continue
}
if previousKey != nil {
data := storage.Get(ctx, key)
storage.Put(ctx, previousKey, data)
previousKey = key
}
}
if previousKey == nil {
return false
}
storage.Delete(ctx, previousKey)
runtime.Notify("DeleteRecord", name, typ)
return true
}
// DeleteDomain deletes the domain with the given name.
func DeleteDomain(name string) {
ctx := storage.GetContext()
deleteDomain(ctx, name)
}
func countSubdomains(name string) int {
countSubDomains := 0
it := Tokens()
for iterator.Next(it) {
domain := iterator.Value(it)
if std.MemorySearch([]byte(domain.(string)), []byte(name)) > 0 {
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))...)
nsRaw := storage.Get(ctx, nsKey)
if nsRaw == nil {
panic("domain not found")
}
ns := std.Deserialize(nsRaw.([]byte)).(NameState)
ns.checkAdmin()
countSubDomain := 0
countSubDomainRaw := storage.Get(ctx, countSubDomainsKey)
if countSubDomainRaw != nil {
countSubDomain = common.FromFixedWidth64(countSubDomainRaw.([]byte))
} else {
countSubDomain = countSubdomains(fragments[len(fragments)-1])
}
if countSubDomain > 1 && len(fragments) == 1 {
panic("can't delete TLD domain that has subdomains")
}
countSubDomain = countSubDomain - 1
storage.Put(ctx, countSubDomainsKey, common.ToFixedWidth64(countSubDomain))
globalNSKey := append([]byte{prefixGlobalDomain}, getTokenKey([]byte(name))...)
globalDomainRaw := storage.Get(ctx, globalNSKey)
globalDomain := globalDomainRaw.(string)
if globalDomainRaw != nil && globalDomain != "" {
deleteDomain(ctx, globalDomain)
}
deleteRecords(ctx, name, CNAME)
deleteRecords(ctx, name, TXT)
deleteRecords(ctx, name, A)
deleteRecords(ctx, name, AAAA)
storage.Delete(ctx, nsKey)
storage.Delete(ctx, append([]byte{prefixRoot}, []byte(name)...))
isAutoCreated := storage.Get(ctx, autoCreatedPrefix)
if countSubDomain == 1 && isAutoCreated != nil && isAutoCreated.(bool) {
deleteDomain(ctx, fragments[len(fragments)-1])
}
if len(fragments) == 1 {
storage.Delete(ctx, countSubDomainsKey)
storage.Delete(ctx, autoCreatedPrefix)
}
runtime.Notify("DeleteDomain", name)
}
// Resolve resolves given name (not more then three redirects are allowed).
@ -508,9 +720,7 @@ func getNameState(ctx storage.Context, tokenID []byte) NameState {
tokenKey := getTokenKey(tokenID)
ns := getNameStateWithKey(ctx, tokenKey)
fragments := std.StringSplit(string(tokenID), ".")
if parentExpired(ctx, 1, fragments) {
panic("parent domain has expired")
}
checkParent(ctx, fragments)
return ns
}
@ -580,6 +790,33 @@ func addRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType,
}
}
globalDomainKey := append([]byte{prefixGlobalDomain}, getTokenKey([]byte(name))...)
globalDomainStorage := storage.Get(ctx, globalDomainKey)
globalDomain := getGlobalDomain(ctx, name)
if globalDomainStorage == nil && typ == TXT {
if globalDomain != "" {
checkAvailableGlobalDomain(ctx, name)
nsOriginal := getNameState(ctx, []byte(tokenIDFromName(name)))
ns := NameState{
Name: globalDomain,
Owner: nsOriginal.Owner,
Expiration: nsOriginal.Expiration,
Admin: nsOriginal.Admin,
}
putNameStateWithKey(ctx, getTokenKey([]byte(globalDomain)), ns)
storage.Put(ctx, globalDomainKey, globalDomain)
var oldOwner interop.Hash160
updateBalance(ctx, []byte(name), nsOriginal.Owner, +1)
postTransfer(oldOwner, nsOriginal.Owner, []byte(name), nil)
putCnameRecord(ctx, globalDomain, name)
} else {
storage.Put(ctx, globalDomainKey, "")
}
}
if typ == CNAME && id != 0 {
panic("you shouldn't have more than one CNAME record")
}
@ -598,6 +835,7 @@ func storeRecord(ctx storage.Context, recordKey []byte, name string, typ RecordT
}
recBytes := std.Serialize(rs)
storage.Put(ctx, recordKey, recBytes)
runtime.Notify("AddRecord", name, typ)
}
// putSoaRecord stores soa domain record.
@ -618,6 +856,24 @@ func putSoaRecord(ctx storage.Context, name, email string, refresh, retry, expir
}
recBytes := std.Serialize(rs)
storage.Put(ctx, recordKey, recBytes)
runtime.Notify("AddRecord", name, SOA)
}
// putCnameRecord stores CNAME domain record.
func putCnameRecord(ctx storage.Context, name, data string) {
var id byte
tokenId := []byte(tokenIDFromName(name))
recordKey := getIdRecordKey(tokenId, name, CNAME, id)
rs := RecordState{
Name: name,
Type: CNAME,
ID: id,
Data: data,
}
recBytes := std.Serialize(rs)
storage.Put(ctx, recordKey, recBytes)
runtime.Notify("AddRecord", name, CNAME)
}
// updateSoaSerial stores soa domain record.
@ -627,7 +883,7 @@ func updateSoaSerial(ctx storage.Context, tokenId []byte) {
recBytes := storage.Get(ctx, recordKey)
if recBytes == nil {
panic("not found soa record")
return
}
rec := std.Deserialize(recBytes.([]byte)).(RecordState)
@ -668,6 +924,14 @@ func isValid(address interop.Hash160) bool {
return address != nil && len(address) == interop.Hash160Len
}
// checkCommitteeAndFrostfsID panics if the script container is not signed by the committee.
// or if the owner does not have permission in FrostfsID.
func checkCommitteeAndFrostfsID(ctx storage.Context, owner interop.Hash160) {
if !checkFrostfsID(ctx, owner) {
checkCommittee()
}
}
// checkCommittee panics if the script container is not signed by the committee.
func checkCommittee() {
committee := neo.GetCommittee()
@ -716,24 +980,29 @@ func isAlNum(c uint8) bool {
}
// splitAndCheck splits domain name into parts and validates it.
func splitAndCheck(name string, allowMultipleFragments bool) []string {
l := len(name)
if l < minDomainNameLength || maxDomainNameLength < l {
return nil
}
func splitAndCheck(name string) []string {
checkDomainNameLength(name)
fragments := std.StringSplit(name, ".")
l = len(fragments)
if l > 2 && !allowMultipleFragments {
return nil
}
l := len(fragments)
for i := 0; i < l; i++ {
if !checkFragment(fragments[i], i == l-1) {
return nil
panic(errInvalidDomainName + " '" + name + "': invalid fragment '" + fragments[i] + "'")
}
}
return fragments
}
// checkDomainNameLength panics if domain name length is out of boundaries.
func checkDomainNameLength(name string) {
l := len(name)
if l > maxDomainNameLength {
panic(errInvalidDomainName + " '" + name + "': domain name too long: got = " + std.Itoa(l, 10) + ", max = " + std.Itoa(maxDomainNameLength, 10))
}
if l < minDomainNameLength {
panic(errInvalidDomainName + " '" + name + "': domain name too short: got = " + std.Itoa(l, 10) + ", min = " + std.Itoa(minDomainNameLength, 10))
}
}
// checkIPv4 checks record on IPv4 compliance.
func checkIPv4(data string) bool {
l := len(data)
@ -846,10 +1115,7 @@ func checkIPv6(data string) bool {
// tokenIDFromName returns token ID (domain.root) from the provided name.
func tokenIDFromName(name string) string {
fragments := splitAndCheck(name, true)
if fragments == nil {
panic("invalid domain name format")
}
fragments := splitAndCheck(name)
ctx := storage.GetReadOnlyContext()
sum := 0
@ -908,3 +1174,16 @@ func getAllRecords(ctx storage.Context, name string) iterator.Iterator {
recordsKey := getRecordsKey(tokenID, name)
return storage.Find(ctx, recordsKey, storage.ValuesOnly|storage.DeserializeValues)
}
func updateSubdDomainCounter(ctx storage.Context, rootZone []byte, countZone int) {
countSubDomain := 0
delInfoRaw := storage.Get(ctx, append([]byte{prefixCountSubDomains}, rootZone...))
if delInfoRaw != nil {
countSubDomain = common.FromFixedWidth64(delInfoRaw.([]byte))
}
if delInfoRaw != nil || countZone == 1 {
countSubDomain = countSubDomain + 1
storage.Put(ctx, append([]byte{prefixCountSubDomains}, rootZone...), common.ToFixedWidth64(countSubDomain))
}
}

View file

@ -1,6 +1,12 @@
name: "APE"
permissions:
- methods: ["update"]
safemethods:
- "getAdmin"
- "listChains"
- "getChain"
- "listChainsByPrefix"
- "listTargets"
- "listChainNames"
- "iteratorChainsByPrefix"
- "version"

View file

@ -6,6 +6,8 @@
|------------------------------------------|--------|-----------------------------------|
| 'c' + uint16(len(container)) + container | []byte | Namespace chain |
| 'n' + uint16(len(namespace)) + namespace | []byte | Container chain |
| 'm' + entity name (namespace/container) | []byte | Mapped name to an encoded number |
| 'Counter' | uint64 | Integer counter used for mapping |
*/

View file

@ -4,6 +4,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
)
@ -15,6 +16,8 @@ type Kind byte
const (
Namespace = 'n'
Container = 'c'
User = 'u'
Group = 'g'
IAM = 'i'
)
@ -22,6 +25,11 @@ const (
ownerKeyPrefix = 'o'
)
const (
mappingKeyPrefix = 'm'
counterKey = "Counter"
)
const (
// ErrNotAuthorized is returned when the none of the transaction signers
// belongs to the list of autorized keys.
@ -31,6 +39,8 @@ const (
// _deploy function sets up initial list of inner ring public keys.
func _deploy(data any, isUpdate bool) {
if isUpdate {
args := data.([]any)
common.CheckVersion(args[len(args)-1].(int))
return
}
@ -44,6 +54,7 @@ func _deploy(data any, isUpdate bool) {
}
storage.Put(ctx, []byte{ownerKeyPrefix}, args.Admin)
}
storage.Put(ctx, counterKey, 0)
}
func checkAuthorization(ctx storage.Context) {
@ -58,6 +69,22 @@ func checkAuthorization(ctx storage.Context) {
panic(ErrNotAuthorized)
}
// Version returns the version of the contract.
func Version() int {
return common.Version
}
// Update method updates contract source code and manifest. It can be invoked
// by committee only.
func Update(script []byte, manifest []byte, data any) {
if !common.HasUpdateAccess() {
panic("only committee can update contract")
}
management.UpdateWithData(script, manifest, common.AppendVersion(data))
runtime.Log("policy contract updated")
}
func SetAdmin(addr interop.Hash160) {
common.CheckAlphabetWitness()
@ -74,24 +101,65 @@ func getAdmin(ctx storage.Context) interop.Hash160 {
return storage.Get(ctx, []byte{ownerKeyPrefix}).(interop.Hash160)
}
func storageKey(prefix Kind, entityName string, name []byte) []byte {
ln := len(entityName)
key := append([]byte{byte(prefix)}, byte(ln&0xFF), byte(ln>>8))
key = append(key, entityName...)
func storageKey(prefix Kind, counter int, name []byte) []byte {
key := append([]byte{byte(prefix)}, common.ToFixedWidth64(counter)...)
return append(key, name...)
}
func mapKey(kind Kind, name []byte) []byte {
return append([]byte{mappingKeyPrefix, byte(kind)}, name...)
}
// mapToNumeric maps a name to a number. That allows to keep more space in
// a storage key shortening long names. Short entity
// names are also mapped to prevent collisions in the map.
func mapToNumeric(ctx storage.Context, kind Kind, name []byte) (mapped int, mappingExists bool) {
mKey := mapKey(kind, name)
numericID := storage.Get(ctx, mKey)
if numericID == nil {
return 0, false
}
mapped = numericID.(int)
mappingExists = true
return
}
// mapToNumericCreateIfNotExists maps a name to a number. That allows to keep
// more space in a storage key shortening long names. Short entity
// names are also mapped to prevent collisions in the map.
// If a mapping cannot be found, then the method creates and returns it.
// mapToNumericCreateIfNotExists is NOT applicable for a read-only context.
func mapToNumericCreateIfNotExists(ctx storage.Context, kind Kind, name []byte) int {
mKey := mapKey(kind, name)
numericID := storage.Get(ctx, mKey)
if numericID == nil {
counter := storage.Get(ctx, counterKey).(int)
counter++
storage.Put(ctx, counterKey, counter)
storage.Put(ctx, mKey, counter)
return counter
}
return numericID.(int)
}
func AddChain(entity Kind, entityName string, name []byte, chain []byte) {
ctx := storage.GetContext()
checkAuthorization(ctx)
key := storageKey(entity, entityName, name)
entityNameBytes := mapToNumericCreateIfNotExists(ctx, entity, []byte(entityName))
key := storageKey(entity, entityNameBytes, name)
storage.Put(ctx, key, chain)
}
func GetChain(entity Kind, entityName string, name []byte) []byte {
ctx := storage.GetReadOnlyContext()
key := storageKey(entity, entityName, name)
entityNameBytes, exists := mapToNumeric(ctx, entity, []byte(entityName))
if !exists {
panic("not found")
}
key := storageKey(entity, entityNameBytes, name)
data := storage.Get(ctx, key).([]byte)
if data == nil {
panic("not found")
@ -104,19 +172,43 @@ func RemoveChain(entity Kind, entityName string, name []byte) {
ctx := storage.GetContext()
checkAuthorization(ctx)
key := storageKey(entity, entityName, name)
entityNameNum, exists := mapToNumeric(ctx, entity, []byte(entityName))
if !exists {
return
}
key := storageKey(entity, entityNameNum, name)
storage.Delete(ctx, key)
// If no chains are left for the target, then remove the mapping.
prefix := append([]byte{byte(entity)}, common.ToFixedWidth64(entityNameNum)...)
it := storage.Find(ctx, prefix, storage.KeysOnly)
if !iterator.Next(it) {
storage.Delete(ctx, mapKey(entity, []byte(entityName)))
}
}
func RemoveChainsByPrefix(entity Kind, entityName string, name []byte) {
ctx := storage.GetContext()
checkAuthorization(ctx)
key := storageKey(entity, entityName, name)
entityNameNum, exists := mapToNumeric(ctx, entity, []byte(entityName))
if !exists {
return
}
key := storageKey(entity, entityNameNum, name)
it := storage.Find(ctx, key, storage.KeysOnly)
for iterator.Next(it) {
storage.Delete(ctx, iterator.Value(it).([]byte))
}
// If no chains are left for the target, then remove the mapping.
prefix := append([]byte{byte(entity)}, common.ToFixedWidth64(entityNameNum)...)
it = storage.Find(ctx, prefix, storage.KeysOnly)
if !iterator.Next(it) {
storage.Delete(ctx, mapKey(entity, []byte(entityName)))
}
}
// ListChains lists all chains for the namespace by prefix.
@ -137,7 +229,12 @@ func ListChainsByPrefix(entity Kind, entityName string, prefix []byte) [][]byte
result := [][]byte{}
keyPrefix := storageKey(entity, entityName, prefix)
entityNameBytes, exists := mapToNumeric(ctx, entity, []byte(entityName))
if !exists {
return result
}
keyPrefix := storageKey(entity, entityNameBytes, prefix)
it := storage.Find(ctx, keyPrefix, storage.ValuesOnly)
for iterator.Next(it) {
result = append(result, iterator.Value(it).([]byte))
@ -145,3 +242,25 @@ func ListChainsByPrefix(entity Kind, entityName string, prefix []byte) [][]byte
return result
}
func IteratorChainsByPrefix(entity Kind, entityName string, prefix []byte) iterator.Iterator {
ctx := storage.GetReadOnlyContext()
id, _ := mapToNumeric(ctx, entity, []byte(entityName))
keyPrefix := storageKey(entity, id, prefix)
return storage.Find(ctx, keyPrefix, storage.ValuesOnly)
}
// ListTargets iterates over targets for which rules are defined.
func ListTargets(entity Kind) iterator.Iterator {
ctx := storage.GetReadOnlyContext()
mKey := mapKey(entity, []byte{})
return storage.Find(ctx, mKey, storage.KeysOnly|storage.RemovePrefix)
}
// ListChainNames iterates over chain names for specific target.
func ListChainNames(entity Kind, entityName string) iterator.Iterator {
ctx := storage.GetReadOnlyContext()
id, _ := mapToNumeric(ctx, entity, []byte(entityName))
keyPrefix := storageKey(entity, id, []byte{})
return storage.Find(ctx, keyPrefix, storage.KeysOnly|storage.RemovePrefix)
}

View file

@ -3,19 +3,11 @@ package processing
import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"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/gas"
"github.com/nspcc-dev/neo-go/pkg/interop/native/ledger"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
"github.com/nspcc-dev/neo-go/pkg/interop/native/roles"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
)
const (
frostfsContractKey = "frostfsScriptHash"
multiaddrMethod = "alphabetAddress"
)
// OnNEP17Payment is a callback for NEP-17 compatible native GAS contract.
@ -33,18 +25,6 @@ func _deploy(data any, isUpdate bool) {
return
}
args := data.(struct {
addrFrostFS interop.Hash160
})
ctx := storage.GetContext()
if len(args.addrFrostFS) != interop.Hash160Len {
panic("incorrect length of contract script hash")
}
storage.Put(ctx, frostfsContractKey, args.addrFrostFS)
runtime.Log("processing contract initialized")
}
@ -66,11 +46,7 @@ func Update(script []byte, manifest []byte, data any) {
// Verify method returns true if transaction contains valid multisignature of
// Alphabet nodes of the Inner Ring.
func Verify() bool {
ctx := storage.GetContext()
frostfsContractAddr := storage.Get(ctx, frostfsContractKey).(interop.Hash160)
multiaddr := contract.Call(frostfsContractAddr, multiaddrMethod, contract.ReadOnly).(interop.Hash160)
return runtime.CheckWitness(multiaddr)
return runtime.CheckWitness(common.AlphabetAddress())
}
// Version returns the version of the contract.

View file

@ -5,10 +5,12 @@ import (
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/native/gas"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
"github.com/nspcc-dev/neo-go/pkg/interop/native/neo"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
)
const accountKeyPrefix = 'a'
// OnNEP17Payment is a callback for NEP-17 compatible native GAS contract.
func OnNEP17Payment(from interop.Hash160, amount int, data any) {
caller := runtime.GetCallingScriptHash()
@ -17,6 +19,15 @@ func OnNEP17Payment(from interop.Hash160, amount int, data any) {
}
}
// OnNEP11Payment is a callback for NEP-11 compatible NNS contract.
func OnNEP11Payment(from interop.Hash160, amount int, token []byte, data any) {
caller := runtime.GetCallingScriptHash()
nnsHash := management.GetContractByID(1).Hash
if !common.BytesEqual(caller, []byte(nnsHash)) {
common.AbortWithMessage("proxy contract accepts NNS tokens only")
}
}
func _deploy(data any, isUpdate bool) {
if isUpdate {
args := data.([]any)
@ -39,20 +50,46 @@ func Update(script []byte, manifest []byte, data any) {
}
// Verify method returns true if transaction contains valid multisignature of
// Alphabet nodes of the Inner Ring.
// Alphabet nodes of the Inner Ring or any of the trusted accounts added via AddAccount.
func Verify() bool {
alphabet := neo.GetCommittee()
sig := common.Multiaddress(alphabet, false)
if !runtime.CheckWitness(sig) {
sig = common.Multiaddress(alphabet, true)
return runtime.CheckWitness(sig)
if !runtime.GetScriptContainer().Sender.Equals(runtime.GetExecutingScriptHash()) {
return false
}
return true
signers := runtime.CurrentSigners()
ctx := storage.GetReadOnlyContext()
for i := 1; /* skip sender */ i < len(signers); i++ {
if storage.Get(ctx, append([]byte{accountKeyPrefix}, signers[i].Account...)) != nil {
return true
}
}
if runtime.CheckWitness(common.CommitteeAddress()) {
return true
}
if runtime.CheckWitness(common.AlphabetAddress()) {
return true
}
return false
}
// Version returns the version of the contract.
func Version() int {
return common.Version
}
func AddAccount(addr interop.Hash160) {
common.CheckWitness(common.CommitteeAddress())
ctx := storage.GetContext()
storage.Put(ctx, append([]byte{accountKeyPrefix}, addr...), []byte{1})
}
func RemoveAccount(addr interop.Hash160) {
common.CheckWitness(common.CommitteeAddress())
ctx := storage.GetContext()
storage.Delete(ctx, append([]byte{accountKeyPrefix}, addr...))
}

View file

@ -1,6 +1,6 @@
// Package alphabet contains RPC wrappers for Alphabet contract.
//
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
// Package alphabet contains RPC wrappers for Alphabet contract.
package alphabet
import (

View file

@ -1,6 +1,6 @@
// Package balance contains RPC wrappers for Balance contract.
//
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
// Package balance contains RPC wrappers for Balance contract.
package balance
import (

View file

@ -1,6 +1,6 @@
// Package container contains RPC wrappers for Container contract.
//
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
// Package container contains RPC wrappers for Container contract.
package container
import (
@ -25,7 +25,7 @@ type PutSuccessEvent struct {
// DeleteSuccessEvent represents "DeleteSuccess" event emitted by the contract.
type DeleteSuccessEvent struct {
ContainerID []byte
ContainerID util.Uint256
}
// SetEACLSuccessEvent represents "SetEACLSuccess" event emitted by the contract.
@ -163,14 +163,14 @@ func (c *ContractReader) Version() (*big.Int, error) {
// Delete creates a transaction invoking `delete` 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) Delete(containerID []byte, signature []byte, publicKey *keys.PublicKey, token []byte) (util.Uint256, uint32, error) {
func (c *Contract) Delete(containerID util.Uint256, signature []byte, publicKey *keys.PublicKey, token []byte) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "delete", containerID, signature, publicKey, token)
}
// DeleteTransaction creates a transaction invoking `delete` method of the contract.
// This transaction is signed, but not sent to the network, instead it's
// returned to the caller.
func (c *Contract) DeleteTransaction(containerID []byte, signature []byte, publicKey *keys.PublicKey, token []byte) (*transaction.Transaction, error) {
func (c *Contract) DeleteTransaction(containerID util.Uint256, signature []byte, publicKey *keys.PublicKey, token []byte) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "delete", containerID, signature, publicKey, token)
}
@ -178,7 +178,7 @@ func (c *Contract) DeleteTransaction(containerID []byte, signature []byte, publi
// 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) DeleteUnsigned(containerID []byte, signature []byte, publicKey *keys.PublicKey, token []byte) (*transaction.Transaction, error) {
func (c *Contract) DeleteUnsigned(containerID util.Uint256, signature []byte, publicKey *keys.PublicKey, token []byte) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "delete", nil, containerID, signature, publicKey, token)
}
@ -270,6 +270,28 @@ func (c *Contract) PutNamedUnsigned(container []byte, signature []byte, publicKe
return c.actor.MakeUnsignedCall(c.hash, "putNamed", nil, container, signature, publicKey, token, name, zone)
}
// SetAdmin creates a transaction invoking `setAdmin` method of the contract.
// This transaction is signed and immediately sent to the network.
// The values returned are its hash, ValidUntilBlock value and error if any.
func (c *Contract) SetAdmin(admin util.Uint160) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "setAdmin", admin)
}
// SetAdminTransaction creates a transaction invoking `setAdmin` method of the contract.
// This transaction is signed, but not sent to the network, instead it's
// returned to the caller.
func (c *Contract) SetAdminTransaction(admin util.Uint160) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "setAdmin", admin)
}
// SetAdminUnsigned creates a transaction invoking `setAdmin` 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) SetAdminUnsigned(admin util.Uint160) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "setAdmin", nil, admin)
}
// SetEACL creates a transaction invoking `setEACL` 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.
@ -480,7 +502,17 @@ func (e *DeleteSuccessEvent) FromStackItem(item *stackitem.Array) error {
err error
)
index++
e.ContainerID, err = arr[index].TryBytes()
e.ContainerID, err = func(item stackitem.Item) (util.Uint256, error) {
b, err := item.TryBytes()
if err != nil {
return util.Uint256{}, err
}
u, err := util.Uint256DecodeBytesBE(b)
if err != nil {
return util.Uint256{}, err
}
return u, nil
}(arr[index])
if err != nil {
return fmt.Errorf("field ContainerID: %w", err)
}

View file

@ -1,6 +1,6 @@
// Package frostfs contains RPC wrappers for FrostFS contract.
//
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
// Package frostfs contains RPC wrappers for FrostFS contract.
package frostfs
import (
@ -50,12 +50,6 @@ type UnbindEvent struct {
Keys []any
}
// AlphabetUpdateEvent represents "AlphabetUpdate" event emitted by the contract.
type AlphabetUpdateEvent struct {
Id []byte
Alphabet []any
}
// SetConfigEvent represents "SetConfig" event emitted by the contract.
type SetConfigEvent struct {
Id []byte
@ -103,11 +97,6 @@ func New(actor Actor, hash util.Uint160) *Contract {
return &Contract{ContractReader{actor, hash}, actor, hash}
}
// AlphabetAddress invokes `alphabetAddress` method of contract.
func (c *ContractReader) AlphabetAddress() (util.Uint160, error) {
return unwrap.Uint160(c.invoker.Call(c.hash, "alphabetAddress"))
}
// Config invokes `config` method of contract.
func (c *ContractReader) Config(key []byte) (any, error) {
return func(item stackitem.Item, err error) (any, error) {
@ -715,77 +704,6 @@ func (e *UnbindEvent) FromStackItem(item *stackitem.Array) error {
return nil
}
// AlphabetUpdateEventsFromApplicationLog retrieves a set of all emitted events
// with "AlphabetUpdate" name from the provided [result.ApplicationLog].
func AlphabetUpdateEventsFromApplicationLog(log *result.ApplicationLog) ([]*AlphabetUpdateEvent, error) {
if log == nil {
return nil, errors.New("nil application log")
}
var res []*AlphabetUpdateEvent
for i, ex := range log.Executions {
for j, e := range ex.Events {
if e.Name != "AlphabetUpdate" {
continue
}
event := new(AlphabetUpdateEvent)
err := event.FromStackItem(e.Item)
if err != nil {
return nil, fmt.Errorf("failed to deserialize AlphabetUpdateEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
}
res = append(res, event)
}
}
return res, nil
}
// FromStackItem converts provided [stackitem.Array] to AlphabetUpdateEvent or
// returns an error if it's not possible to do to so.
func (e *AlphabetUpdateEvent) FromStackItem(item *stackitem.Array) error {
if item == nil {
return errors.New("nil item")
}
arr, ok := item.Value().([]stackitem.Item)
if !ok {
return errors.New("not an array")
}
if len(arr) != 2 {
return errors.New("wrong number of structure elements")
}
var (
index = -1
err error
)
index++
e.Id, err = arr[index].TryBytes()
if err != nil {
return fmt.Errorf("field Id: %w", err)
}
index++
e.Alphabet, err = func(item stackitem.Item) ([]any, error) {
arr, ok := item.Value().([]stackitem.Item)
if !ok {
return nil, errors.New("not an array")
}
res := make([]any, len(arr))
for i := range res {
res[i], err = arr[i].Value(), error(nil)
if err != nil {
return nil, fmt.Errorf("item %d: %w", i, err)
}
}
return res, nil
}(arr[index])
if err != nil {
return fmt.Errorf("field Alphabet: %w", err)
}
return nil
}
// SetConfigEventsFromApplicationLog retrieves a set of all emitted events
// with "SetConfig" name from the provided [result.ApplicationLog].
func SetConfigEventsFromApplicationLog(log *result.ApplicationLog) ([]*SetConfigEvent, error) {

View file

@ -1,6 +1,6 @@
// Package identity contains RPC wrappers for Identity contract.
//
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
// Package identity contains RPC wrappers for Identity contract.
package identity
import (
@ -177,6 +177,11 @@ func (c *ContractReader) GetGroup(ns string, groupID *big.Int) ([]stackitem.Item
return unwrap.Array(c.invoker.Call(c.hash, "getGroup", ns, groupID))
}
// GetGroupByName invokes `getGroupByName` method of contract.
func (c *ContractReader) GetGroupByName(ns string, name string) ([]stackitem.Item, error) {
return unwrap.Array(c.invoker.Call(c.hash, "getGroupByName", ns, name))
}
// GetGroupExtended invokes `getGroupExtended` method of contract.
func (c *ContractReader) GetGroupExtended(ns string, groupID *big.Int) ([]stackitem.Item, error) {
return unwrap.Array(c.invoker.Call(c.hash, "getGroupExtended", ns, groupID))
@ -207,11 +212,21 @@ func (c *ContractReader) GetSubjectByKey(key *keys.PublicKey) ([]stackitem.Item,
return unwrap.Array(c.invoker.Call(c.hash, "getSubjectByKey", key))
}
// GetSubjectByName invokes `getSubjectByName` method of contract.
func (c *ContractReader) GetSubjectByName(ns string, name string) ([]stackitem.Item, error) {
return unwrap.Array(c.invoker.Call(c.hash, "getSubjectByName", ns, name))
}
// GetSubjectExtended invokes `getSubjectExtended` method of contract.
func (c *ContractReader) GetSubjectExtended(addr util.Uint160) ([]stackitem.Item, error) {
return unwrap.Array(c.invoker.Call(c.hash, "getSubjectExtended", addr))
}
// GetSubjectKV invokes `getSubjectKV` method of contract.
func (c *ContractReader) GetSubjectKV(addr util.Uint160, name string) (string, error) {
return unwrap.UTF8String(c.invoker.Call(c.hash, "getSubjectKV", addr, name))
}
// GetSubjectKeyByName invokes `getSubjectKeyByName` method of contract.
func (c *ContractReader) GetSubjectKeyByName(ns string, name string) (*keys.PublicKey, error) {
return unwrap.PublicKey(c.invoker.Call(c.hash, "getSubjectKeyByName", ns, name))
@ -336,28 +351,6 @@ func (c *Contract) AddSubjectToGroupUnsigned(addr util.Uint160, groupID *big.Int
return c.actor.MakeUnsignedCall(c.hash, "addSubjectToGroup", nil, addr, groupID)
}
// AddSubjectToNamespace creates a transaction invoking `addSubjectToNamespace` 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) AddSubjectToNamespace(addr util.Uint160, ns string) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "addSubjectToNamespace", addr, ns)
}
// AddSubjectToNamespaceTransaction creates a transaction invoking `addSubjectToNamespace` method of the contract.
// This transaction is signed, but not sent to the network, instead it's
// returned to the caller.
func (c *Contract) AddSubjectToNamespaceTransaction(addr util.Uint160, ns string) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "addSubjectToNamespace", addr, ns)
}
// AddSubjectToNamespaceUnsigned creates a transaction invoking `addSubjectToNamespace` 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) AddSubjectToNamespaceUnsigned(addr util.Uint160, ns string) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "addSubjectToNamespace", nil, addr, ns)
}
// ClearAdmin creates a transaction invoking `clearAdmin` 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.
@ -427,23 +420,23 @@ func (c *Contract) CreateNamespaceUnsigned(ns string) (*transaction.Transaction,
// CreateSubject creates a transaction invoking `createSubject` 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) CreateSubject(key *keys.PublicKey) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "createSubject", key)
func (c *Contract) CreateSubject(ns string, key *keys.PublicKey) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "createSubject", ns, key)
}
// CreateSubjectTransaction creates a transaction invoking `createSubject` method of the contract.
// This transaction is signed, but not sent to the network, instead it's
// returned to the caller.
func (c *Contract) CreateSubjectTransaction(key *keys.PublicKey) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "createSubject", key)
func (c *Contract) CreateSubjectTransaction(ns string, key *keys.PublicKey) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "createSubject", ns, key)
}
// CreateSubjectUnsigned creates a transaction invoking `createSubject` 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) CreateSubjectUnsigned(key *keys.PublicKey) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "createSubject", nil, key)
func (c *Contract) CreateSubjectUnsigned(ns string, key *keys.PublicKey) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "createSubject", nil, ns, key)
}
// DeleteGroup creates a transaction invoking `deleteGroup` method of the contract.
@ -556,28 +549,6 @@ func (c *Contract) RemoveSubjectFromGroupUnsigned(addr util.Uint160, groupID *bi
return c.actor.MakeUnsignedCall(c.hash, "removeSubjectFromGroup", nil, addr, groupID)
}
// RemoveSubjectFromNamespace creates a transaction invoking `removeSubjectFromNamespace` 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) RemoveSubjectFromNamespace(addr util.Uint160) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "removeSubjectFromNamespace", addr)
}
// RemoveSubjectFromNamespaceTransaction creates a transaction invoking `removeSubjectFromNamespace` method of the contract.
// This transaction is signed, but not sent to the network, instead it's
// returned to the caller.
func (c *Contract) RemoveSubjectFromNamespaceTransaction(addr util.Uint160) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "removeSubjectFromNamespace", addr)
}
// RemoveSubjectFromNamespaceUnsigned creates a transaction invoking `removeSubjectFromNamespace` 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) RemoveSubjectFromNamespaceUnsigned(addr util.Uint160) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "removeSubjectFromNamespace", nil, addr)
}
// RemoveSubjectKey creates a transaction invoking `removeSubjectKey` 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.

View file

@ -1,6 +1,6 @@
// Package netmap contains RPC wrappers for Netmap contract.
//
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
// Package netmap contains RPC wrappers for Netmap contract.
package netmap
import (

View file

@ -1,9 +1,11 @@
// Package nameservice contains RPC wrappers for NameService contract.
//
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
// Package nameservice contains RPC wrappers for NameService contract.
package nameservice
import (
"errors"
"fmt"
"github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
@ -13,8 +15,37 @@ import (
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"math/big"
"unicode/utf8"
)
// RegisterDomainEvent represents "RegisterDomain" event emitted by the contract.
type RegisterDomainEvent struct {
Name string
}
// AddRecordEvent represents "AddRecord" event emitted by the contract.
type AddRecordEvent struct {
Name string
Type *big.Int
}
// DeleteRecordEvent represents "DeleteRecord" event emitted by the contract.
type DeleteRecordEvent struct {
Name string
Type *big.Int
}
// DeleteRecordsEvent represents "DeleteRecords" event emitted by the contract.
type DeleteRecordsEvent struct {
Name string
Type *big.Int
}
// DeleteDomainEvent represents "DeleteDomain" event emitted by the contract.
type DeleteDomainEvent struct {
Name string
}
// Invoker is used by ContractReader to call various safe methods.
type Invoker interface {
nep11.Invoker
@ -60,6 +91,20 @@ func New(actor Actor, hash util.Uint160) *Contract {
return &Contract{ContractReader{nep11ndt.NonDivisibleReader, actor, hash}, nep11ndt.BaseWriter, actor, hash}
}
// GetAllRecords invokes `getAllRecords` method of contract.
func (c *ContractReader) GetAllRecords(name string) (uuid.UUID, result.Iterator, error) {
return unwrap.SessionIterator(c.invoker.Call(c.hash, "getAllRecords", name))
}
// GetAllRecordsExpanded is similar to GetAllRecords (uses the same contract
// method), but can be useful if the server used doesn't support sessions and
// doesn't expand iterators. It creates a script that will get the specified
// number of result items from the iterator right in the VM and return them to
// you. It's only limited by VM stack and GAS available for RPC invocations.
func (c *ContractReader) GetAllRecordsExpanded(name string, _numOfIteratorItems int) ([]stackitem.Item, error) {
return unwrap.Array(c.invoker.CallAndExpandIterator(c.hash, "getAllRecords", _numOfIteratorItems, name))
}
// GetPrice invokes `getPrice` method of contract.
func (c *ContractReader) GetPrice() (*big.Int, error) {
return unwrap.BigInt(c.invoker.Call(c.hash, "getPrice"))
@ -121,6 +166,66 @@ func (c *Contract) AddRecordUnsigned(name string, typ *big.Int, data string) (*t
return c.actor.MakeUnsignedCall(c.hash, "addRecord", nil, name, typ, data)
}
// DeleteDomain creates a transaction invoking `deleteDomain` 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) DeleteDomain(name string) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "deleteDomain", name)
}
// DeleteDomainTransaction creates a transaction invoking `deleteDomain` method of the contract.
// This transaction is signed, but not sent to the network, instead it's
// returned to the caller.
func (c *Contract) DeleteDomainTransaction(name string) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "deleteDomain", name)
}
// DeleteDomainUnsigned creates a transaction invoking `deleteDomain` 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) DeleteDomainUnsigned(name string) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "deleteDomain", nil, name)
}
func (c *Contract) scriptForDeleteRecord(name string, typ *big.Int, data string) ([]byte, error) {
return smartcontract.CreateCallWithAssertScript(c.hash, "deleteRecord", name, typ, data)
}
// DeleteRecord creates a transaction invoking `deleteRecord` 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) DeleteRecord(name string, typ *big.Int, data string) (util.Uint256, uint32, error) {
script, err := c.scriptForDeleteRecord(name, typ, data)
if err != nil {
return util.Uint256{}, 0, err
}
return c.actor.SendRun(script)
}
// DeleteRecordTransaction creates a transaction invoking `deleteRecord` method of the contract.
// This transaction is signed, but not sent to the network, instead it's
// returned to the caller.
func (c *Contract) DeleteRecordTransaction(name string, typ *big.Int, data string) (*transaction.Transaction, error) {
script, err := c.scriptForDeleteRecord(name, typ, data)
if err != nil {
return nil, err
}
return c.actor.MakeRun(script)
}
// DeleteRecordUnsigned creates a transaction invoking `deleteRecord` 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) DeleteRecordUnsigned(name string, typ *big.Int, data string) (*transaction.Transaction, error) {
script, err := c.scriptForDeleteRecord(name, typ, data)
if err != nil {
return nil, err
}
return c.actor.MakeUnsignedRun(script, nil)
}
// DeleteRecords creates a transaction invoking `deleteRecords` 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.
@ -143,28 +248,6 @@ func (c *Contract) DeleteRecordsUnsigned(name string, typ *big.Int) (*transactio
return c.actor.MakeUnsignedCall(c.hash, "deleteRecords", nil, name, typ)
}
// GetAllRecords creates a transaction invoking `getAllRecords` method of the contract.
// This transaction is signed and immediately sent to the network.
// The values returned are its hash, ValidUntilBlock value and error if any.
func (c *Contract) GetAllRecords(name string) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "getAllRecords", name)
}
// GetAllRecordsTransaction creates a transaction invoking `getAllRecords` method of the contract.
// This transaction is signed, but not sent to the network, instead it's
// returned to the caller.
func (c *Contract) GetAllRecordsTransaction(name string) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "getAllRecords", name)
}
// GetAllRecordsUnsigned creates a transaction invoking `getAllRecords` method of the contract.
// This transaction is not signed, it's simply returned to the caller.
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
func (c *Contract) GetAllRecordsUnsigned(name string) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "getAllRecords", nil, name)
}
func (c *Contract) scriptForRegister(name string, owner util.Uint160, email string, refresh *big.Int, retry *big.Int, expire *big.Int, ttl *big.Int) ([]byte, error) {
return smartcontract.CreateCallWithAssertScript(c.hash, "register", name, owner, email, refresh, retry, expire, ttl)
}
@ -334,3 +417,326 @@ func (c *Contract) UpdateSOATransaction(name string, email string, refresh *big.
func (c *Contract) UpdateSOAUnsigned(name string, email string, refresh *big.Int, retry *big.Int, expire *big.Int, ttl *big.Int) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "updateSOA", nil, name, email, refresh, retry, expire, ttl)
}
// RegisterDomainEventsFromApplicationLog retrieves a set of all emitted events
// with "RegisterDomain" name from the provided [result.ApplicationLog].
func RegisterDomainEventsFromApplicationLog(log *result.ApplicationLog) ([]*RegisterDomainEvent, error) {
if log == nil {
return nil, errors.New("nil application log")
}
var res []*RegisterDomainEvent
for i, ex := range log.Executions {
for j, e := range ex.Events {
if e.Name != "RegisterDomain" {
continue
}
event := new(RegisterDomainEvent)
err := event.FromStackItem(e.Item)
if err != nil {
return nil, fmt.Errorf("failed to deserialize RegisterDomainEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
}
res = append(res, event)
}
}
return res, nil
}
// FromStackItem converts provided [stackitem.Array] to RegisterDomainEvent or
// returns an error if it's not possible to do to so.
func (e *RegisterDomainEvent) FromStackItem(item *stackitem.Array) error {
if item == nil {
return errors.New("nil item")
}
arr, ok := item.Value().([]stackitem.Item)
if !ok {
return errors.New("not an array")
}
if len(arr) != 1 {
return errors.New("wrong number of structure elements")
}
var (
index = -1
err error
)
index++
e.Name, err = func(item stackitem.Item) (string, error) {
b, err := item.TryBytes()
if err != nil {
return "", err
}
if !utf8.Valid(b) {
return "", errors.New("not a UTF-8 string")
}
return string(b), nil
}(arr[index])
if err != nil {
return fmt.Errorf("field Name: %w", err)
}
return nil
}
// AddRecordEventsFromApplicationLog retrieves a set of all emitted events
// with "AddRecord" name from the provided [result.ApplicationLog].
func AddRecordEventsFromApplicationLog(log *result.ApplicationLog) ([]*AddRecordEvent, error) {
if log == nil {
return nil, errors.New("nil application log")
}
var res []*AddRecordEvent
for i, ex := range log.Executions {
for j, e := range ex.Events {
if e.Name != "AddRecord" {
continue
}
event := new(AddRecordEvent)
err := event.FromStackItem(e.Item)
if err != nil {
return nil, fmt.Errorf("failed to deserialize AddRecordEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
}
res = append(res, event)
}
}
return res, nil
}
// FromStackItem converts provided [stackitem.Array] to AddRecordEvent or
// returns an error if it's not possible to do to so.
func (e *AddRecordEvent) FromStackItem(item *stackitem.Array) error {
if item == nil {
return errors.New("nil item")
}
arr, ok := item.Value().([]stackitem.Item)
if !ok {
return errors.New("not an array")
}
if len(arr) != 2 {
return errors.New("wrong number of structure elements")
}
var (
index = -1
err error
)
index++
e.Name, err = func(item stackitem.Item) (string, error) {
b, err := item.TryBytes()
if err != nil {
return "", err
}
if !utf8.Valid(b) {
return "", errors.New("not a UTF-8 string")
}
return string(b), nil
}(arr[index])
if err != nil {
return fmt.Errorf("field Name: %w", err)
}
index++
e.Type, err = arr[index].TryInteger()
if err != nil {
return fmt.Errorf("field Type: %w", err)
}
return nil
}
// DeleteRecordEventsFromApplicationLog retrieves a set of all emitted events
// with "DeleteRecord" name from the provided [result.ApplicationLog].
func DeleteRecordEventsFromApplicationLog(log *result.ApplicationLog) ([]*DeleteRecordEvent, error) {
if log == nil {
return nil, errors.New("nil application log")
}
var res []*DeleteRecordEvent
for i, ex := range log.Executions {
for j, e := range ex.Events {
if e.Name != "DeleteRecord" {
continue
}
event := new(DeleteRecordEvent)
err := event.FromStackItem(e.Item)
if err != nil {
return nil, fmt.Errorf("failed to deserialize DeleteRecordEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
}
res = append(res, event)
}
}
return res, nil
}
// FromStackItem converts provided [stackitem.Array] to DeleteRecordEvent or
// returns an error if it's not possible to do to so.
func (e *DeleteRecordEvent) FromStackItem(item *stackitem.Array) error {
if item == nil {
return errors.New("nil item")
}
arr, ok := item.Value().([]stackitem.Item)
if !ok {
return errors.New("not an array")
}
if len(arr) != 2 {
return errors.New("wrong number of structure elements")
}
var (
index = -1
err error
)
index++
e.Name, err = func(item stackitem.Item) (string, error) {
b, err := item.TryBytes()
if err != nil {
return "", err
}
if !utf8.Valid(b) {
return "", errors.New("not a UTF-8 string")
}
return string(b), nil
}(arr[index])
if err != nil {
return fmt.Errorf("field Name: %w", err)
}
index++
e.Type, err = arr[index].TryInteger()
if err != nil {
return fmt.Errorf("field Type: %w", err)
}
return nil
}
// DeleteRecordsEventsFromApplicationLog retrieves a set of all emitted events
// with "DeleteRecords" name from the provided [result.ApplicationLog].
func DeleteRecordsEventsFromApplicationLog(log *result.ApplicationLog) ([]*DeleteRecordsEvent, error) {
if log == nil {
return nil, errors.New("nil application log")
}
var res []*DeleteRecordsEvent
for i, ex := range log.Executions {
for j, e := range ex.Events {
if e.Name != "DeleteRecords" {
continue
}
event := new(DeleteRecordsEvent)
err := event.FromStackItem(e.Item)
if err != nil {
return nil, fmt.Errorf("failed to deserialize DeleteRecordsEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
}
res = append(res, event)
}
}
return res, nil
}
// FromStackItem converts provided [stackitem.Array] to DeleteRecordsEvent or
// returns an error if it's not possible to do to so.
func (e *DeleteRecordsEvent) FromStackItem(item *stackitem.Array) error {
if item == nil {
return errors.New("nil item")
}
arr, ok := item.Value().([]stackitem.Item)
if !ok {
return errors.New("not an array")
}
if len(arr) != 2 {
return errors.New("wrong number of structure elements")
}
var (
index = -1
err error
)
index++
e.Name, err = func(item stackitem.Item) (string, error) {
b, err := item.TryBytes()
if err != nil {
return "", err
}
if !utf8.Valid(b) {
return "", errors.New("not a UTF-8 string")
}
return string(b), nil
}(arr[index])
if err != nil {
return fmt.Errorf("field Name: %w", err)
}
index++
e.Type, err = arr[index].TryInteger()
if err != nil {
return fmt.Errorf("field Type: %w", err)
}
return nil
}
// DeleteDomainEventsFromApplicationLog retrieves a set of all emitted events
// with "DeleteDomain" name from the provided [result.ApplicationLog].
func DeleteDomainEventsFromApplicationLog(log *result.ApplicationLog) ([]*DeleteDomainEvent, error) {
if log == nil {
return nil, errors.New("nil application log")
}
var res []*DeleteDomainEvent
for i, ex := range log.Executions {
for j, e := range ex.Events {
if e.Name != "DeleteDomain" {
continue
}
event := new(DeleteDomainEvent)
err := event.FromStackItem(e.Item)
if err != nil {
return nil, fmt.Errorf("failed to deserialize DeleteDomainEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
}
res = append(res, event)
}
}
return res, nil
}
// FromStackItem converts provided [stackitem.Array] to DeleteDomainEvent or
// returns an error if it's not possible to do to so.
func (e *DeleteDomainEvent) FromStackItem(item *stackitem.Array) error {
if item == nil {
return errors.New("nil item")
}
arr, ok := item.Value().([]stackitem.Item)
if !ok {
return errors.New("not an array")
}
if len(arr) != 1 {
return errors.New("wrong number of structure elements")
}
var (
index = -1
err error
)
index++
e.Name, err = func(item stackitem.Item) (string, error) {
b, err := item.TryBytes()
if err != nil {
return "", err
}
if !utf8.Valid(b) {
return "", errors.New("not a UTF-8 string")
}
return string(b), nil
}(arr[index])
if err != nil {
return fmt.Errorf("field Name: %w", err)
}
return nil
}

View file

@ -1,9 +1,10 @@
// Package ape contains RPC wrappers for APE contract.
//
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
// Package ape contains RPC wrappers for APE contract.
package ape
import (
"github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
@ -15,6 +16,9 @@ import (
// Invoker is used by ContractReader to call various safe methods.
type Invoker interface {
Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error)
CallAndExpandIterator(contract util.Uint160, method string, maxItems int, params ...any) (*result.Invoke, error)
TerminateSession(sessionID uuid.UUID) error
TraverseIterator(sessionID uuid.UUID, iterator *result.Iterator, num int) ([]stackitem.Item, error)
}
// Actor is used by Contract to call state-changing methods.
@ -62,6 +66,34 @@ func (c *ContractReader) GetChain(entity *big.Int, entityName string, name []byt
return unwrap.Bytes(c.invoker.Call(c.hash, "getChain", entity, entityName, name))
}
// IteratorChainsByPrefix invokes `iteratorChainsByPrefix` method of contract.
func (c *ContractReader) IteratorChainsByPrefix(entity *big.Int, entityName string, prefix []byte) (uuid.UUID, result.Iterator, error) {
return unwrap.SessionIterator(c.invoker.Call(c.hash, "iteratorChainsByPrefix", entity, entityName, prefix))
}
// IteratorChainsByPrefixExpanded is similar to IteratorChainsByPrefix (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) IteratorChainsByPrefixExpanded(entity *big.Int, entityName string, prefix []byte, _numOfIteratorItems int) ([]stackitem.Item, error) {
return unwrap.Array(c.invoker.CallAndExpandIterator(c.hash, "iteratorChainsByPrefix", _numOfIteratorItems, entity, entityName, prefix))
}
// ListChainNames invokes `listChainNames` method of contract.
func (c *ContractReader) ListChainNames(entity *big.Int, entityName string) (uuid.UUID, result.Iterator, error) {
return unwrap.SessionIterator(c.invoker.Call(c.hash, "listChainNames", entity, entityName))
}
// ListChainNamesExpanded is similar to ListChainNames (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) ListChainNamesExpanded(entity *big.Int, entityName string, _numOfIteratorItems int) ([]stackitem.Item, error) {
return unwrap.Array(c.invoker.CallAndExpandIterator(c.hash, "listChainNames", _numOfIteratorItems, entity, entityName))
}
// ListChains invokes `listChains` method of contract.
func (c *ContractReader) ListChains(namespace string, container string, name []byte) ([]stackitem.Item, error) {
return unwrap.Array(c.invoker.Call(c.hash, "listChains", namespace, container, name))
@ -72,6 +104,25 @@ func (c *ContractReader) ListChainsByPrefix(entity *big.Int, entityName string,
return unwrap.Array(c.invoker.Call(c.hash, "listChainsByPrefix", entity, entityName, prefix))
}
// ListTargets invokes `listTargets` method of contract.
func (c *ContractReader) ListTargets(entity *big.Int) (uuid.UUID, result.Iterator, error) {
return unwrap.SessionIterator(c.invoker.Call(c.hash, "listTargets", entity))
}
// ListTargetsExpanded is similar to ListTargets (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) ListTargetsExpanded(entity *big.Int, _numOfIteratorItems int) ([]stackitem.Item, error) {
return unwrap.Array(c.invoker.CallAndExpandIterator(c.hash, "listTargets", _numOfIteratorItems, entity))
}
// Version invokes `version` method of contract.
func (c *ContractReader) Version() (*big.Int, error) {
return unwrap.BigInt(c.invoker.Call(c.hash, "version"))
}
// AddChain creates a transaction invoking `addChain` 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.
@ -159,3 +210,25 @@ func (c *Contract) SetAdminTransaction(addr util.Uint160) (*transaction.Transact
func (c *Contract) SetAdminUnsigned(addr util.Uint160) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "setAdmin", nil, addr)
}
// Update creates a transaction invoking `update` 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) Update(script []byte, manifest []byte, data any) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "update", script, manifest, data)
}
// UpdateTransaction creates a transaction invoking `update` method of the contract.
// This transaction is signed, but not sent to the network, instead it's
// returned to the caller.
func (c *Contract) UpdateTransaction(script []byte, manifest []byte, data any) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "update", script, manifest, data)
}
// UpdateUnsigned creates a transaction invoking `update` 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) UpdateUnsigned(script []byte, manifest []byte, data any) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "update", nil, script, manifest, data)
}

View file

@ -1,6 +1,6 @@
// Package multisignatureprocessing contains RPC wrappers for Multi Signature Processing contract.
//
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
// Package multisignatureprocessing contains RPC wrappers for Multi Signature Processing contract.
package multisignatureprocessing
import (

View file

@ -1,6 +1,6 @@
// Package notaryproxy contains RPC wrappers for Notary Proxy contract.
//
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
// Package notaryproxy contains RPC wrappers for Notary Proxy contract.
package notaryproxy
import (
@ -61,6 +61,50 @@ func (c *ContractReader) Version() (*big.Int, error) {
return unwrap.BigInt(c.invoker.Call(c.hash, "version"))
}
// AddAccount creates a transaction invoking `addAccount` 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) AddAccount(addr util.Uint160) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "addAccount", addr)
}
// AddAccountTransaction creates a transaction invoking `addAccount` method of the contract.
// This transaction is signed, but not sent to the network, instead it's
// returned to the caller.
func (c *Contract) AddAccountTransaction(addr util.Uint160) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "addAccount", addr)
}
// AddAccountUnsigned creates a transaction invoking `addAccount` 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) AddAccountUnsigned(addr util.Uint160) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "addAccount", nil, addr)
}
// RemoveAccount creates a transaction invoking `removeAccount` 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) RemoveAccount(addr util.Uint160) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "removeAccount", addr)
}
// RemoveAccountTransaction creates a transaction invoking `removeAccount` method of the contract.
// This transaction is signed, but not sent to the network, instead it's
// returned to the caller.
func (c *Contract) RemoveAccountTransaction(addr util.Uint160) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "removeAccount", addr)
}
// RemoveAccountUnsigned creates a transaction invoking `removeAccount` 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) RemoveAccountUnsigned(addr util.Uint160) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "removeAccount", nil, addr)
}
// Update creates a transaction invoking `update` 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.

View file

@ -47,7 +47,7 @@ func newAlphabetInvoker(t *testing.T) (*neotest.Executor, *neotest.ContractInvok
container.AliasFeeKey, int64(containerAliasFee))
deployBalanceContract(t, e, ctrNetmap.Hash, ctrContainer.Hash)
deployContainerContract(t, e, ctrNetmap.Hash, ctrBalance.Hash, ctrNNS.Hash)
deployProxyContract(t, e, ctrNetmap.Hash)
deployProxyContract(t, e)
hash := deployAlphabetContract(t, e, ctrNetmap.Hash, ctrProxy.Hash, "Az", 0, 1)
alphabet := getAlphabetAcc(t, e)

View file

@ -28,13 +28,17 @@ const (
containerAliasFee = 0o_0050_0000
)
const (
containerRootDomain = "frostfs"
)
func deployContainerContract(t *testing.T, e *neotest.Executor, addrNetmap, addrBalance, addrNNS util.Uint160) util.Uint160 {
args := make([]any, 5)
args[0] = addrNetmap
args[1] = addrBalance
args[2] = util.Uint160{} // not needed for now
args[3] = addrNNS
args[4] = "frostfs"
args[4] = containerRootDomain
c := neotest.CompileFile(t, e.CommitteeHash, containerPath, path.Join(containerPath, "config.yml"))
e.DeployContract(t, c, args)
@ -165,6 +169,14 @@ func checkContainerList(t *testing.T, c *neotest.ContractInvoker, expected [][]b
})
}
const (
// default SOA record field values
defaultRefresh = 3600 // 1 hour
defaultRetry = 600 // 10 min
defaultExpire = 3600 * 24 * 365 * 10 // 10 years
defaultTTL = 3600 // 1 hour
)
func TestContainerPut(t *testing.T) {
c, cBal, _ := newContainerInvoker(t)
@ -199,14 +211,14 @@ func TestContainerPut(t *testing.T) {
stackitem.NewByteArray([]byte(base58.Encode(cnt.id[:]))),
})
cNNS := c.CommitteeInvoker(nnsHash)
cNNS.Invoke(t, expected, "resolve", "mycnt.frostfs", int64(nns.TXT))
cNNS.Invoke(t, expected, "resolve", "mycnt."+containerRootDomain, int64(nns.TXT))
t.Run("name is already taken", func(t *testing.T) {
c.InvokeFail(t, "name is already taken", "putNamed", putArgs...)
})
c.Invoke(t, stackitem.Null{}, "delete", cnt.id[:], cnt.sig, cnt.pub, cnt.token)
cNNS.Invoke(t, stackitem.Null{}, "resolve", "mycnt.frostfs", int64(nns.TXT))
cNNS.Invoke(t, stackitem.Null{}, "resolve", "mycnt."+containerRootDomain, int64(nns.TXT))
t.Run("register in advance", func(t *testing.T) {
cnt.value[len(cnt.value)-1] = 10
@ -233,6 +245,73 @@ func TestContainerPut(t *testing.T) {
})
})
t.Run("test setAdmin", func(t *testing.T) {
ctrNNS := neotest.CompileFile(t, c.CommitteeHash, nnsPath, path.Join(nnsPath, "config.yml"))
nnsInv := c.NewInvoker(ctrNNS.Hash, acc)
containerInv := c.WithSigners(c.Committee, acc)
nnsInv.InvokeFail(t, "not witnessed by admin", "addRecord", containerRootDomain, int64(nns.TXT), nns.Cnametgt+"=animals")
containerInv.Invoke(t, stackitem.Null{}, "setAdmin", acc.ScriptHash())
nnsInv.Invoke(t, stackitem.Null{}, "addRecord", containerRootDomain, int64(nns.TXT), nns.Cnametgt+"=animals")
})
t.Run("create global domain", func(t *testing.T) {
ctrNNS := neotest.CompileFile(t, c.CommitteeHash, nnsPath, path.Join(nnsPath, "config.yml"))
nnsHash := ctrNNS.Hash
cNNS := c.CommitteeInvoker(nnsHash)
cNNS.Invoke(t, true, "register",
"animals", c.CommitteeHash,
"whateveriwant@world.com", int64(defaultRefresh), int64(defaultRetry), int64(defaultExpire), int64(defaultTTL))
cNNS.Invoke(t, true, "register",
"ns", c.CommitteeHash,
"whateveriwant@world.com", int64(defaultRefresh), int64(defaultRetry), int64(defaultExpire), int64(0))
cNNS.Invoke(t, true, "register",
"poland.ns", c.CommitteeHash,
"whateveriwant@world.com", int64(defaultRefresh), int64(defaultRetry), int64(defaultExpire), int64(0))
cNNS.Invoke(t, true, "register",
"sweden.ns", c.CommitteeHash,
"whateveriwant@world.com", int64(defaultRefresh), int64(defaultRetry), int64(defaultExpire), int64(defaultExpire))
cNNS.Invoke(t, stackitem.Null{}, "addRecord",
"poland.ns", int64(nns.TXT), nns.Cnametgt+"=animals")
cNNS.Invoke(t, stackitem.Null{}, "addRecord", "poland.ns", int64(nns.TXT), "random-record")
cNNS.Invoke(t, stackitem.Null{}, "addRecord", "poland.ns", int64(nns.TXT), "ne-qqq=random-record2")
cNNS.Invoke(t, stackitem.Null{}, "addRecord", "sweden.ns", int64(nns.TXT), nns.Cnametgt+"=animals")
balanceMint(t, cBal, acc, (containerFee+containerAliasFee)*5, []byte{})
cNNS.Invoke(t, true, "isAvailable", "bober.animals")
putArgs := []any{cnt.value, cnt.sig, cnt.pub, cnt.token, "bober", "poland.ns"}
c3 := c.WithSigners(c.Committee, acc)
c3.Invoke(t, stackitem.Null{}, "putNamed", putArgs...)
cNNS.Invoke(t, false, "isAvailable", "bober.animals")
putArgs = []any{cnt.value, cnt.sig, cnt.pub, cnt.token, "bober", "sweden.ns"}
c3.InvokeFail(t, "global domain is already taken", "putNamed", putArgs...)
cNNS.InvokeFail(t, "global domain is already taken", "isAvailable", "bober.poland.ns")
cnt2 := dummyContainer(acc)
cNNS.Invoke(t, true, "isAvailable", "uzik.poland.ns")
putArgs = []any{cnt2.value, cnt2.sig, cnt2.pub, cnt2.token, "uzik", "poland.ns"}
c3.Invoke(t, stackitem.Null{}, "putNamed", putArgs...)
c3.Invoke(t, stackitem.Null{}, "delete", cnt.id[:], cnt.sig, cnt.pub, cnt.token)
cNNS.Invoke(t, true, "isAvailable", "bober.animals")
cNNS.Invoke(t, false, "isAvailable", "bober.poland.ns")
cNNS.InvokeFail(t, "global domain is already taken", "isAvailable", "uzik.poland.ns")
cNNS.Invoke(t, false, "isAvailable", "uzik.animals")
records := stackitem.NewArray([]stackitem.Item{stackitem.NewBuffer([]byte("uzik.poland.ns")), stackitem.NewByteArray([]byte(base58.Encode(cnt2.id[:])))})
cNNS.Invoke(t, records, "resolve", "uzik.animals", int64(nns.TXT))
})
t.Run("gas costs are the same for all containers in block", func(t *testing.T) {
const (
containerPerBlock = 512

View file

@ -11,6 +11,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet"
@ -62,7 +63,7 @@ func frostfsidRPCClient(t *testing.T, address string, contractHash util.Uint160,
acc = accs[0]
}
cli, err := client.New(rpcCli, acc, contractHash, nil)
cli, err := client.New(rpcCli, acc, contractHash, client.Options{})
require.NoError(t, err)
return cli, rpcCli
@ -115,7 +116,7 @@ func TestFrostFSID_Client_SubjectManagement(t *testing.T) {
subjLogin := "subj-login"
iamPathKV := "iam/path"
ffsid.a.await(ffsid.cli.CreateSubject(subjKey.PublicKey()))
ffsid.a.await(ffsid.cli.CreateSubject(defaultNamespace, subjKey.PublicKey()))
ffsid.a.await(ffsid.cli.SetSubjectName(subjAddr, subjLogin))
ffsid.a.await(ffsid.cli.SetSubjectKV(subjAddr, client.IAMPathKey, iamPathKV))
ffsid.a.await(ffsid.cli.AddSubjectKey(subjAddr, extraKey.PublicKey()))
@ -157,25 +158,22 @@ func TestFrostFSID_Client_NamespaceManagement(t *testing.T) {
ffsid, cancel := initFrostfsIFClientTest(t)
defer cancel()
subjKey, subjAddr := newKey(t)
ffsid.a.await(ffsid.cli.CreateSubject(subjKey.PublicKey()))
namespace := "namespace"
subjKey, subjAddr := newKey(t)
ffsid.a.await(ffsid.cli.CreateNamespace(namespace))
_, _, err := ffsid.cli.CreateNamespace(namespace)
require.ErrorContains(t, err, "already exists")
ffsid.a.await(ffsid.cli.CreateSubject(namespace, subjKey.PublicKey()))
ns, err := ffsid.cli.GetNamespace(namespace)
require.NoError(t, err)
require.Equal(t, namespace, ns.Name)
namespaces, err := ffsid.cli.ListNamespaces()
require.NoError(t, err)
require.ElementsMatch(t, []*client.Namespace{ns}, namespaces)
ffsid.a.await(ffsid.cli.AddSubjectToNamespace(subjAddr, namespace))
_, _, err = ffsid.cli.AddSubjectToNamespace(subjAddr, namespace)
require.ErrorContains(t, err, "already added")
require.ElementsMatch(t, []*client.Namespace{{}, ns}, namespaces)
subj, err := ffsid.cli.GetSubject(subjAddr)
require.NoError(t, err)
@ -185,26 +183,57 @@ func TestFrostFSID_Client_NamespaceManagement(t *testing.T) {
require.NoError(t, err)
require.ElementsMatch(t, []util.Uint160{subjAddr}, subjects)
ffsid.a.await(ffsid.cli.RemoveSubjectFromNamespace(subjAddr))
subj, err = ffsid.cli.GetSubject(subjAddr)
require.NoError(t, err)
require.Empty(t, subj.Namespace)
ffsid.a.await(ffsid.cli.DeleteSubject(subjAddr))
subjects, err = ffsid.cli.ListNamespaceSubjects(namespace)
require.NoError(t, err)
require.Empty(t, subjects)
}
func TestFrostFSID_Client_DefaultNamespace(t *testing.T) {
ffsid, cancel := initFrostfsIFClientTest(t)
defer cancel()
group := "group"
subjKey, subjAddr := newKey(t)
ffsid.a.await(ffsid.cli.CreateSubject(defaultNamespace, subjKey.PublicKey()))
groupID, err := ffsid.cli.ParseGroupID(ffsid.cli.Wait(ffsid.cli.CreateGroup(defaultNamespace, group)))
require.NoError(t, err)
ffsid.a.await(ffsid.cli.AddSubjectToGroup(subjAddr, groupID))
namespaces, err := ffsid.cli.ListNamespaces()
require.NoError(t, err)
require.EqualValues(t, []*client.Namespace{{}}, namespaces)
groups, err := ffsid.cli.ListGroups(defaultNamespace)
require.NoError(t, err)
require.EqualValues(t, []*client.Group{{ID: groupID, Name: group, Namespace: defaultNamespace}}, groups)
subjects, err := ffsid.cli.ListNamespaceSubjects(defaultNamespace)
require.NoError(t, err)
require.EqualValues(t, []util.Uint160{subjAddr}, subjects)
subjects, err = ffsid.cli.ListGroupSubjects(defaultNamespace, groupID)
require.NoError(t, err)
require.EqualValues(t, []util.Uint160{subjAddr}, subjects)
subject, err := ffsid.cli.GetSubjectExtended(subjAddr)
require.NoError(t, err)
require.True(t, subjKey.PublicKey().Equal(subject.PrimaryKey))
require.Equal(t, defaultNamespace, subject.Namespace)
require.EqualValues(t, []*client.Group{{ID: groupID, Name: group, Namespace: defaultNamespace}}, subject.Groups)
}
func TestFrostFSID_Client_GroupManagement(t *testing.T) {
ffsid, cancel := initFrostfsIFClientTest(t)
defer cancel()
subjKey, subjAddr := newKey(t)
ffsid.a.await(ffsid.cli.CreateSubject(subjKey.PublicKey()))
namespace := "namespace"
subjKey, subjAddr := newKey(t)
ffsid.a.await(ffsid.cli.CreateNamespace(namespace))
ffsid.a.await(ffsid.cli.CreateSubject(namespace, subjKey.PublicKey()))
groupName := "group"
groupID := int64(1)
@ -229,7 +258,6 @@ func TestFrostFSID_Client_GroupManagement(t *testing.T) {
require.NoError(t, err)
require.Zero(t, groupExt.SubjectsCount)
ffsid.a.await(ffsid.cli.AddSubjectToNamespace(subjAddr, namespace))
ffsid.a.await(ffsid.cli.AddSubjectToGroup(subjAddr, groupID))
subjExt, err := ffsid.cli.GetSubjectExtended(subjAddr)
@ -276,73 +304,17 @@ func TestFrostFSID_Client_Lists(t *testing.T) {
ffsid, cancel := initFrostfsIFClientTest(t)
defer cancel()
namespaces := []string{"empty-ns0", "ns1", "ns2", "ns3"}
namespaces := append([]string{defaultNamespace}, createNamespaces(t, ffsid, 3)...)
tx := ffsid.cli.StartTx()
for _, namespace := range namespaces {
err := tx.WrapCall(ffsid.cli.CreateNamespaceCall(namespace))
require.NoError(t, err)
}
ffsid.a.await(ffsid.cli.SendTx(tx))
subjects := createSubjectsInNS(t, ffsid, namespaces[1], 3)
subjects = append(subjects, createSubjectsInNS(t, ffsid, namespaces[2], 2)...)
subjects = append(subjects, createSubjectsInNS(t, ffsid, namespaces[3], 5)...)
subjects := make([]testSubject, 10)
tx = ffsid.cli.StartTx()
for i := range subjects {
subjects[i].key, subjects[i].addr = newKey(t)
err := tx.WrapCall(ffsid.cli.CreateSubjectCall(subjects[i].key.PublicKey()))
require.NoError(t, err)
}
ffsid.a.await(ffsid.cli.SendTx(tx))
groups := createGroupsInNS(t, ffsid, namespaces[0], 1)
groups = append(groups, createGroupsInNS(t, ffsid, namespaces[1], 2)...)
groups = append(groups, createGroupsInNS(t, ffsid, namespaces[2], 1)...)
groups = append(groups, createGroupsInNS(t, ffsid, namespaces[3], 2)...)
groups := []client.Group{
{
ID: 1,
Name: "empty-group0",
Namespace: namespaces[0],
},
{
ID: 2,
Name: "empty-group1",
Namespace: namespaces[1],
},
{
ID: 3,
Name: "group2",
Namespace: namespaces[1],
},
{
ID: 4,
Name: "group3",
Namespace: namespaces[2],
},
{
ID: 5,
Name: "group4",
Namespace: namespaces[3],
},
{
ID: 6,
Name: "group5",
Namespace: namespaces[3],
},
}
tx = ffsid.cli.StartTx()
for _, group := range groups[:3] {
err := tx.WrapCall(ffsid.cli.CreateGroupCall(group.Namespace, group.Name))
require.NoError(t, err)
}
ffsid.a.await(ffsid.cli.SendTx(tx))
// we split into two tx because of gas limit exceeded error
tx = ffsid.cli.StartTx()
for _, group := range groups[3:] {
err := tx.WrapCall(ffsid.cli.CreateGroupCall(group.Namespace, group.Name))
require.NoError(t, err)
}
ffsid.a.await(ffsid.cli.SendTx(tx))
addSubjectsToNamespaces(t, ffsid, namespaces, subjects)
addSubjectsToGroups(t, ffsid, groups, subjects)
nsList, err := ffsid.cli.ListNamespaces()
@ -370,29 +342,55 @@ func TestFrostFSID_Client_Lists(t *testing.T) {
checkGroupSubjects(t, ffsid.cli, groups[5], subjects, 8, 10)
}
func addSubjectsToNamespaces(t *testing.T, ffsid *testFrostFSIDClientInvoker, namespaces []string, subjects []testSubject) {
cli := ffsid.cli
func createSubjectsInNS(t *testing.T, ffsid *testFrostFSIDClientInvoker, ns string, count int) []testSubject {
subjects := make([]testSubject, count)
tx := cli.StartTx()
for _, subject := range subjects[:3] {
err := tx.WrapCall(cli.AddSubjectToNamespaceCall(subject.addr, namespaces[1]))
tx := ffsid.cli.StartTx()
for i := range subjects {
subjects[i].key, subjects[i].addr = newKey(t)
err := tx.WrapCall(ffsid.cli.CreateSubjectCall(ns, subjects[i].key.PublicKey()))
require.NoError(t, err)
}
ffsid.a.await(cli.SendTx(tx))
ffsid.a.await(ffsid.cli.SendTx(tx))
tx = cli.StartTx()
for _, subject := range subjects[3:5] {
err := tx.WrapCall(cli.AddSubjectToNamespaceCall(subject.addr, namespaces[2]))
return subjects
}
func createGroupsInNS(t *testing.T, ffsid *testFrostFSIDClientInvoker, ns string, count int) []client.Group {
groups := make([]client.Group, count)
tx := ffsid.cli.StartTx()
for i := range groups {
groups[i].Namespace = ns
groups[i].Name = ns + "/group" + strconv.Itoa(i+1)
err := tx.WrapCall(ffsid.cli.CreateGroupCall(ns, groups[i].Name))
require.NoError(t, err)
}
ffsid.a.await(cli.SendTx(tx))
res := ffsid.a.await(ffsid.cli.SendTx(tx))
tx = cli.StartTx()
for _, subject := range subjects[5:] {
err := tx.WrapCall(cli.AddSubjectToNamespaceCall(subject.addr, namespaces[3]))
ids, err := unwrap.ArrayOfBigInts(makeValidRes(stackitem.NewArray(res.Stack)), nil)
require.NoError(t, err)
for i, id := range ids {
groups[i].ID = id.Int64()
}
return groups
}
func createNamespaces(t *testing.T, ffsid *testFrostFSIDClientInvoker, count int) []string {
namespaces := make([]string, count)
tx := ffsid.cli.StartTx()
for i := range namespaces {
namespaces[i] = "ns" + strconv.Itoa(i+1)
err := tx.WrapCall(ffsid.cli.CreateNamespaceCall(namespaces[i]))
require.NoError(t, err)
}
ffsid.a.await(cli.SendTx(tx))
ffsid.a.await(ffsid.cli.SendTx(tx))
return namespaces
}
func addSubjectsToGroups(t *testing.T, ffsid *testFrostFSIDClientInvoker, groups []client.Group, subjects []testSubject) {
@ -457,14 +455,12 @@ func TestFrostFSID_Client_UseCaseWithS3GW(t *testing.T) {
tx := ffsid.cli.StartTx()
err := tx.WrapCall(ffsid.cli.CreateNamespaceCall(namespace))
require.NoError(t, err)
err = tx.WrapCall(ffsid.cli.CreateSubjectCall(dataUserKey.PublicKey()))
err = tx.WrapCall(ffsid.cli.CreateSubjectCall(namespace, dataUserKey.PublicKey()))
require.NoError(t, err)
err = tx.WrapCall(ffsid.cli.SetSubjectNameCall(dataUserAddr, login))
require.NoError(t, err)
err = tx.WrapCall(ffsid.cli.AddSubjectKeyCall(dataUserAddr, extraDataUserKey.PublicKey()))
require.NoError(t, err)
err = tx.WrapCall(ffsid.cli.AddSubjectToNamespaceCall(dataUserAddr, namespace))
require.NoError(t, err)
ffsid.a.await(ffsid.cli.SendTx(tx))
// s3-gw
@ -497,12 +493,10 @@ func TestFrostFSID_Client_UseCaseListNSSubjects(t *testing.T) {
for i := range subjects {
tx = ffsid.cli.StartTx()
subjects[i].key, subjects[i].addr = newKey(t)
err = tx.WrapCall(ffsid.cli.CreateSubjectCall(subjects[i].key.PublicKey()))
err = tx.WrapCall(ffsid.cli.CreateSubjectCall(namespace, subjects[i].key.PublicKey()))
require.NoError(t, err)
err = tx.WrapCall(ffsid.cli.SetSubjectNameCall(subjects[i].addr, "login"+strconv.Itoa(i)))
require.NoError(t, err)
err = tx.WrapCall(ffsid.cli.AddSubjectToNamespaceCall(subjects[i].addr, namespace))
require.NoError(t, err)
if i > len(subjects)/2 {
err = tx.WrapCall(ffsid.cli.AddSubjectToGroupCall(subjects[i].addr, groupID))
@ -522,6 +516,42 @@ func TestFrostFSID_Client_UseCaseListNSSubjects(t *testing.T) {
prettyPrintExtendedSubjects(res)
}
func TestFrostFSID_Client_GetSubjectByName(t *testing.T) {
ffsid, cancel := initFrostfsIFClientTest(t)
defer cancel()
key, addr := newKey(t)
subjName := "name"
tx := ffsid.cli.StartTx()
err := tx.WrapCall(ffsid.cli.CreateSubjectCall(defaultNamespace, key.PublicKey()))
require.NoError(t, err)
err = tx.WrapCall(ffsid.cli.SetSubjectNameCall(addr, subjName))
require.NoError(t, err)
ffsid.a.await(ffsid.cli.SendTx(tx))
subj, err := ffsid.cli.GetSubjectByName(defaultNamespace, subjName)
require.NoError(t, err)
require.Equal(t, subjName, subj.Name)
require.True(t, key.PublicKey().Equal(subj.PrimaryKey))
require.Equal(t, defaultNamespace, subj.Namespace)
}
func TestFrostFSID_Client_GetGroupByName(t *testing.T) {
ffsid, cancel := initFrostfsIFClientTest(t)
defer cancel()
groupName := "group"
actGroupID, err := ffsid.cli.ParseGroupID(ffsid.cli.Wait(ffsid.cli.CreateGroup(defaultNamespace, groupName)))
require.NoError(t, err)
group, err := ffsid.cli.GetGroupByName(defaultNamespace, groupName)
require.NoError(t, err)
require.Equal(t, actGroupID, group.ID)
require.Equal(t, defaultNamespace, group.Namespace)
require.Equal(t, groupName, group.Name)
}
func prettyPrintExtendedSubjects(subjects []*client.SubjectExtended) {
for _, subj := range subjects {
var sb strings.Builder

View file

@ -22,6 +22,8 @@ import (
const frostfsidPath = "../frostfsid"
const defaultNamespace = ""
const (
setAdminMethod = "setAdmin"
getAdminMethod = "getAdmin"
@ -39,13 +41,11 @@ const (
deleteSubjectKVMethod = "deleteSubjectKV"
deleteSubjectMethod = "deleteSubject"
createNamespaceMethod = "createNamespace"
getNamespaceMethod = "getNamespace"
getNamespaceExtendedMethod = "getNamespaceExtended"
listNamespacesMethod = "listNamespaces"
addSubjectToNamespaceMethod = "addSubjectToNamespace"
removeSubjectFromNamespaceMethod = "removeSubjectFromNamespace"
listNamespaceSubjectsMethod = "listNamespaceSubjects"
createNamespaceMethod = "createNamespace"
getNamespaceMethod = "getNamespace"
getNamespaceExtendedMethod = "getNamespaceExtended"
listNamespacesMethod = "listNamespaces"
listNamespaceSubjectsMethod = "listNamespaceSubjects"
createGroupMethod = "createGroup"
getGroupMethod = "getGroup"
@ -174,6 +174,26 @@ func checkOwner(t *testing.T, invoker *neotest.ContractInvoker, owner ...util.Ui
require.Equal(t, bs, owner[0].BytesBE())
}
func TestFrostFSID_DefaultNamespace(t *testing.T) {
f := newFrostFSIDInvoker(t)
subjKey, err := keys.NewPrivateKey()
require.NoError(t, err)
subjKeyAddr := subjKey.PublicKey().GetScriptHash()
invoker := f.OwnerInvoker()
groupID := int64(1)
groupName := "group"
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, subjKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Make(groupID), createGroupMethod, defaultNamespace, groupName)
invoker.Invoke(t, stackitem.Null{}, addSubjectToGroupMethod, subjKeyAddr, groupID)
invoker.Invoke(t, stackitem.Null{}, removeSubjectFromGroupMethod, subjKeyAddr, groupID)
invoker.Invoke(t, stackitem.Null{}, deleteGroupMethod, defaultNamespace, groupID)
invoker.Invoke(t, stackitem.Null{}, deleteSubjectMethod, subjKeyAddr)
}
func TestFrostFSID_SubjectManagement(t *testing.T) {
f := newFrostFSIDInvoker(t)
@ -184,9 +204,9 @@ func TestFrostFSID_SubjectManagement(t *testing.T) {
anonInvoker := f.AnonInvoker(t)
invoker := f.OwnerInvoker()
anonInvoker.InvokeFail(t, notWitnessedError, createSubjectMethod, subjKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, subjKey.PublicKey().Bytes())
invoker.InvokeFail(t, "already exists", createSubjectMethod, subjKey.PublicKey().Bytes())
anonInvoker.InvokeFail(t, notWitnessedError, createSubjectMethod, defaultNamespace, subjKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, subjKey.PublicKey().Bytes())
invoker.InvokeFail(t, "already exists", createSubjectMethod, defaultNamespace, subjKey.PublicKey().Bytes())
s, err := anonInvoker.TestInvoke(t, getSubjectMethod, subjKeyAddr)
require.NoError(t, err)
@ -218,6 +238,13 @@ func TestFrostFSID_SubjectManagement(t *testing.T) {
subj = parseSubject(t, s)
require.True(t, subjKey.PublicKey().Equal(&subj.PrimaryKey), "keys must be the same")
t.Run("with GetSubject", func(t *testing.T) {
s, err = anonInvoker.TestInvoke(t, getSubjectMethod, subjKey.PublicKey().GetScriptHash())
require.NoError(t, err)
subj = parseSubject(t, s)
require.True(t, subjKey.PublicKey().Equal(&subj.PrimaryKey), "keys must be the same")
})
t.Run("remove subject key", func(t *testing.T) {
anonInvoker.InvokeFail(t, notWitnessedError, removeSubjectKeyMethod, subjKeyAddr, subjNewKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, removeSubjectKeyMethod, subjKeyAddr, subjNewKey.PublicKey().Bytes())
@ -263,7 +290,7 @@ func TestFrostFSID_SubjectManagement(t *testing.T) {
newSubjKey, err := keys.NewPrivateKey()
require.NoError(t, err)
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, newSubjKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, newSubjKey.PublicKey().Bytes())
s, err = anonInvoker.TestInvoke(t, listSubjectsMethod)
require.NoError(t, err)
@ -293,20 +320,22 @@ func TestFrostFSIS_SubjectNameRelatedInvariants(t *testing.T) {
require.NoError(t, err)
subjKeyAddr2 := subjKey2.PublicKey().GetScriptHash()
subjName3 := "subj3"
subjKey3, err := keys.NewPrivateKey()
require.NoError(t, err)
subjKeyAddr3 := subjKey3.PublicKey().GetScriptHash()
invoker := f.OwnerInvoker()
ns1, ns2 := "ns1", "ns2"
// Create two subject (one of them with name)
// Create two namespace.
// Add these subjects to ns1
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, subjKey1.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjKeyAddr1, subjName1)
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, subjKey2.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, ns1)
invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, ns2)
invoker.Invoke(t, stackitem.Null{}, addSubjectToNamespaceMethod, subjKeyAddr1, ns1)
invoker.Invoke(t, stackitem.Null{}, addSubjectToNamespaceMethod, subjKeyAddr2, ns1)
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, ns1, subjKey1.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjKeyAddr1, subjName1)
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, ns1, subjKey2.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, ns2, subjKey3.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjKeyAddr3, subjName3)
// Check that we can find public key by name for subj1 (with name)
// and cannot find key for subj2 (without name)
@ -323,25 +352,17 @@ func TestFrostFSIS_SubjectNameRelatedInvariants(t *testing.T) {
// Check that we cannot set for second subject name that the first subject has already taken
invoker.InvokeFail(t, "not available", setSubjectNameMethod, subjKeyAddr2, subjName1)
// Check that we cannot move subject from one namespace to another
invoker.InvokeFail(t, "cannot be moved", addSubjectToNamespaceMethod, subjKeyAddr2, ns2)
// Check that we cannot find public key by name for subject that was removed from namespace
invoker.Invoke(t, stackitem.Null{}, removeSubjectFromNamespaceMethod, subjKeyAddr2)
// Check that we cannot find public key by name for subject that was removed
invoker.Invoke(t, stackitem.Null{}, deleteSubjectMethod, subjKeyAddr2)
s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns1, subjName2)
checkPublicKeyResult(t, s, err, nil)
// Check that we can find public key by name for subject in new namespace
invoker.Invoke(t, stackitem.Null{}, addSubjectToNamespaceMethod, subjKeyAddr2, ns2)
s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns2, subjName2)
checkPublicKeyResult(t, s, err, subjKey2)
// Check that subj2 can have the same name as subj1 if they belong to different namespaces
// Check that subj3 can have the same name as subj1 if they belong to different namespaces
// Also check that after subject renaming its key cannot be found by old name
invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjKeyAddr2, subjName1)
invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjKeyAddr3, subjName1)
s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns2, subjName1)
checkPublicKeyResult(t, s, err, subjKey2)
s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns2, subjName2)
checkPublicKeyResult(t, s, err, subjKey3)
s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns2, subjName3)
checkPublicKeyResult(t, s, err, nil)
}
@ -414,16 +435,12 @@ func TestFrostFSID_NamespaceManagement(t *testing.T) {
t.Run("add user to namespace", func(t *testing.T) {
subjKey, err := keys.NewPrivateKey()
require.NoError(t, err)
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, subjKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, ns.Name, subjKey.PublicKey().Bytes())
subjName := "name"
subjAddress := subjKey.PublicKey().GetScriptHash()
invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjAddress, subjName)
anonInvoker.InvokeFail(t, notWitnessedError, addSubjectToNamespaceMethod, subjAddress, namespace)
invoker.Invoke(t, stackitem.Null{}, addSubjectToNamespaceMethod, subjAddress, namespace)
invoker.InvokeFail(t, "already added", addSubjectToNamespaceMethod, subjAddress, namespace)
s, err := anonInvoker.TestInvoke(t, getSubjectMethod, subjAddress)
require.NoError(t, err)
subj := parseSubject(t, s)
@ -456,7 +473,7 @@ func TestFrostFSID_NamespaceManagement(t *testing.T) {
namespaces := parseNamespaces(t, readIteratorAll(s))
require.NoError(t, err)
require.ElementsMatch(t, namespaces, []Namespace{{Name: namespace}, {Name: namespace2}})
require.ElementsMatch(t, namespaces, []Namespace{{Name: defaultNamespace}, {Name: namespace}, {Name: namespace2}})
t.Run("find namespaces with some subjects", func(t *testing.T) {
for _, ns := range namespaces {
@ -468,21 +485,6 @@ func TestFrostFSID_NamespaceManagement(t *testing.T) {
require.Equal(t, namespace, nsExt.Name)
}
}
t.Run("remove subject from namespace", func(t *testing.T) {
anonInvoker.InvokeFail(t, notWitnessedError, removeSubjectFromNamespaceMethod, subjAddress)
invoker.Invoke(t, stackitem.Null{}, removeSubjectFromNamespaceMethod, subjAddress)
s, err := anonInvoker.TestInvoke(t, getSubjectMethod, subjAddress)
require.NoError(t, err)
subj := parseSubject(t, s)
require.Empty(t, subj.Namespace)
s, err = anonInvoker.TestInvoke(t, getNamespaceExtendedMethod, namespace)
require.NoError(t, err)
nsExt := parseNamespaceExtended(t, s.Pop().Item())
require.Zero(t, nsExt.SubjectsCount)
})
})
})
})
@ -516,10 +518,9 @@ func TestFrostFSID_GroupManagement(t *testing.T) {
t.Run("add subjects to group", func(t *testing.T) {
subjKey, err := keys.NewPrivateKey()
require.NoError(t, err)
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, subjKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, nsName, subjKey.PublicKey().Bytes())
subjAddress := subjKey.PublicKey().GetScriptHash()
invoker.Invoke(t, stackitem.Null{}, addSubjectToNamespaceMethod, subjAddress, nsName)
anonInvoker.InvokeFail(t, "not witnessed", addSubjectToGroupMethod, subjAddress, groupID)
invoker.Invoke(t, stackitem.Null{}, addSubjectToGroupMethod, subjAddress, groupID)
@ -546,8 +547,7 @@ func TestFrostFSID_GroupManagement(t *testing.T) {
for i := 0; i < subjectsCount; i++ {
subjKey, err := keys.NewPrivateKey()
require.NoError(t, err)
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, subjKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, addSubjectToNamespaceMethod, subjKey.PublicKey().GetScriptHash(), nsName)
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, nsName, subjKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, addSubjectToGroupMethod, subjKey.PublicKey().GetScriptHash(), groupID)
}
@ -611,6 +611,51 @@ 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)
subjFPrimaryKey, 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())
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, subjFPrimaryKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, deleteSubjectMethod, subjFPrimaryKey.PublicKey().GetScriptHash())
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, subjFPrimaryKey.PublicKey().Bytes())
}
func checkPublicKeyResult(t *testing.T, s *vm.Stack, err error, key *keys.PrivateKey) {
if key == nil {
require.ErrorContains(t, err, "not found")

View file

@ -10,8 +10,14 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
"github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require"
)
@ -19,8 +25,7 @@ const nnsPath = "../nns"
const msPerYear = 365 * 24 * time.Hour / time.Millisecond
func newNNSInvoker(t *testing.T, addRoot bool) *neotest.ContractInvoker {
e := newExecutor(t)
func deployNNS(t *testing.T, e *neotest.Executor, addRoot bool) *neotest.ContractInvoker {
ctr := neotest.CompileFile(t, e.CommitteeHash, nnsPath, path.Join(nnsPath, "config.yml"))
e.DeployContract(t, ctr, nil)
@ -35,6 +40,28 @@ func newNNSInvoker(t *testing.T, addRoot bool) *neotest.ContractInvoker {
return c
}
func newNNSInvoker(t *testing.T, addRoot bool) *neotest.ContractInvoker {
e := newExecutor(t)
return deployNNS(t, e, addRoot)
}
func newNNSInvokerWithFrostfsID(t *testing.T, addRoot bool) (*neotest.ContractInvoker, *neotest.ContractInvoker) {
e := newExecutor(t)
c := deployNNS(t, e, addRoot)
frostfdID := deployFrostFSIDContract(t, e, e.CommitteeHash)
c.Invoke(t, true, "register",
nns.FrostfsIDNNSName, c.CommitteeHash,
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
c.Invoke(t, stackitem.Null{}, "addRecord",
nns.FrostfsIDNNSName, int64(nns.TXT), frostfdID.StringLE())
c.Invoke(t, stackitem.Null{}, "addRecord",
nns.FrostfsIDNNSName, int64(nns.TXT), address.Uint160ToString(frostfdID))
return e.NewInvoker(c.Hash), e.CommitteeInvoker(frostfdID)
}
func TestNNSGeneric(t *testing.T) {
c := newNNSInvoker(t, false)
@ -51,6 +78,9 @@ func TestNNSRegisterTLD(t *testing.T) {
c.InvokeFail(t, "invalid domain name format", "register",
"0com", c.CommitteeHash,
"email@frostfs.info", refresh, retry, expire, ttl)
c.InvokeFail(t, "invalid fragment '0com'", "register",
"0com", c.CommitteeHash,
"email@frostfs.info", refresh, retry, expire, ttl)
acc := c.NewAccount(t)
cAcc := c.WithSigners(acc)
@ -58,6 +88,22 @@ func TestNNSRegisterTLD(t *testing.T) {
"com", acc.ScriptHash(),
"email@frostfs.info", refresh, retry, expire, ttl)
t.Run("size checks", func(t *testing.T) {
c.Invoke(t, true, "register",
"ns", c.CommitteeHash,
"email@frostfs.info", refresh, retry, expire, ttl)
c.InvokeFail(t, "invalid domain name format", "register",
"x", c.CommitteeHash,
"email@frostfs.info", refresh, retry, expire, ttl)
c.InvokeFail(t, "domain name too short", "register",
"x", c.CommitteeHash,
"email@frostfs.info", refresh, retry, expire, ttl)
c.InvokeFail(t, "domain name too long", "register",
getTooLongDomainName(255), c.CommitteeHash,
"email@frostfs.info", refresh, retry, expire, ttl)
})
c.Invoke(t, true, "register",
"com", c.CommitteeHash,
"email@frostfs.info", refresh, retry, expire, ttl)
@ -77,6 +123,18 @@ func TestNNSRegister(t *testing.T) {
"com", accTop.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl)
c1.Invoke(t, true, "register",
"aa.bb.zz", accTop.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl)
c1.InvokeFail(t, "TLD already exists", "register",
"zz", accTop.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl)
c1.Invoke(t, true, "register",
"xx.bb.zz", accTop.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl)
acc := c.NewAccount(t)
c2 := c.WithSigners(c.Committee, acc)
c2.InvokeFail(t, "not witnessed by admin", "register",
@ -88,26 +146,45 @@ func TestNNSRegister(t *testing.T) {
c3.InvokeFail(t, "invalid domain name format", "register",
"-testdomain.com", acc.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl)
c3.InvokeFail(t, "invalid fragment '-testdomain'", "register",
"-testdomain.com", acc.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl)
c3.InvokeFail(t, "invalid domain name format", "register",
"testdomain-.com", acc.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl)
c3.InvokeFail(t, "invalid fragment 'testdomain-'", "register",
"testdomain-.com", acc.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl)
c3.Invoke(t, true, "register",
"test-domain.com", acc.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl)
})
c3.Invoke(t, true, "register",
expected := stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray([]byte("testdomain.com")),
})
tx := c3.Invoke(t, true, "register",
"testdomain.com", acc.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl)
c.CheckTxNotificationEvent(t, tx, -1, state.NotificationEvent{ScriptHash: c.Hash, Name: "RegisterDomain", Item: expected})
b := c.TopBlock(t)
expected := stackitem.NewArray([]stackitem.Item{stackitem.NewBuffer(
expected = stackitem.NewArray([]stackitem.Item{stackitem.NewBuffer(
[]byte(fmt.Sprintf("testdomain.com myemail@frostfs.info %d %d %d %d %d",
b.Timestamp, refresh, retry, expire, ttl)))})
c.Invoke(t, expected, "getRecords", "testdomain.com", int64(nns.SOA))
cAcc := c.WithSigners(acc)
cAcc.Invoke(t, stackitem.Null{}, "addRecord",
expected = stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray([]byte("testdomain.com")),
stackitem.NewBigInteger(big.NewInt(int64(nns.TXT))),
})
tx = cAcc.Invoke(t, stackitem.Null{}, "addRecord",
"testdomain.com", int64(nns.TXT), "first TXT record")
c.CheckTxNotificationEvent(t, tx, 0, state.NotificationEvent{ScriptHash: c.Hash, Name: "AddRecord", Item: expected})
cAcc.InvokeFail(t, "record already exists", "addRecord",
"testdomain.com", int64(nns.TXT), "first TXT record")
cAcc.Invoke(t, stackitem.Null{}, "addRecord",
@ -119,14 +196,188 @@ func TestNNSRegister(t *testing.T) {
})
c.Invoke(t, expected, "getRecords", "testdomain.com", int64(nns.TXT))
cAcc.Invoke(t, stackitem.Null{}, "setRecord",
expected = stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray([]byte("testdomain.com")),
stackitem.NewBigInteger(big.NewInt(int64(nns.TXT))),
})
tx = cAcc.Invoke(t, stackitem.Null{}, "setRecord",
"testdomain.com", int64(nns.TXT), int64(0), "replaced first")
c.CheckTxNotificationEvent(t, tx, 0, state.NotificationEvent{ScriptHash: c.Hash, Name: "AddRecord", Item: expected})
expected = stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray([]byte("replaced first")),
stackitem.NewByteArray([]byte("second TXT record")),
})
c.Invoke(t, expected, "getRecords", "testdomain.com", int64(nns.TXT))
tx = cAcc.Invoke(t, stackitem.Null{}, "deleteRecords", "testdomain.com", int64(nns.TXT))
expected = stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray([]byte("testdomain.com")),
stackitem.NewBigInteger(big.NewInt(int64(nns.TXT))),
})
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))
cAcc.Invoke(t, stackitem.Null{}, "addRecord",
"testdomain.com", int64(nns.TXT), "rec1")
cAcc.Invoke(t, stackitem.Null{}, "addRecord",
"testdomain.com", int64(nns.TXT), "rec2")
cAcc.Invoke(t, stackitem.Null{}, "addRecord",
"testdomain.com", int64(nns.TXT), "rec3")
cAcc.Invoke(t, stackitem.Null{}, "addRecord",
"testdomain.com", int64(nns.TXT), "rec4")
cAcc.Invoke(t, false, "deleteRecord",
"testdomain.com", int64(nns.TXT), "rec9999")
cAcc.Invoke(t, true, "deleteRecord",
"testdomain.com", int64(nns.TXT), "rec1")
expected = stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray([]byte("rec2")),
stackitem.NewByteArray([]byte("rec3")),
stackitem.NewByteArray([]byte("rec4")),
})
c.Invoke(t, expected, "getRecords", "testdomain.com", int64(nns.TXT))
cAcc.Invoke(t, true, "deleteRecord",
"testdomain.com", int64(nns.TXT), "rec4")
cAcc.Invoke(t, true, "deleteRecord",
"testdomain.com", int64(nns.TXT), "rec2")
expected = stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray([]byte("rec3")),
})
c.Invoke(t, expected, "getRecords", "testdomain.com", int64(nns.TXT))
cAcc.Invoke(t, true, "deleteRecord",
"testdomain.com", int64(nns.TXT), "rec3")
c.Invoke(t, stackitem.Null{}, "getRecords", "testdomain.com", int64(nns.TXT))
tx = cAcc.Invoke(t, stackitem.Null{}, "deleteDomain", "testdomain.com")
expected = stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray([]byte("testdomain.com")),
stackitem.NewBigInteger(big.NewInt(int64(nns.CNAME))),
})
c.CheckTxNotificationEvent(t, tx, 0, state.NotificationEvent{ScriptHash: c.Hash, Name: "DeleteRecords", Item: expected})
expected = stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte("testdomain.com"))})
c.CheckTxNotificationEvent(t, tx, 4, state.NotificationEvent{ScriptHash: c.Hash, Name: "DeleteDomain", Item: expected})
c.InvokeFail(t, "token not found", "getRecords", "testdomain.com", int64(nns.SOA))
}
func TestDeleteDomain(t *testing.T) {
c := newNNSInvoker(t, false)
acc1 := c.NewAccount(t)
c1 := c.WithSigners(c.Committee, acc1)
acc2 := c.NewAccount(t)
c2 := c.WithSigners(c.Committee, acc2)
c1.Invoke(t, true, "register",
"com", acc1.ScriptHash(),
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
c1.Invoke(t, true, "register",
"testdomain.com", acc1.ScriptHash(),
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
c1.Invoke(t, true, "register",
"domik.testdomain.com", acc1.ScriptHash(),
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
c1.InvokeFail(t, "domain not found", "deleteDomain", "ru")
c1.InvokeFail(t, "can't delete TLD domain that has subdomains", "deleteDomain", "com")
c1.Invoke(t, stackitem.Null{}, "deleteDomain", "testdomain.com")
c1.Invoke(t, true, "register",
"aa.bb.zz", acc1.ScriptHash(),
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
c1.InvokeFail(t, "TLD already exists", "register",
"zz", acc1.ScriptHash(),
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
c1.Invoke(t, stackitem.Null{}, "deleteDomain", "aa.bb.zz")
c1.Invoke(t, true, "register",
"zz", acc1.ScriptHash(),
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
c1.Invoke(t, true, "register",
"cn", acc1.ScriptHash(),
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
c2.InvokeFail(t, "not witnessed by admin", "deleteDomain", "cn")
c1.Invoke(t, stackitem.Null{}, "deleteDomain", "cn")
c2.Invoke(t, true, "register",
"cn", acc2.ScriptHash(),
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
c1.Invoke(t, true, "register",
"gu.bububu.bubu.bu", c.CommitteeHash,
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
c1.Invoke(t, true, "register",
"buu.gu.bububu.bubu.bu", c.CommitteeHash,
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
c1.Invoke(t, true, "register",
"bubu.bu", c.CommitteeHash,
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
}
func TestGlobalDomain(t *testing.T) {
c := newNNSInvoker(t, false)
accTop := c.NewAccount(t)
refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104)
c1 := c.WithSigners(c.Committee, accTop)
c1.Invoke(t, true, "register",
"com", accTop.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl)
c1.Invoke(t, true, "register",
"testdomain.com", accTop.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl)
c1.Invoke(t, true, "register",
"globaldomain.com", accTop.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl)
c1.Invoke(t, true, "register",
"domik.testdomain.com", accTop.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl)
c1.Invoke(t, stackitem.Null{}, "addRecord",
"domik.testdomain.com", int64(nns.TXT), "CID")
c.Invoke(t, true, "isAvailable", "domik.globaldomain.com")
c1.Invoke(t, stackitem.Null{}, "addRecord",
"testdomain.com", int64(nns.TXT), nns.Cnametgt+"=globaldomain.com")
c.Invoke(t, true, "isAvailable", "dom.testdomain.com")
c1.Invoke(t, stackitem.Null{}, "addRecord",
"domik.testdomain.com", int64(nns.TXT), "random txt record")
c.Invoke(t, true, "isAvailable", "domik.globaldomain.com")
c1.Invoke(t, true, "register",
"dom.testdomain.com", accTop.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl)
c1.Invoke(t, stackitem.Null{}, "addRecord",
"dom.testdomain.com", int64(nns.TXT), "CID")
c.InvokeFail(t, "global domain is already taken", "isAvailable", "dom.testdomain.com")
}
func TestTLDRecord(t *testing.T) {
@ -153,11 +404,6 @@ func TestNNSRegisterMulti(t *testing.T) {
cBoth.Invoke(t, true, "register", args...)
c1 := c.WithSigners(acc)
t.Run("parent domain is missing", func(t *testing.T) {
msg := "one of the parent domains is not registered"
args[0] = "testnet.fs.neo.com"
c1.InvokeFail(t, msg, "register", args...)
})
args[0] = "fs.neo.com"
c1.Invoke(t, true, "register", args...)
@ -313,11 +559,44 @@ func TestNNSSetAdmin(t *testing.T) {
"testdomain.com", int64(nns.TXT), "will be added")
}
func TestNNS_Frostfsid(t *testing.T) {
nnsInv, f := newNNSInvokerWithFrostfsID(t, false)
acc, err := wallet.NewAccount()
require.NoError(t, err)
nnsUserInv := nnsInv.NewInvoker(nnsInv.Hash, newSigner(t, nnsInv, acc))
nnsUserInv.InvokeFail(t, "not witnessed by committee", "register",
"ddd", acc.ScriptHash(),
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
nnsUserInv.InvokeFail(t, "not witnessed by committee", "register",
"testdomain.kz", acc.ScriptHash(),
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
f.Invoke(t, stackitem.Null{}, createSubjectMethod, "", acc.PrivateKey().PublicKey().Bytes())
nnsUserInv.InvokeFail(t, "not witnessed by committee", "register",
"testdomain.kz", acc.ScriptHash(),
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
f.Invoke(t, stackitem.Null{}, setSubjectKVMethod, acc.ScriptHash(), nns.FrostfsIDNNSTLDPermissionKey, nns.FrostfsIDTLDRegistrationAllowed)
nnsUserInv.Invoke(t, true, "register",
"testdomain.kz", acc.ScriptHash(),
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
f.Invoke(t, stackitem.Null{}, deleteSubjectKVMethod, acc.ScriptHash(), nns.FrostfsIDNNSTLDPermissionKey)
nnsUserInv.InvokeFail(t, "not witnessed by committee", "register",
"testdomain.uz", acc.ScriptHash(),
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
}
func TestNNSIsAvailable(t *testing.T) {
c := newNNSInvoker(t, false)
c.Invoke(t, true, "isAvailable", "com")
c.InvokeFail(t, "TLD not found", "isAvailable", "domain.com")
refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104)
c.Invoke(t, true, "register",
@ -329,11 +608,36 @@ func TestNNSIsAvailable(t *testing.T) {
acc := c.NewAccount(t)
c1 := c.WithSigners(c.Committee, acc)
c1.Invoke(t, true, "register",
"domain.com", acc.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl)
c1.Invoke(t, true, "register",
"globaldomain.com", acc.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl)
c.Invoke(t, false, "isAvailable", "domain.com")
c.Invoke(t, true, "isAvailable", "dom.domain.com")
c1.Invoke(t, true, "register",
"dom.domain.com", acc.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl)
c.Invoke(t, false, "isAvailable", "dom.domain.com")
c.Invoke(t, true, "isAvailable", "dom.dom.domain.com")
c1.Invoke(t, stackitem.Null{}, "addRecord",
"dom.domain.com", int64(nns.TXT), nns.Cnametgt+"=globaldomain.com")
c.Invoke(t, true, "isAvailable", "dom.dom.domain.com")
c1.Invoke(t, true, "register",
"dom.globaldomain.com", acc.ScriptHash(),
"myemail@frostfs.info", refresh, retry, expire, ttl)
c.InvokeFail(t, "global domain is already taken", "isAvailable", "dom.dom.domain.com")
c.InvokeFail(t, "domain name too long", "isAvailable", getTooLongDomainName(255))
}
func TestNNSRenew(t *testing.T) {
@ -358,6 +662,7 @@ func TestNNSRenew(t *testing.T) {
{Key: stackitem.Make("expiration"), Value: stackitem.Make(ts)},
})
cAcc.Invoke(t, expected, "properties", "testdomain.com")
c.InvokeFail(t, "domain name too long", "renew", getTooLongDomainName(255))
}
func TestNNSResolve(t *testing.T) {
@ -376,3 +681,42 @@ func TestNNSResolve(t *testing.T) {
c.Invoke(t, records, "resolve", "test.com.", int64(nns.TXT))
c.InvokeFail(t, "invalid domain name format", "resolve", "test.com..", int64(nns.TXT))
}
func TestNNSAndProxy(t *testing.T) {
c := newNNSInvoker(t, false)
proxyHash := deployProxyContract(t, c.Executor)
proxySigner := neotest.NewContractSigner(proxyHash, func(*transaction.Transaction) []any { return nil })
g := c.NewInvoker(gas.Hash, c.Validator)
g.Invoke(t, true, "transfer",
c.Validator.ScriptHash(), proxyHash, 100_0000_0000, nil)
cc := c.WithSigners(proxySigner, c.Committee)
cc.Invoke(t, true, "register", "ns", proxyHash,
"ops@frostfs.info", 100, 100, 100, 100)
checkBalance := func(t *testing.T, owner util.Uint160, balance int64) {
s, err := cc.TestInvoke(t, "balanceOf", owner)
require.NoError(t, err)
require.Equal(t, 1, s.Len())
require.Equal(t, int64(balance), s.Pop().BigInt().Int64())
}
checkBalance(t, proxyHash, 1)
checkBalance(t, c.CommitteeHash, 0)
t.Run("ensure domain is not lost", func(t *testing.T) {
cc.Invoke(t, true, "transfer", c.CommitteeHash, "ns", nil)
checkBalance(t, proxyHash, 0)
checkBalance(t, c.CommitteeHash, 1)
})
}
func getTooLongDomainName(max int) (res string) {
for len(res) < max {
res += "dom."
}
res += "com"
return res
}

View file

@ -6,6 +6,7 @@ import (
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-contract/policy"
"github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
@ -31,6 +32,9 @@ func newPolicyInvoker(t *testing.T) *neotest.ContractInvoker {
func TestPolicy(t *testing.T) {
e := newPolicyInvoker(t)
checkChainsIteratorByPrefix(t, e, policy.Namespace, "mynamespace", "ingress", [][]byte{})
checkChainsIteratorByPrefix(t, e, policy.Container, "cnr1", "ingress", [][]byte{})
// Policies are opaque to the contract and are just raw bytes to store.
p1 := []byte("chain1")
p2 := []byte("chain2")
@ -38,23 +42,34 @@ func TestPolicy(t *testing.T) {
p33 := []byte("chain33")
e.Invoke(t, stackitem.Null{}, "addChain", policy.Namespace, "mynamespace", "ingress:123", p1)
checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")})
checkChains(t, e, "mynamespace", "", "ingress", [][]byte{p1})
checkChains(t, e, "mynamespace", "", "all", nil)
e.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule2", p2)
checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")})
checkTargets(t, e, policy.Container, [][]byte{[]byte("cnr1")})
checkChains(t, e, "mynamespace", "", "ingress", [][]byte{p1}) // Only namespace chains.
checkChains(t, e, "mynamespace", "cnr1", "ingress", [][]byte{p1, p2})
checkChains(t, e, "mynamespace", "cnr1", "all", nil) // No chains attached to 'all'.
checkChains(t, e, "mynamespace", "cnr2", "ingress", [][]byte{p1}) // Only namespace, no chains for the container.
e.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p3)
checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")})
checkTargets(t, e, policy.Container, [][]byte{[]byte("cnr1")})
checkChains(t, e, "mynamespace", "cnr1", "ingress", [][]byte{p1, p2, p3})
e.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p33)
checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")})
checkTargets(t, e, policy.Container, [][]byte{[]byte("cnr1")})
checkChain(t, e, policy.Container, "cnr1", "ingress:myrule3", p33)
checkChains(t, e, "mynamespace", "cnr1", "ingress", [][]byte{p1, p2, p33}) // Override chain.
checkChainsByPrefix(t, e, policy.Container, "cnr1", "", [][]byte{p2, p33})
checkChainsByPrefix(t, e, policy.IAM, "", "", nil)
checkChainKeys(t, e, policy.Container, "cnr1", []string{"ingress:myrule2", "ingress:myrule3"})
checkChainsIteratorByPrefix(t, e, policy.Container, "cnr1", "ingress:myrule3", [][]byte{p33})
checkChainsIteratorByPrefix(t, e, policy.Container, "cnr1", "ingress", [][]byte{p2, p33})
t.Run("removal", func(t *testing.T) {
t.Run("wrong name", func(t *testing.T) {
@ -69,6 +84,21 @@ func TestPolicy(t *testing.T) {
// Remove by prefix.
e.Invoke(t, stackitem.Null{}, "removeChainsByPrefix", policy.Container, "cnr1", "ingress")
checkChains(t, e, "mynamespace", "cnr1", "ingress", nil)
// Remove by prefix.
e.Invoke(t, stackitem.Null{}, "removeChainsByPrefix", policy.Container, "cnr1", "ingress")
checkChains(t, e, "mynamespace", "cnr1", "ingress", nil)
checkTargets(t, e, policy.Namespace, [][]byte{})
checkTargets(t, e, policy.Container, [][]byte{})
})
t.Run("add again after removal", func(t *testing.T) {
e.Invoke(t, stackitem.Null{}, "addChain", policy.Namespace, "mynamespace", "ingress:123", p1)
e.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p33)
checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")})
checkTargets(t, e, policy.Container, [][]byte{[]byte("cnr1")})
})
}
@ -103,6 +133,44 @@ func checkChainsByPrefix(t *testing.T, e *neotest.ContractInvoker, kind byte, en
checksChainsOnStack(t, s, expected)
}
func checkChainsIteratorByPrefix(t *testing.T, e *neotest.ContractInvoker, kind byte, entityName, prefix string, expected [][]byte) {
s, err := e.TestInvoke(t, "iteratorChainsByPrefix", kind, entityName, prefix)
require.NoError(t, err)
if s.Len() == 0 {
t.Fatal("Stack is empty")
}
iteratorItem := s.Pop().Value().(*storage.Iterator)
policys := iteratorToArray(iteratorItem)
require.Equal(t, len(expected), len(policys))
for i := range expected {
bytesPolicy, err := policys[i].TryBytes()
require.NoError(t, err)
require.Equal(t, expected[i], bytesPolicy)
}
}
func checkChainKeys(t *testing.T, e *neotest.ContractInvoker, kind byte, entityName string, expected []string) {
s, err := e.TestInvoke(t, "listChainNames", kind, entityName)
require.NoError(t, err)
if s.Len() == 0 {
t.Fatal("Stack is empty")
}
iteratorItem := s.Pop().Value().(*storage.Iterator)
policys := iteratorToArray(iteratorItem)
require.Equal(t, len(expected), len(policys))
for i := range expected {
bytesPolicy, err := policys[i].TryBytes()
require.NoError(t, err)
require.Equal(t, expected[i], string(bytesPolicy))
}
}
func checksChainsOnStack(t *testing.T, s *vm.Stack, expected [][]byte) {
require.Equal(t, 1, s.Len())
@ -125,3 +193,20 @@ func checkChain(t *testing.T, e *neotest.ContractInvoker, kind byte, entityName,
require.True(t, bytes.Equal(expected, s.Pop().Bytes()))
}
func checkTargets(t *testing.T, e *neotest.ContractInvoker, kind byte, expected [][]byte) {
s, err := e.TestInvoke(t, "listTargets", kind)
require.NoError(t, err)
require.NotEqual(t, 0, s.Len(), "stack is empty")
iteratorItem := s.Pop().Value().(*storage.Iterator)
targets := iteratorToArray(iteratorItem)
require.Equal(t, len(expected), len(targets))
for i := range expected {
bytesTargets, err := targets[i].TryBytes()
require.NoError(t, err)
require.Equal(t, expected[i], bytesTargets)
}
}

View file

@ -4,28 +4,41 @@ import (
"path"
"testing"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require"
)
const processingPath = "../processing"
func deployProcessingContract(t *testing.T, e *neotest.Executor, addrFrostFS util.Uint160) util.Uint160 {
func deployProcessingContract(t *testing.T, e *neotest.Executor) util.Uint160 {
c := neotest.CompileFile(t, e.CommitteeHash, processingPath, path.Join(processingPath, "config.yml"))
args := make([]any, 1)
args[0] = addrFrostFS
e.DeployContract(t, c, args)
e.DeployContract(t, c, nil)
return c.Hash
}
func newProcessingInvoker(t *testing.T) (*neotest.ContractInvoker, neotest.Signer) {
frostfsInvoker, irMultiAcc, _ := newFrostFSInvoker(t, 2)
hash := deployProcessingContract(t, frostfsInvoker.Executor, frostfsInvoker.Hash)
func newProcessingInvoker(t *testing.T) (*neotest.ContractInvoker, neotest.SingleSigner) {
e := newExecutor(t)
return frostfsInvoker.CommitteeInvoker(hash), irMultiAcc
acc, err := wallet.NewAccount()
require.NoError(t, err)
sig := neotest.NewSingleSigner(acc)
gasHash, err := e.Chain.GetNativeContractScriptHash(nativenames.Gas)
require.NoError(t, err)
vc := e.CommitteeInvoker(gasHash).WithSigners(e.Validator)
vc.Invoke(t, true, "transfer",
e.Validator.ScriptHash(), sig.ScriptHash(),
int64(10_0000_0000), nil)
hash := deployProcessingContract(t, e)
return e.CommitteeInvoker(hash), sig
}
func TestVerify_Processing(t *testing.T) {
@ -35,6 +48,6 @@ func TestVerify_Processing(t *testing.T) {
cIR := c.WithSigners(irMultiAcc)
cIR.Invoke(t, stackitem.NewBool(true), method)
c.Invoke(t, stackitem.NewBool(false), method)
cIR.Invoke(t, stackitem.NewBool(false), method)
c.Invoke(t, stackitem.NewBool(true), method)
}

View file

@ -4,45 +4,52 @@ import (
"path"
"testing"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)
const proxyPath = "../proxy"
func deployProxyContract(t *testing.T, e *neotest.Executor, addrNetmap util.Uint160) util.Uint160 {
args := make([]any, 1)
args[0] = addrNetmap
func deployProxyContract(t *testing.T, e *neotest.Executor) util.Uint160 {
c := neotest.CompileFile(t, e.CommitteeHash, proxyPath, path.Join(proxyPath, "config.yml"))
e.DeployContract(t, c, args)
e.DeployContract(t, c, []any{})
return c.Hash
}
func newProxyInvoker(t *testing.T) *neotest.ContractInvoker {
e := newExecutor(t)
ctrNetmap := neotest.CompileFile(t, e.CommitteeHash, netmapPath, path.Join(netmapPath, "config.yml"))
ctrBalance := neotest.CompileFile(t, e.CommitteeHash, balancePath, path.Join(balancePath, "config.yml"))
ctrContainer := neotest.CompileFile(t, e.CommitteeHash, containerPath, path.Join(containerPath, "config.yml"))
ctrProxy := neotest.CompileFile(t, e.CommitteeHash, proxyPath, path.Join(proxyPath, "config.yml"))
proxyHash := deployProxyContract(t, e)
deployNetmapContract(t, e, ctrBalance.Hash, ctrContainer.Hash)
deployProxyContract(t, e, ctrNetmap.Hash)
return e.CommitteeInvoker(ctrProxy.Hash)
return e.CommitteeInvoker(proxyHash)
}
func TestVerify(t *testing.T) {
e := newProxyInvoker(t)
acc := e.NewAccount(t)
const method = "verify"
gas := e.NewInvoker(e.NativeHash(t, nativenames.Gas), e.Validator)
gas.Invoke(t, true, "transfer", e.Validator.ScriptHash(), e.Hash, 100_0000_0000, nil)
e.Invoke(t, stackitem.NewBool(true), method)
s := neotest.NewContractSigner(e.Hash, func(*transaction.Transaction) []any { return nil })
t.Run("proxy + committee", func(t *testing.T) {
tx := e.PrepareInvocation(t, []byte{byte(opcode.RET)}, []neotest.Signer{s, e.Committee})
require.NoError(t, e.Chain.VerifyTx(tx))
})
t.Run("proxy + custom account", func(t *testing.T) {
t.Run("bad, only proxy", func(t *testing.T) {
tx := e.PrepareInvocation(t, []byte{byte(opcode.RET)}, []neotest.Signer{s, acc})
require.Error(t, e.Chain.VerifyTx(tx))
})
notAlphabet := e.NewAccount(t)
cNotAlphabet := e.WithSigners(notAlphabet)
e.Invoke(t, stackitem.Null{}, "addAccount", acc.ScriptHash())
cNotAlphabet.Invoke(t, stackitem.NewBool(false), method)
tx := e.PrepareInvocation(t, []byte{byte(opcode.RET)}, []neotest.Signer{s, acc})
require.NoError(t, e.Chain.VerifyTx(tx))
})
}