Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce private attributes of the storage nodes #2580

Merged
merged 6 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Changelog for NeoFS Node

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

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

### Updating from v0.38.1

Expand Down
2 changes: 1 addition & 1 deletion cmd/neofs-adm/internal/modules/morph/n3client.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ type clientContext struct {
SentTxs []hashVUBPair
}

func getN3Client(v *viper.Viper) (Client, error) {
func getN3Client(v *viper.Viper) (*rpcclient.Client, error) {
// number of opened connections
// by neo-go client per one host
const (
Expand Down
64 changes: 64 additions & 0 deletions cmd/neofs-adm/internal/modules/morph/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ const (
localDumpFlag = "local-dump"
protoConfigPath = "protocol"
walletAddressFlag = "wallet-address"
domainFlag = "domain"
neoAddressesFlag = "neo-addresses"
publicKeysFlag = "public-keys"
walletFlag = "wallet"
)

var (
Expand Down Expand Up @@ -257,6 +261,39 @@ Values for unknown keys are added exactly the way they're provided, no conversio
},
RunE: listNetmapCandidatesNodes,
}

verifiedNodesDomainCmd = &cobra.Command{
Use: "verified-nodes-domain",
Short: "Group of commands to work with verified domains for the storage nodes",
Args: cobra.NoArgs,
PersistentPreRun: func(cmd *cobra.Command, _ []string) {
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
_ = viper.BindPFlag(domainFlag, cmd.Flags().Lookup(domainFlag))
},
}

verifiedNodesDomainAccessListCmd = &cobra.Command{
Use: "access-list",
Short: "Get access list for the verified nodes' domain",
Long: "List Neo addresses of the storage nodes that have access to use the specified verified domain.",
Args: cobra.NoArgs,
RunE: verifiedNodesDomainAccessList,
}

verifiedNodesDomainSetAccessListCmd = &cobra.Command{
Use: "set-access-list",
Short: "Set access list for the verified nodes' domain",
Long: "Set list of the storage nodes that have access to use the specified verified domain. " +
"The list may be either Neo addresses or HEX-encoded public keys of the nodes.",
Args: cobra.NoArgs,
PreRun: func(cmd *cobra.Command, _ []string) {
_ = viper.BindPFlag(walletFlag, cmd.Flags().Lookup(walletFlag))
_ = viper.BindPFlag(walletAccountFlag, cmd.Flags().Lookup(walletAccountFlag))
_ = viper.BindPFlag(publicKeysFlag, cmd.Flags().Lookup(publicKeysFlag))
_ = viper.BindPFlag(neoAddressesFlag, cmd.Flags().Lookup(neoAddressesFlag))
},
RunE: verifiedNodesDomainSetAccessList,
}
)

func init() {
Expand Down Expand Up @@ -365,4 +402,31 @@ func init() {

RootCmd.AddCommand(netmapCandidatesCmd)
netmapCandidatesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")

cmd := verifiedNodesDomainAccessListCmd
fs := cmd.Flags()
fs.StringP(endpointFlag, "r", "", "NeoFS Sidechain RPC endpoint")
_ = cmd.MarkFlagRequired(endpointFlag)
fs.StringP(domainFlag, "d", "", "Verified domain of the storage nodes. Must be a valid NeoFS NNS domain (e.g. 'nodes.some-org.neofs')")
_ = cmd.MarkFlagRequired(domainFlag)

verifiedNodesDomainCmd.AddCommand(cmd)

cmd = verifiedNodesDomainSetAccessListCmd
fs = cmd.Flags()
fs.StringP(walletFlag, "w", "", "Path to the Neo wallet file")
_ = cmd.MarkFlagRequired(walletFlag)
fs.StringP(walletAccountFlag, "a", "", "Optional Neo address of the wallet account for signing transactions. "+
"If omitted, default change address from the wallet is used")
fs.StringP(endpointFlag, "r", "", "NeoFS Sidechain RPC endpoint")
_ = cmd.MarkFlagRequired(endpointFlag)
fs.StringP(domainFlag, "d", "", "Verified domain of the storage nodes. Must be a valid NeoFS NNS domain (e.g. 'nodes.some-org.neofs')")
_ = cmd.MarkFlagRequired(domainFlag)
fs.StringSlice(neoAddressesFlag, nil, "Neo addresses resolved from public keys of the storage nodes")
fs.StringSlice(publicKeysFlag, nil, "HEX-encoded public keys of the storage nodes")
cmd.MarkFlagsMutuallyExclusive(publicKeysFlag, neoAddressesFlag)

verifiedNodesDomainCmd.AddCommand(cmd)

RootCmd.AddCommand(verifiedNodesDomainCmd)
}
241 changes: 241 additions & 0 deletions cmd/neofs-adm/internal/modules/morph/verified_domains.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
package morph

import (
"errors"
"fmt"
"strings"

"github.com/nspcc-dev/neo-go/cli/input"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet"
nnsrpc "github.com/nspcc-dev/neofs-contract/rpc/nns"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

// as described in NEP-18 Specification https://github.com/neo-project/proposals/pull/133
const nnsNeoAddressTextRecordPrefix = "address="

func verifiedNodesDomainAccessList(cmd *cobra.Command, _ []string) error {
vpr := viper.GetViper()

n3Client, err := getN3Client(vpr)
if err != nil {
return fmt.Errorf("open connection: %w", err)
}

nnsContractAddr, err := nnsrpc.InferHash(n3Client)
if err != nil {
return fmt.Errorf("get NeoFS NNS contract address: %w", err)
}

domain := vpr.GetString(domainFlag)
nnsContract := nnsrpc.NewReader(invoker.New(n3Client, nil), nnsContractAddr)

records, err := nnsContract.Resolve(domain, nnsrpc.TXT)
if err != nil {
// Track https://github.com/nspcc-dev/neofs-node/issues/2583.
if strings.Contains(err.Error(), "token not found") {
cmd.Println("Domain not found.")
return nil
}

return fmt.Errorf("get all text records of the NNS domain %q: %w", domain, err)
}

if len(records) == 0 {
cmd.Println("List is empty.")
return nil
}

for i := range records {
neoAddr := strings.TrimPrefix(records[i], nnsNeoAddressTextRecordPrefix)
if len(neoAddr) == len(records[i]) {
cmd.Printf("%s (not a Neo address)\n", records[i])
continue
}

cmd.Println(neoAddr)
}

return nil
}

func verifiedNodesDomainSetAccessList(cmd *cobra.Command, _ []string) error {
vpr := viper.GetViper()

strNeoAddresses := vpr.GetStringSlice(neoAddressesFlag)
strPublicKeys := vpr.GetStringSlice(publicKeysFlag)
if len(strNeoAddresses)+len(strPublicKeys) == 0 {
// Track https://github.com/nspcc-dev/neofs-node/issues/2595.
return errors.New("neither Neo addresses nor public keys are set")
}

if len(strNeoAddresses)*len(strPublicKeys) != 0 {
// just to make sure
panic("mutually exclusive flags bypassed Cobra")
}

var err error
var additionalRecords []string

if len(strNeoAddresses) > 0 {
for i := range strNeoAddresses {
for j := i + 1; j < len(strNeoAddresses); j++ {
if strNeoAddresses[i] == strNeoAddresses[j] {
return fmt.Errorf("duplicated Neo address %s", strNeoAddresses[i])
}
}

_, err = address.StringToUint160(strNeoAddresses[i])
if err != nil {
return fmt.Errorf("address #%d is invalid: %w", i, err)
}

additionalRecords = append(additionalRecords, nnsNeoAddressTextRecordPrefix+strNeoAddresses[i])
}
} else {
additionalRecords = make([]string, len(strPublicKeys))

for i := range strPublicKeys {
for j := i + 1; j < len(strPublicKeys); j++ {
if strPublicKeys[i] == strPublicKeys[j] {
return fmt.Errorf("duplicated public key %s", strPublicKeys[i])
}
}

pubKey, err := keys.NewPublicKeyFromString(strPublicKeys[i])
if err != nil {
return fmt.Errorf("public key #%d is not a HEX-encoded public key: %w", i, err)
}

additionalRecords[i] = nnsNeoAddressTextRecordPrefix + address.Uint160ToString(pubKey.GetScriptHash())
}
}

w, err := wallet.NewWalletFromFile(viper.GetString(walletFlag))
if err != nil {
return fmt.Errorf("decode Neo wallet from file: %v", err)
}

var accAddr util.Uint160
if strAccAddr := viper.GetString(walletAccountFlag); strAccAddr != "" {
accAddr, err = address.StringToUint160(strAccAddr)
if err != nil {
return fmt.Errorf("invalid Neo account address in flag --%s: %q", walletAccountFlag, strAccAddr)
}
} else {
accAddr = w.GetChangeAddress()
}

acc := w.GetAccount(accAddr)
if acc == nil {
return fmt.Errorf("account %s not found in the wallet", address.Uint160ToString(accAddr))
}

prompt := fmt.Sprintf("Enter password for %s >", address.Uint160ToString(accAddr))
pass, err := input.ReadPassword(prompt)
if err != nil {
return fmt.Errorf("failed to read account password: %v", err)
}

err = acc.Decrypt(pass, keys.NEP2ScryptParams())
if err != nil {
return fmt.Errorf("failed to unlock the account with password: %v", err)
}

n3Client, err := getN3Client(vpr)
if err != nil {
return fmt.Errorf("open connection: %w", err)
}

nnsContractAddr, err := nnsrpc.InferHash(n3Client)
if err != nil {
return fmt.Errorf("get NeoFS NNS contract address: %w", err)
}

actr, err := actor.NewSimple(n3Client, acc)
if err != nil {
return fmt.Errorf("init committee actor: %w", err)
}

nnsContract := nnsrpc.New(actr, nnsContractAddr)
domain := vpr.GetString(domainFlag)
scriptBuilder := smartcontract.NewBuilder()

records, err := nnsContract.Resolve(domain, nnsrpc.TXT)
if err != nil {
// Track https://github.com/nspcc-dev/neofs-node/issues/2583.
if !strings.Contains(err.Error(), "token not found") {
return fmt.Errorf("get all text records of the NNS domain %q: %w", domain, err)
}

domainToRegister := domain
if labels := strings.Split(domainToRegister, "."); len(labels) > 2 {
// we need explicitly register L2 domain like 'some-org.neofs'
// and then just add records to inferior domains
domainToRegister = labels[len(labels)-2] + "." + labels[len(labels)-1]
}

scriptBuilder.InvokeMethod(nnsContractAddr, "register",
domainToRegister, acc.ScriptHash(), "[email protected]", int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
}

hasOtherRecord := false
mAlreadySetIndices := make(map[int]struct{}, len(additionalRecords))

loop:
for i := range records {
for j := range additionalRecords {
if additionalRecords[j] == records[i] {
mAlreadySetIndices[i] = struct{}{}
continue loop
}
}

hasOtherRecord = true

break
}

if !hasOtherRecord && len(mAlreadySetIndices) == len(additionalRecords) {
cmd.Println("Current list is already the same, skip.")
return nil
}

if hasOtherRecord {
// there is no way to delete particular record, so clean all first
scriptBuilder.InvokeMethod(nnsContractAddr, "deleteRecords",
domain, nnsrpc.TXT.Int64())
}

for i := range additionalRecords {
if !hasOtherRecord {
if _, ok := mAlreadySetIndices[i]; ok {
continue
}
}

scriptBuilder.InvokeMethod(nnsContractAddr, "addRecord",
domain, nnsrpc.TXT.Int64(), additionalRecords[i])
}

txScript, err := scriptBuilder.Script()
if err != nil {
return fmt.Errorf("build transaction script: %w", err)
}

_, err = actr.Wait(actr.SendRun(txScript))
if err != nil {
return fmt.Errorf("send transction with built script and wait for it to be accepted: %w", err)
}

cmd.Println("Access list has been successfully updated.")

return nil
}
3 changes: 1 addition & 2 deletions cmd/neofs-cli/modules/storagegroup/put.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ func initSGPutCmd() {
}

func putSG(cmd *cobra.Command, _ []string) {
// with 1.8.0 cobra release we can use this instead of below
// sgPutCmd.MarkFlagsOneRequired("expire-at", "lifetime")
// Track https://github.com/nspcc-dev/neofs-node/issues/2595.
exp, _ := cmd.Flags().GetUint64(commonflags.ExpireAt)
lifetime, _ := cmd.Flags().GetUint64(commonflags.Lifetime)
if exp == 0 && lifetime == 0 { // mutual exclusion is ensured by cobra
Expand Down
3 changes: 2 additions & 1 deletion cmd/neofs-node/config/node/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,10 @@ func TestNodeSection(t *testing.T) {

require.Equal(t, true, relay)

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

require.NotNil(t, wKey)
require.Equal(t,
Expand Down
1 change: 1 addition & 0 deletions config/example/node.env
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ NEOFS_NODE_WALLET_PASSWORD=password
NEOFS_NODE_ADDRESSES="s01.neofs.devenv:8080 /dns4/s02.neofs.devenv/tcp/8081 grpc://127.0.0.1:8082 grpcs://localhost:8083"
NEOFS_NODE_ATTRIBUTE_0=Price:11
NEOFS_NODE_ATTRIBUTE_1="UN-LOCODE:RU MSK"
NEOFS_NODE_ATTRIBUTE_2="VerifiedNodesDomain:nodes.some-org.neofs"
NEOFS_NODE_RELAY=true
NEOFS_NODE_PERSISTENT_SESSIONS_PATH=/sessions
NEOFS_NODE_PERSISTENT_STATE_PATH=/state
Expand Down
1 change: 1 addition & 0 deletions config/example/node.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
],
"attribute_0": "Price:11",
"attribute_1": "UN-LOCODE:RU MSK",
"attribute_2": "VerifiedNodesDomain:nodes.some-org.neofs",
"relay": true,
"persistent_sessions": {
"path": "/sessions"
Expand Down
Loading
Loading