Skip to content

Commit 1d7bd26

Browse files
authored
Introduce private attributes of the storage nodes (#2580)
Closes #2280.
2 parents 1006609 + b87545a commit 1d7bd26

File tree

19 files changed

+960
-163
lines changed

19 files changed

+960
-163
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Changelog for NeoFS Node
55

66
### Added
77
- Policer's setting to the SN's application configuration (#2600)
8+
- Support of verified domains for the storage nodes (#2280)
89

910
### Fixed
1011
- `neofs-cli netmap netinfo` documentation (#2555)
@@ -28,6 +29,7 @@ Changelog for NeoFS Node
2829
- `neofs-lens` `inspect` object commands to `get` with `inspect` deprecation (#2548)
2930
- Update `tzhash` to `v1.7.1`
3031
- `github.com/panjf2000/ants/v2` to `v2.8.2`
32+
- `github.com/nspcc-dev/neofs-contract` to `v0.18.0` (#2580)
3133

3234
### Updating from v0.38.1
3335

cmd/neofs-adm/internal/modules/morph/n3client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ type clientContext struct {
5858
SentTxs []hashVUBPair
5959
}
6060

61-
func getN3Client(v *viper.Viper) (Client, error) {
61+
func getN3Client(v *viper.Viper) (*rpcclient.Client, error) {
6262
// number of opened connections
6363
// by neo-go client per one host
6464
const (

cmd/neofs-adm/internal/modules/morph/root.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ const (
4242
localDumpFlag = "local-dump"
4343
protoConfigPath = "protocol"
4444
walletAddressFlag = "wallet-address"
45+
domainFlag = "domain"
46+
neoAddressesFlag = "neo-addresses"
47+
publicKeysFlag = "public-keys"
48+
walletFlag = "wallet"
4549
)
4650

4751
var (
@@ -257,6 +261,39 @@ Values for unknown keys are added exactly the way they're provided, no conversio
257261
},
258262
RunE: listNetmapCandidatesNodes,
259263
}
264+
265+
verifiedNodesDomainCmd = &cobra.Command{
266+
Use: "verified-nodes-domain",
267+
Short: "Group of commands to work with verified domains for the storage nodes",
268+
Args: cobra.NoArgs,
269+
PersistentPreRun: func(cmd *cobra.Command, _ []string) {
270+
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
271+
_ = viper.BindPFlag(domainFlag, cmd.Flags().Lookup(domainFlag))
272+
},
273+
}
274+
275+
verifiedNodesDomainAccessListCmd = &cobra.Command{
276+
Use: "access-list",
277+
Short: "Get access list for the verified nodes' domain",
278+
Long: "List Neo addresses of the storage nodes that have access to use the specified verified domain.",
279+
Args: cobra.NoArgs,
280+
RunE: verifiedNodesDomainAccessList,
281+
}
282+
283+
verifiedNodesDomainSetAccessListCmd = &cobra.Command{
284+
Use: "set-access-list",
285+
Short: "Set access list for the verified nodes' domain",
286+
Long: "Set list of the storage nodes that have access to use the specified verified domain. " +
287+
"The list may be either Neo addresses or HEX-encoded public keys of the nodes.",
288+
Args: cobra.NoArgs,
289+
PreRun: func(cmd *cobra.Command, _ []string) {
290+
_ = viper.BindPFlag(walletFlag, cmd.Flags().Lookup(walletFlag))
291+
_ = viper.BindPFlag(walletAccountFlag, cmd.Flags().Lookup(walletAccountFlag))
292+
_ = viper.BindPFlag(publicKeysFlag, cmd.Flags().Lookup(publicKeysFlag))
293+
_ = viper.BindPFlag(neoAddressesFlag, cmd.Flags().Lookup(neoAddressesFlag))
294+
},
295+
RunE: verifiedNodesDomainSetAccessList,
296+
}
260297
)
261298

262299
func init() {
@@ -365,4 +402,31 @@ func init() {
365402

366403
RootCmd.AddCommand(netmapCandidatesCmd)
367404
netmapCandidatesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
405+
406+
cmd := verifiedNodesDomainAccessListCmd
407+
fs := cmd.Flags()
408+
fs.StringP(endpointFlag, "r", "", "NeoFS Sidechain RPC endpoint")
409+
_ = cmd.MarkFlagRequired(endpointFlag)
410+
fs.StringP(domainFlag, "d", "", "Verified domain of the storage nodes. Must be a valid NeoFS NNS domain (e.g. 'nodes.some-org.neofs')")
411+
_ = cmd.MarkFlagRequired(domainFlag)
412+
413+
verifiedNodesDomainCmd.AddCommand(cmd)
414+
415+
cmd = verifiedNodesDomainSetAccessListCmd
416+
fs = cmd.Flags()
417+
fs.StringP(walletFlag, "w", "", "Path to the Neo wallet file")
418+
_ = cmd.MarkFlagRequired(walletFlag)
419+
fs.StringP(walletAccountFlag, "a", "", "Optional Neo address of the wallet account for signing transactions. "+
420+
"If omitted, default change address from the wallet is used")
421+
fs.StringP(endpointFlag, "r", "", "NeoFS Sidechain RPC endpoint")
422+
_ = cmd.MarkFlagRequired(endpointFlag)
423+
fs.StringP(domainFlag, "d", "", "Verified domain of the storage nodes. Must be a valid NeoFS NNS domain (e.g. 'nodes.some-org.neofs')")
424+
_ = cmd.MarkFlagRequired(domainFlag)
425+
fs.StringSlice(neoAddressesFlag, nil, "Neo addresses resolved from public keys of the storage nodes")
426+
fs.StringSlice(publicKeysFlag, nil, "HEX-encoded public keys of the storage nodes")
427+
cmd.MarkFlagsMutuallyExclusive(publicKeysFlag, neoAddressesFlag)
428+
429+
verifiedNodesDomainCmd.AddCommand(cmd)
430+
431+
RootCmd.AddCommand(verifiedNodesDomainCmd)
368432
}
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
package morph
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/nspcc-dev/neo-go/cli/input"
9+
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
10+
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
11+
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
12+
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
13+
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
14+
"github.com/nspcc-dev/neo-go/pkg/util"
15+
"github.com/nspcc-dev/neo-go/pkg/wallet"
16+
nnsrpc "github.com/nspcc-dev/neofs-contract/rpc/nns"
17+
"github.com/spf13/cobra"
18+
"github.com/spf13/viper"
19+
)
20+
21+
// as described in NEP-18 Specification https://github.com/neo-project/proposals/pull/133
22+
const nnsNeoAddressTextRecordPrefix = "address="
23+
24+
func verifiedNodesDomainAccessList(cmd *cobra.Command, _ []string) error {
25+
vpr := viper.GetViper()
26+
27+
n3Client, err := getN3Client(vpr)
28+
if err != nil {
29+
return fmt.Errorf("open connection: %w", err)
30+
}
31+
32+
nnsContractAddr, err := nnsrpc.InferHash(n3Client)
33+
if err != nil {
34+
return fmt.Errorf("get NeoFS NNS contract address: %w", err)
35+
}
36+
37+
domain := vpr.GetString(domainFlag)
38+
nnsContract := nnsrpc.NewReader(invoker.New(n3Client, nil), nnsContractAddr)
39+
40+
records, err := nnsContract.Resolve(domain, nnsrpc.TXT)
41+
if err != nil {
42+
// Track https://github.com/nspcc-dev/neofs-node/issues/2583.
43+
if strings.Contains(err.Error(), "token not found") {
44+
cmd.Println("Domain not found.")
45+
return nil
46+
}
47+
48+
return fmt.Errorf("get all text records of the NNS domain %q: %w", domain, err)
49+
}
50+
51+
if len(records) == 0 {
52+
cmd.Println("List is empty.")
53+
return nil
54+
}
55+
56+
for i := range records {
57+
neoAddr := strings.TrimPrefix(records[i], nnsNeoAddressTextRecordPrefix)
58+
if len(neoAddr) == len(records[i]) {
59+
cmd.Printf("%s (not a Neo address)\n", records[i])
60+
continue
61+
}
62+
63+
cmd.Println(neoAddr)
64+
}
65+
66+
return nil
67+
}
68+
69+
func verifiedNodesDomainSetAccessList(cmd *cobra.Command, _ []string) error {
70+
vpr := viper.GetViper()
71+
72+
strNeoAddresses := vpr.GetStringSlice(neoAddressesFlag)
73+
strPublicKeys := vpr.GetStringSlice(publicKeysFlag)
74+
if len(strNeoAddresses)+len(strPublicKeys) == 0 {
75+
// Track https://github.com/nspcc-dev/neofs-node/issues/2595.
76+
return errors.New("neither Neo addresses nor public keys are set")
77+
}
78+
79+
if len(strNeoAddresses)*len(strPublicKeys) != 0 {
80+
// just to make sure
81+
panic("mutually exclusive flags bypassed Cobra")
82+
}
83+
84+
var err error
85+
var additionalRecords []string
86+
87+
if len(strNeoAddresses) > 0 {
88+
for i := range strNeoAddresses {
89+
for j := i + 1; j < len(strNeoAddresses); j++ {
90+
if strNeoAddresses[i] == strNeoAddresses[j] {
91+
return fmt.Errorf("duplicated Neo address %s", strNeoAddresses[i])
92+
}
93+
}
94+
95+
_, err = address.StringToUint160(strNeoAddresses[i])
96+
if err != nil {
97+
return fmt.Errorf("address #%d is invalid: %w", i, err)
98+
}
99+
100+
additionalRecords = append(additionalRecords, nnsNeoAddressTextRecordPrefix+strNeoAddresses[i])
101+
}
102+
} else {
103+
additionalRecords = make([]string, len(strPublicKeys))
104+
105+
for i := range strPublicKeys {
106+
for j := i + 1; j < len(strPublicKeys); j++ {
107+
if strPublicKeys[i] == strPublicKeys[j] {
108+
return fmt.Errorf("duplicated public key %s", strPublicKeys[i])
109+
}
110+
}
111+
112+
pubKey, err := keys.NewPublicKeyFromString(strPublicKeys[i])
113+
if err != nil {
114+
return fmt.Errorf("public key #%d is not a HEX-encoded public key: %w", i, err)
115+
}
116+
117+
additionalRecords[i] = nnsNeoAddressTextRecordPrefix + address.Uint160ToString(pubKey.GetScriptHash())
118+
}
119+
}
120+
121+
w, err := wallet.NewWalletFromFile(viper.GetString(walletFlag))
122+
if err != nil {
123+
return fmt.Errorf("decode Neo wallet from file: %v", err)
124+
}
125+
126+
var accAddr util.Uint160
127+
if strAccAddr := viper.GetString(walletAccountFlag); strAccAddr != "" {
128+
accAddr, err = address.StringToUint160(strAccAddr)
129+
if err != nil {
130+
return fmt.Errorf("invalid Neo account address in flag --%s: %q", walletAccountFlag, strAccAddr)
131+
}
132+
} else {
133+
accAddr = w.GetChangeAddress()
134+
}
135+
136+
acc := w.GetAccount(accAddr)
137+
if acc == nil {
138+
return fmt.Errorf("account %s not found in the wallet", address.Uint160ToString(accAddr))
139+
}
140+
141+
prompt := fmt.Sprintf("Enter password for %s >", address.Uint160ToString(accAddr))
142+
pass, err := input.ReadPassword(prompt)
143+
if err != nil {
144+
return fmt.Errorf("failed to read account password: %v", err)
145+
}
146+
147+
err = acc.Decrypt(pass, keys.NEP2ScryptParams())
148+
if err != nil {
149+
return fmt.Errorf("failed to unlock the account with password: %v", err)
150+
}
151+
152+
n3Client, err := getN3Client(vpr)
153+
if err != nil {
154+
return fmt.Errorf("open connection: %w", err)
155+
}
156+
157+
nnsContractAddr, err := nnsrpc.InferHash(n3Client)
158+
if err != nil {
159+
return fmt.Errorf("get NeoFS NNS contract address: %w", err)
160+
}
161+
162+
actr, err := actor.NewSimple(n3Client, acc)
163+
if err != nil {
164+
return fmt.Errorf("init committee actor: %w", err)
165+
}
166+
167+
nnsContract := nnsrpc.New(actr, nnsContractAddr)
168+
domain := vpr.GetString(domainFlag)
169+
scriptBuilder := smartcontract.NewBuilder()
170+
171+
records, err := nnsContract.Resolve(domain, nnsrpc.TXT)
172+
if err != nil {
173+
// Track https://github.com/nspcc-dev/neofs-node/issues/2583.
174+
if !strings.Contains(err.Error(), "token not found") {
175+
return fmt.Errorf("get all text records of the NNS domain %q: %w", domain, err)
176+
}
177+
178+
domainToRegister := domain
179+
if labels := strings.Split(domainToRegister, "."); len(labels) > 2 {
180+
// we need explicitly register L2 domain like 'some-org.neofs'
181+
// and then just add records to inferior domains
182+
domainToRegister = labels[len(labels)-2] + "." + labels[len(labels)-1]
183+
}
184+
185+
scriptBuilder.InvokeMethod(nnsContractAddr, "register",
186+
domainToRegister, acc.ScriptHash(), "[email protected]", int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
187+
}
188+
189+
hasOtherRecord := false
190+
mAlreadySetIndices := make(map[int]struct{}, len(additionalRecords))
191+
192+
loop:
193+
for i := range records {
194+
for j := range additionalRecords {
195+
if additionalRecords[j] == records[i] {
196+
mAlreadySetIndices[i] = struct{}{}
197+
continue loop
198+
}
199+
}
200+
201+
hasOtherRecord = true
202+
203+
break
204+
}
205+
206+
if !hasOtherRecord && len(mAlreadySetIndices) == len(additionalRecords) {
207+
cmd.Println("Current list is already the same, skip.")
208+
return nil
209+
}
210+
211+
if hasOtherRecord {
212+
// there is no way to delete particular record, so clean all first
213+
scriptBuilder.InvokeMethod(nnsContractAddr, "deleteRecords",
214+
domain, nnsrpc.TXT.Int64())
215+
}
216+
217+
for i := range additionalRecords {
218+
if !hasOtherRecord {
219+
if _, ok := mAlreadySetIndices[i]; ok {
220+
continue
221+
}
222+
}
223+
224+
scriptBuilder.InvokeMethod(nnsContractAddr, "addRecord",
225+
domain, nnsrpc.TXT.Int64(), additionalRecords[i])
226+
}
227+
228+
txScript, err := scriptBuilder.Script()
229+
if err != nil {
230+
return fmt.Errorf("build transaction script: %w", err)
231+
}
232+
233+
_, err = actr.Wait(actr.SendRun(txScript))
234+
if err != nil {
235+
return fmt.Errorf("send transction with built script and wait for it to be accepted: %w", err)
236+
}
237+
238+
cmd.Println("Access list has been successfully updated.")
239+
240+
return nil
241+
}

cmd/neofs-cli/modules/storagegroup/put.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,7 @@ func initSGPutCmd() {
4949
}
5050

5151
func putSG(cmd *cobra.Command, _ []string) {
52-
// with 1.8.0 cobra release we can use this instead of below
53-
// sgPutCmd.MarkFlagsOneRequired("expire-at", "lifetime")
52+
// Track https://github.com/nspcc-dev/neofs-node/issues/2595.
5453
exp, _ := cmd.Flags().GetUint64(commonflags.ExpireAt)
5554
lifetime, _ := cmd.Flags().GetUint64(commonflags.Lifetime)
5655
if exp == 0 && lifetime == 0 { // mutual exclusion is ensured by cobra

cmd/neofs-node/config/node/config_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,10 @@ func TestNodeSection(t *testing.T) {
111111

112112
require.Equal(t, true, relay)
113113

114-
require.Len(t, attributes, 2)
114+
require.Len(t, attributes, 3)
115115
require.Equal(t, "Price:11", attributes[0])
116116
require.Equal(t, "UN-LOCODE:RU MSK", attributes[1])
117+
require.Equal(t, "VerifiedNodesDomain:nodes.some-org.neofs", attributes[2])
117118

118119
require.NotNil(t, wKey)
119120
require.Equal(t,

config/example/node.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ NEOFS_NODE_WALLET_PASSWORD=password
1616
NEOFS_NODE_ADDRESSES="s01.neofs.devenv:8080 /dns4/s02.neofs.devenv/tcp/8081 grpc://127.0.0.1:8082 grpcs://localhost:8083"
1717
NEOFS_NODE_ATTRIBUTE_0=Price:11
1818
NEOFS_NODE_ATTRIBUTE_1="UN-LOCODE:RU MSK"
19+
NEOFS_NODE_ATTRIBUTE_2="VerifiedNodesDomain:nodes.some-org.neofs"
1920
NEOFS_NODE_RELAY=true
2021
NEOFS_NODE_PERSISTENT_SESSIONS_PATH=/sessions
2122
NEOFS_NODE_PERSISTENT_STATE_PATH=/state

config/example/node.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
],
2828
"attribute_0": "Price:11",
2929
"attribute_1": "UN-LOCODE:RU MSK",
30+
"attribute_2": "VerifiedNodesDomain:nodes.some-org.neofs",
3031
"relay": true,
3132
"persistent_sessions": {
3233
"path": "/sessions"

0 commit comments

Comments
 (0)