diff --git a/cmd/frostfs-adm/docs/subnetwork-creation.md b/cmd/frostfs-adm/docs/subnetwork-creation.md deleted file mode 100644 index 5ada9438..00000000 --- a/cmd/frostfs-adm/docs/subnetwork-creation.md +++ /dev/null @@ -1,39 +0,0 @@ -# FrostFS subnetwork creation - -This is a short guide on how to create FrostFS subnetworks. This guide -considers that the sidechain and the inner ring (alphabet nodes) have already been -deployed and the sidechain contains a deployed `subnet` contract. - -## Prerequisites - -To follow this guide, you need: -- neo-go sidechain RPC endpoint; -- latest released version of [frostfs-adm](https://github.com/TrueCloudLab/frostfs-node/releases); -- wallet with FrostFS account. - -## Creation - -```shell -$ frostfs-adm morph subnet create \ - -r \ - -w \ - --notary -Create subnet request sent successfully. ID: 4223489767. -``` - -**NOTE:** in notary-enabled environment you should have a sufficient -notary deposit (not expired, with enough GAS balance). Your subnet ID -will differ from the example. - -The default account in the wallet that has been passed with `-w` flag is the owner -of the just created subnetwork. - -You can check if your subnetwork was created successfully: - -```shell -$ frostfs-adm morph subnet get \ - -r \ - --subnet -Owner: NUc734PMJXiqa2J9jRtvskU3kCdyyuSN8Q -``` -Your owner will differ from the example. diff --git a/cmd/frostfs-adm/docs/subnetwork-usage.md b/cmd/frostfs-adm/docs/subnetwork-usage.md deleted file mode 100644 index 0d505b3a..00000000 --- a/cmd/frostfs-adm/docs/subnetwork-usage.md +++ /dev/null @@ -1,137 +0,0 @@ -# Managing Subnetworks - -This is a short guide on how to manage FrostFS subnetworks. This guide -considers that the sidechain and the inner ring (alphabet nodes) have already been -deployed, and the sidechain contains a deployed `subnet` contract. - -## Prerequisites - -- neo-go sidechain RPC endpoint; -- latest released version of [frostfs-adm](https://github.com/TrueCloudLab/frostfs-node/releases); -- [created](subnetwork-creation.md) subnetwork; -- wallet with the account that owns the subnetwork; -- public key of the Storage Node; -- public keys of the node and client administrators; -- owner IDs of the FrostFS users. - -## Add node administrator - -Node administrators are accounts that can manage (add and delete nodes) -the whitelist of the nodes which can be included to a subnetwork. Only the subnet -owner is allowed to add and remove node administrators from the subnetwork. - -```shell -$ frostfs-adm morph subnet admin add \ - -r \ - -w \ - --admin \ - --subnet -Add admin request sent successfully. -``` - -## Add node - -Adding a node to a subnetwork means that the node becomes able to service -containers that have been created in that subnetwork. Addition only changes -the list of the allowed nodes. Node is not required to be bootstrapped at the -moment of its inclusion. - -```shell -$ frostfs-adm morph subnet node add \ - -r \ - -w \ - --node \ - --subnet -Add node request sent successfully. -``` - -**NOTE:** the owner of the subnetwork is also allowed to add nodes. - -## Add client administrator - -Client administrators are accounts that can manage (add and delete -nodes) the whitelist of the clients that can create containers in the -subnetwork. Only the subnet owner is allowed to add and remove client -administrators from the subnetwork. - -```shell -$ frostfs-adm morph subnet admin add \ - -r \ - -w \ - --admin \ - --subnet \ - --client \ - --group -Add admin request sent successfully. -``` - -**NOTE:** you do not need to create a group explicitly, it will be created -right after the first client admin is added. Group ID is a 4-byte -positive integer number. - -## Add client - -```shell -$ frostfs-adm morph subnet client add \ - -r \ - -w \ - --client \ - --subnet \ - --group -Add client request sent successfully. -``` - -**NOTE:** the owner of the subnetwork is also allowed to add clients. This is -the only one command that accepts `ownerID`, not the public key. -Administrator can manage only their group (a group where that administrator -has been added by the subnet owner). - -# Bootstrapping Storage Node - -After a subnetwork [is created](subnetwork-creation.md) and a node is included into it, the -node could be bootstrapped and service subnetwork containers. - -For bootstrapping, you need to specify the ID of the subnetwork in the node's -configuration: - -```yaml -... -node: - ... - subnet: - entries: # list of IDs of subnets to enter in a text format of FrostFS API protocol (overrides corresponding attributes) - - - ... -... -``` - -**NOTE:** specifying subnetwork that is denied for the node is not an error: -that configuration value would be ignored. You do not need to specify zero -(with 0 ID) subnetwork: its inclusion is implicit. On the contrary, to exclude -a node from the default zero subnetwork, you need to specify it explicitly: - -```yaml -... -node: - ... - subnet: - exit_zero: true # toggle entrance to zero subnet (overrides corresponding attribute and occurrence in `entries`) - ... -... -``` - -# Creating container in non-zero subnetwork - -Creating containers without using `--subnet` flag is equivalent to -creating container in the zero subnetwork. - -To create a container in a private network, your wallet must be added to -the client whitelist by the client admins or the subnet owners: - -```shell -$ frostfs-cli container create \ - --policy 'REP 1' \ - -w \ - -r s01.frostfs.devenv:8080 \ - --subnet -``` diff --git a/cmd/frostfs-adm/internal/modules/morph/initialize_deploy.go b/cmd/frostfs-adm/internal/modules/morph/initialize_deploy.go index 9a15b69b..1e35fb79 100644 --- a/cmd/frostfs-adm/internal/modules/morph/initialize_deploy.go +++ b/cmd/frostfs-adm/internal/modules/morph/initialize_deploy.go @@ -47,7 +47,6 @@ const ( frostfsIDContract = "frostfsid" netmapContract = "netmap" proxyContract = "proxy" - subnetContract = "subnet" ) var ( @@ -58,7 +57,6 @@ var ( frostfsIDContract, netmapContract, proxyContract, - subnetContract, } fullContractList = append([]string{ @@ -593,7 +591,6 @@ func (c *initializeContext) getContractDeployData(ctrName string, keysParam []an configParam) case proxyContract: items = nil - case subnetContract: default: panic(fmt.Sprintf("invalid contract name: %s", ctrName)) } diff --git a/cmd/frostfs-adm/internal/modules/morph/internal/types.go b/cmd/frostfs-adm/internal/modules/morph/internal/types.go deleted file mode 100644 index 93d2cb00..00000000 --- a/cmd/frostfs-adm/internal/modules/morph/internal/types.go +++ /dev/null @@ -1,65 +0,0 @@ -package internal - -import ( - "fmt" - "strconv" - - "google.golang.org/protobuf/proto" -) - -// StringifySubnetClientGroupID returns string representation of SubnetClientGroupID using MarshalText. -// Returns a string with a message on error. -func StringifySubnetClientGroupID(id *SubnetClientGroupID) string { - text, err := id.MarshalText() - if err != nil { - return fmt.Sprintf(" %v", err) - } - - return string(text) -} - -// MarshalText encodes SubnetClientGroupID into text format according to FrostFS API V2 protocol: -// value in base-10 integer string format. -// -// It implements encoding.TextMarshaler. -func (x *SubnetClientGroupID) MarshalText() ([]byte, error) { - num := x.GetValue() // NPE safe, returns zero on nil - - return []byte(strconv.FormatUint(uint64(num), 10)), nil -} - -// UnmarshalText decodes the SubnetID from the text according to FrostFS API V2 protocol: -// should be base-10 integer string format with bitsize = 32. -// -// Returns strconv.ErrRange if integer overflows uint32. -// -// Must not be called on nil. -// -// Implements encoding.TextUnmarshaler. -func (x *SubnetClientGroupID) UnmarshalText(txt []byte) error { - num, err := strconv.ParseUint(string(txt), 10, 32) - if err != nil { - return fmt.Errorf("invalid numeric value: %w", err) - } - - x.SetNumber(uint32(num)) - - return nil -} - -// Marshal encodes the SubnetClientGroupID into a binary format of FrostFS API V2 protocol -// (Protocol Buffers with direct field order). -func (x *SubnetClientGroupID) Marshal() ([]byte, error) { - return proto.Marshal(x) -} - -// Unmarshal decodes the SubnetClientGroupID from FrostFS API V2 binary format (see Marshal). Must not be called on nil. -func (x *SubnetClientGroupID) Unmarshal(data []byte) error { - return proto.Unmarshal(data, x) -} - -// SetNumber sets SubnetClientGroupID value in uint32 format. Must not be called on nil. -// By default, number is 0. -func (x *SubnetClientGroupID) SetNumber(num uint32) { - x.Value = num -} diff --git a/cmd/frostfs-adm/internal/modules/morph/internal/types.pb.go b/cmd/frostfs-adm/internal/modules/morph/internal/types.pb.go deleted file mode 100644 index 6754bf66..00000000 Binary files a/cmd/frostfs-adm/internal/modules/morph/internal/types.pb.go and /dev/null differ diff --git a/cmd/frostfs-adm/internal/modules/morph/internal/types.proto b/cmd/frostfs-adm/internal/modules/morph/internal/types.proto deleted file mode 100644 index 2ce61b3c..00000000 --- a/cmd/frostfs-adm/internal/modules/morph/internal/types.proto +++ /dev/null @@ -1,15 +0,0 @@ -syntax = "proto3"; - -package neo.fs.v2.refs; - -option go_package = "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/internal"; - -// Client group identifier in the FrostFS subnet. -// -// String representation of a value is base-10 integer. -// -// JSON representation is an object containing single `value` number field. -message SubnetClientGroupID { - // 4-byte integer identifier of the subnet client group. - fixed32 value = 1 [json_name = "value"]; -} diff --git a/cmd/frostfs-adm/internal/modules/morph/root.go b/cmd/frostfs-adm/internal/modules/morph/root.go index e92fd216..1361fe15 100644 --- a/cmd/frostfs-adm/internal/modules/morph/root.go +++ b/cmd/frostfs-adm/internal/modules/morph/root.go @@ -255,7 +255,6 @@ func init() { initRestoreContainersCmd() initListContainersCmd() initRefillGasCmd() - initSubnetCmd() initDepositoryNotaryCmd() initNetmapCandidatesCmd() } @@ -274,10 +273,6 @@ func initDepositoryNotaryCmd() { depositNotaryCmd.Flags().String(notaryDepositTillFlag, "", "Notary deposit duration in blocks") } -func initSubnetCmd() { - RootCmd.AddCommand(cmdSubnet) -} - func initRefillGasCmd() { RootCmd.AddCommand(refillGasCmd) refillGasCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir") diff --git a/cmd/frostfs-adm/internal/modules/morph/subnet.go b/cmd/frostfs-adm/internal/modules/morph/subnet.go deleted file mode 100644 index bdead773..00000000 --- a/cmd/frostfs-adm/internal/modules/morph/subnet.go +++ /dev/null @@ -1,1101 +0,0 @@ -package morph - -import ( - "encoding/hex" - "errors" - "fmt" - - "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/internal" - "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client" - "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/rand" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/subnet" - subnetid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/subnet/id" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" - "github.com/nspcc-dev/neo-go/cli/flags" - "github.com/nspcc-dev/neo-go/cli/input" - "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" - "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/hash" - "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/rpcclient" - "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/vm/opcode" - "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" - "github.com/nspcc-dev/neo-go/pkg/wallet" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -func viperBindFlags(cmd *cobra.Command, flags ...string) { - for i := range flags { - _ = viper.BindPFlag(flags[i], cmd.Flags().Lookup(flags[i])) - } -} - -// subnet command section. -var cmdSubnet = &cobra.Command{ - Use: "subnet", - Short: "FrostFS subnet management", - PreRun: func(cmd *cobra.Command, _ []string) { - viperBindFlags(cmd, - endpointFlag, - ) - }, -} - -// shared flags of cmdSubnet sub-commands. -const ( - flagSubnet = "subnet" // subnet identifier - flagSubnetGroup = "group" // subnet client group ID - flagSubnetWallet = "wallet" // filepath to wallet - flagSubnetAddress = "address" // address in the wallet, optional -) - -// reads wallet from the filepath configured in flagSubnetWallet flag, -// looks for address specified in flagSubnetAddress flag (uses default -// address if flag is empty) and decrypts private key. -func readSubnetKey(key *keys.PrivateKey) error { - // read wallet from file - - walletPath := viper.GetString(flagSubnetWallet) - if walletPath == "" { - return errors.New("missing path to wallet") - } - - w, err := wallet.NewWalletFromFile(walletPath) - if err != nil { - return fmt.Errorf("read wallet from file: %w", err) - } - - // read account from the wallet - - var ( - addr util.Uint160 - addrStr = viper.GetString(flagSubnetAddress) - ) - - if addrStr == "" { - addr = w.GetChangeAddress() - } else { - addr, err = flags.ParseAddress(addrStr) - if err != nil { - return fmt.Errorf("read wallet address: %w", err) - } - } - - acc := w.GetAccount(addr) - if acc == nil { - return fmt.Errorf("address %s not found in %s", addrStr, walletPath) - } - - // read password - pass, err := input.ReadPassword("Enter password > ") - if err != nil { - return fmt.Errorf("read password: %w", err) - } - - // decrypt with just read password - err = acc.Decrypt(pass, keys.NEP2ScryptParams()) - if err != nil { - return fmt.Errorf("decrypt wallet: %w", err) - } - - *key = *acc.PrivateKey() - - return nil -} - -// create subnet command. -var cmdSubnetCreate = &cobra.Command{ - Use: "create", - Short: "Create FrostFS subnet", - PreRun: func(cmd *cobra.Command, _ []string) { - viperBindFlags(cmd, - flagSubnetWallet, - flagSubnetAddress, - ) - }, - RunE: func(cmd *cobra.Command, _ []string) error { - // read private key - var key keys.PrivateKey - - err := readSubnetKey(&key) - if err != nil { - return fmt.Errorf("read private key: %w", err) - } - - // generate subnet ID and marshal it - var ( - id subnetid.ID - num uint32 - ) - - for { - num = rand.Uint32() - - id.SetNumeric(num) - - if !subnetid.IsZero(id) { - break - } - } - - // declare creator ID and encode it - var creator user.ID - user.IDFromKey(&creator, key.PrivateKey.PublicKey) - - // fill subnet info and encode it - var info subnet.Info - - info.SetID(id) - info.SetOwner(creator) - - err = invokeMethod(key, true, "put", id.Marshal(), key.PublicKey().Bytes(), info.Marshal()) - if err != nil { - return fmt.Errorf("morph invocation: %w", err) - } - - cmd.Printf("Create subnet request sent successfully. ID: %s.\n", &id) - - return nil - }, -} - -// cmdSubnetRemove flags. -const ( - // subnet ID to be removed. - flagSubnetRemoveID = flagSubnet -) - -// errZeroSubnet is returned on attempts to work with zero subnet which is virtual. -var errZeroSubnet = errors.New("zero subnet") - -// remove subnet command. -var cmdSubnetRemove = &cobra.Command{ - Use: "remove", - Short: "Remove FrostFS subnet", - PreRun: func(cmd *cobra.Command, _ []string) { - viperBindFlags(cmd, - flagSubnetWallet, - flagSubnetAddress, - flagSubnetRemoveID, - ) - }, - RunE: func(cmd *cobra.Command, _ []string) error { - // read private key - var key keys.PrivateKey - - err := readSubnetKey(&key) - if err != nil { - return fmt.Errorf("read private key: %w", err) - } - - // read ID and encode it - var id subnetid.ID - - err = id.DecodeString(viper.GetString(flagSubnetRemoveID)) - if err != nil { - return fmt.Errorf("decode ID text: %w", err) - } - - if subnetid.IsZero(id) { - return errZeroSubnet - } - - err = invokeMethod(key, false, "delete", id.Marshal()) - if err != nil { - return fmt.Errorf("morph invocation: %w", err) - } - - cmd.Println("Remove subnet request sent successfully") - - return nil - }, -} - -// cmdSubnetGet flags. -const ( - // subnet ID to be read. - flagSubnetGetID = flagSubnet -) - -// get subnet command. -var cmdSubnetGet = &cobra.Command{ - Use: "get", - Short: "Read information about the FrostFS subnet", - PreRun: func(cmd *cobra.Command, _ []string) { - viperBindFlags(cmd, - flagSubnetGetID, - ) - }, - RunE: func(cmd *cobra.Command, _ []string) error { - // read ID and encode it - var id subnetid.ID - - err := id.DecodeString(viper.GetString(flagSubnetGetID)) - if err != nil { - return fmt.Errorf("decode ID text: %w", err) - } - - if subnetid.IsZero(id) { - return errZeroSubnet - } - - // use random key to fetch the data - // we could use raw neo-go client to perform testInvoke - // without keys, as it is done in other commands - key, err := keys.NewPrivateKey() - if err != nil { - return fmt.Errorf("init subnet client: %w", err) - } - - res, err := testInvokeMethod(*key, "get", id.Marshal()) - if err != nil { - return fmt.Errorf("morph invocation: %w", err) - } - - if len(res) == 0 { - return errors.New("subnet does not exist") - } - - data, err := client.BytesFromStackItem(res[0]) - if err != nil { - return fmt.Errorf("decoding contract response: %w", err) - } - - // decode info - var info subnet.Info - if err = info.Unmarshal(data); err != nil { - return fmt.Errorf("decode subnet info: %w", err) - } - - // print information - cmd.Printf("Owner: %s\n", info.Owner()) - - return nil - }, -} - -// cmdSubnetAdmin subnet flags. -const ( - flagSubnetAdminSubnet = flagSubnet // subnet ID to be managed - flagSubnetAdminID = "admin" // admin public key - flagSubnetAdminClient = "client" // manage client admins instead of node ones -) - -// command to manage subnet admins. -var cmdSubnetAdmin = &cobra.Command{ - Use: "admin", - Short: "Manage administrators of the FrostFS subnet", - PreRun: func(cmd *cobra.Command, args []string) { - viperBindFlags(cmd, - flagSubnetWallet, - flagSubnetAddress, - flagSubnetAdminSubnet, - flagSubnetAdminID, - ) - }, -} - -// cmdSubnetAdminAdd flags. -const ( - flagSubnetAdminAddGroup = flagSubnetGroup // client group ID -) - -// common executor cmdSubnetAdminAdd and cmdSubnetAdminRemove commands. -func manageSubnetAdmins(cmd *cobra.Command, rm bool) error { - // read private key - var key keys.PrivateKey - - err := readSubnetKey(&key) - if err != nil { - return fmt.Errorf("read private key: %w", err) - } - - // read ID and encode it - var id subnetid.ID - - err = id.DecodeString(viper.GetString(flagSubnetAdminSubnet)) - if err != nil { - return fmt.Errorf("decode ID text: %w", err) - } - - if subnetid.IsZero(id) { - return errZeroSubnet - } - - // read admin key and decode it - binAdminKey, err := hex.DecodeString(viper.GetString(flagSubnetAdminID)) - if err != nil { - return fmt.Errorf("decode admin key text: %w", err) - } - - var pubkey keys.PublicKey - if err = pubkey.DecodeBytes(binAdminKey); err != nil { - return fmt.Errorf("admin key format: %w", err) - } - - return invokeMethodWithParams(cmd, id, rm, binAdminKey, key) -} - -func invokeMethodWithParams(cmd *cobra.Command, id subnetid.ID, rm bool, binAdminKey []byte, key keys.PrivateKey) error { - prm := make([]any, 0, 3) - prm = append(prm, id.Marshal()) - - var method string - - if viper.GetBool(flagSubnetAdminClient) { - var groupID internal.SubnetClientGroupID - - err := groupID.UnmarshalText([]byte(viper.GetString(flagSubnetAdminAddGroup))) - if err != nil { - return fmt.Errorf("decode group ID text: %w", err) - } - - binGroupID, err := groupID.Marshal() - if err != nil { - return fmt.Errorf("marshal group ID: %w", err) - } - - if rm { - method = "removeClientAdmin" - } else { - method = "addClientAdmin" - } - - prm = append(prm, binGroupID) - } else { - if rm { - method = "removeNodeAdmin" - } else { - method = "addNodeAdmin" - } - } - - prm = append(prm, binAdminKey) - - err := invokeMethod(key, false, method, prm...) - if err != nil { - return fmt.Errorf("morph invocation: %w", err) - } - - var op string - - if rm { - op = "Remove" - } else { - op = "Add" - } - - cmd.Printf("%s admin request sent successfully.\n", op) - - return nil -} - -// command to add subnet admin. -var cmdSubnetAdminAdd = &cobra.Command{ - Use: "add", - Short: "Add admin to the FrostFS subnet", - PreRun: func(cmd *cobra.Command, _ []string) { - viperBindFlags(cmd, - flagSubnetAdminAddGroup, - flagSubnetAdminClient, - ) - }, - RunE: func(cmd *cobra.Command, _ []string) error { - return manageSubnetAdmins(cmd, false) - }, -} - -// command to remove subnet admin. -var cmdSubnetAdminRemove = &cobra.Command{ - Use: "remove", - Short: "Remove admin of the FrostFS subnet", - PreRun: func(cmd *cobra.Command, _ []string) { - viperBindFlags(cmd, - flagSubnetAdminClient, - ) - }, - RunE: func(cmd *cobra.Command, _ []string) error { - return manageSubnetAdmins(cmd, true) - }, -} - -// cmdSubnetClient flags. -const ( - flagSubnetClientSubnet = flagSubnet // ID of the subnet to be managed - flagSubnetClientID = flagSubnetAdminClient // client's NeoFS ID - flagSubnetClientGroup = flagSubnetGroup // ID of the subnet client group -) - -// command to manage subnet clients. -var cmdSubnetClient = &cobra.Command{ - Use: "client", - Short: "Manage clients of the FrostFS subnet", - PreRun: func(cmd *cobra.Command, _ []string) { - viperBindFlags(cmd, - flagSubnetWallet, - flagSubnetAddress, - flagSubnetClientSubnet, - flagSubnetClientID, - flagSubnetClientGroup, - ) - }, -} - -// common executor cmdSubnetClientAdd and cmdSubnetClientRemove commands. -func manageSubnetClients(cmd *cobra.Command, rm bool) error { - // read private key - var key keys.PrivateKey - - err := readSubnetKey(&key) - if err != nil { - return fmt.Errorf("read private key: %w", err) - } - - // read ID and encode it - var id subnetid.ID - - err = id.DecodeString(viper.GetString(flagSubnetClientSubnet)) - if err != nil { - return fmt.Errorf("decode ID text: %w", err) - } - - if subnetid.IsZero(id) { - return errZeroSubnet - } - - // read client ID and encode it - var clientID user.ID - - err = clientID.DecodeString(viper.GetString(flagSubnetClientID)) - if err != nil { - return fmt.Errorf("decode client ID text: %w", err) - } - - // read group ID and encode it - var groupID internal.SubnetClientGroupID - - err = groupID.UnmarshalText([]byte(viper.GetString(flagSubnetAdminAddGroup))) - if err != nil { - return fmt.Errorf("decode group ID text: %w", err) - } - - binGroupID, err := groupID.Marshal() - if err != nil { - return fmt.Errorf("marshal group ID: %w", err) - } - - var method string - if rm { - method = "removeUser" - } else { - method = "addUser" - } - - err = invokeMethod(key, false, method, id.Marshal(), binGroupID, clientID.WalletBytes()) - if err != nil { - return fmt.Errorf("morph invocation: %w", err) - } - - var op string - - if rm { - op = "Remove" - } else { - op = "Add" - } - - cmd.Printf("%s client request sent successfully.\n", op) - - return nil -} - -// command to add subnet client. -var cmdSubnetClientAdd = &cobra.Command{ - Use: "add", - Short: "Add client to the FrostFS subnet", - RunE: func(cmd *cobra.Command, _ []string) error { - return manageSubnetClients(cmd, false) - }, -} - -// command to remove subnet client. -var cmdSubnetClientRemove = &cobra.Command{ - Use: "remove", - Short: "Remove client of the FrostFS subnet", - RunE: func(cmd *cobra.Command, _ []string) error { - return manageSubnetClients(cmd, true) - }, -} - -// cmdSubnetNode flags. -const ( - flagSubnetNode = "node" // node ID - flagSubnetNodeSubnet = flagSubnet // ID of the subnet to be managed -) - -// common executor cmdSubnetNodeAdd and cmdSubnetNodeRemove commands. -func manageSubnetNodes(cmd *cobra.Command, rm bool) error { - // read private key - var key keys.PrivateKey - - err := readSubnetKey(&key) - if err != nil { - return fmt.Errorf("read private key: %w", err) - } - - // read ID and encode it - var id subnetid.ID - - err = id.DecodeString(viper.GetString(flagSubnetNodeSubnet)) - if err != nil { - return fmt.Errorf("decode ID text: %w", err) - } - - if subnetid.IsZero(id) { - return errZeroSubnet - } - - // read node ID and encode it - binNodeID, err := hex.DecodeString(viper.GetString(flagSubnetNode)) - if err != nil { - return fmt.Errorf("decode node ID text: %w", err) - } - - var pubkey keys.PublicKey - if err = pubkey.DecodeBytes(binNodeID); err != nil { - return fmt.Errorf("node ID format: %w", err) - } - - var method string - if rm { - method = "removeNode" - } else { - method = "addNode" - } - - err = invokeMethod(key, false, method, id.Marshal(), binNodeID) - if err != nil { - return fmt.Errorf("morph invocation: %w", err) - } - - var op string - - if rm { - op = "Remove" - } else { - op = "Add" - } - - cmd.Printf("%s node request sent successfully.\n", op) - - return nil -} - -// command to manage subnet nodes. -var cmdSubnetNode = &cobra.Command{ - Use: "node", - Short: "Manage nodes of the FrostFS subnet", - PreRun: func(cmd *cobra.Command, _ []string) { - viperBindFlags(cmd, - flagSubnetWallet, - flagSubnetNode, - flagSubnetNodeSubnet, - ) - }, -} - -// command to add subnet node. -var cmdSubnetNodeAdd = &cobra.Command{ - Use: "add", - Short: "Add node to the FrostFS subnet", - RunE: func(cmd *cobra.Command, _ []string) error { - return manageSubnetNodes(cmd, false) - }, -} - -// command to remove subnet node. -var cmdSubnetNodeRemove = &cobra.Command{ - Use: "remove", - Short: "Remove node from the FrostFS subnet", - RunE: func(cmd *cobra.Command, _ []string) error { - return manageSubnetNodes(cmd, true) - }, -} - -// returns function which calls PreRun on parent if it exists. -func inheritPreRun(preRun func(*cobra.Command, []string)) func(*cobra.Command, []string) { - return func(cmd *cobra.Command, args []string) { - par := cmd.Parent() - if par != nil && par.PreRun != nil { - par.PreRun(par, args) - } - - if preRun != nil { - preRun(cmd, args) - } - } -} - -// inherits PreRun function of parent command in all sub-commands and -// adds them to the parent. -func addCommandInheritPreRun(par *cobra.Command, subs ...*cobra.Command) { - for _, sub := range subs { - sub.PreRun = inheritPreRun(sub.PreRun) - } - - par.AddCommand(subs...) -} - -// registers flags and binds sub-commands for subnet commands. -func init() { - initCreateSubnetFlags() - initGetSubnetFlags() - initRemoveSubnetFlags() - initSubnetAdminFlags() - initSubnetAdminAddFlags() - initSubnetAdminRemoveFlags() - initClientManagementFlags() - - // add all admin managing commands to corresponding command section - addCommandInheritPreRun(cmdSubnetAdmin, - cmdSubnetAdminAdd, - cmdSubnetAdminRemove, - ) - - // add all client managing commands to corresponding command section - addCommandInheritPreRun(cmdSubnetClient, - cmdSubnetClientAdd, - cmdSubnetClientRemove, - ) - - initSubnetNodeFlags() - - // add all node managing commands to corresponding command section - addCommandInheritPreRun(cmdSubnetNode, - cmdSubnetNodeAdd, - cmdSubnetNodeRemove, - ) - - initSubnetGlobalFlags() - - // add all subnet commands to corresponding command section - addCommandInheritPreRun(cmdSubnet, - cmdSubnetCreate, - cmdSubnetRemove, - cmdSubnetGet, - cmdSubnetAdmin, - cmdSubnetClient, - cmdSubnetNode, - ) -} - -func initSubnetGlobalFlags() { - cmdSubnetFlags := cmdSubnet.PersistentFlags() - cmdSubnetFlags.StringP(endpointFlag, "r", "", "N3 RPC node endpoint") - _ = cmdSubnet.MarkFlagRequired(endpointFlag) -} - -func initSubnetNodeFlags() { - nodeFlags := cmdSubnetNode.PersistentFlags() - nodeFlags.StringP(flagSubnetWallet, "w", "", "Path to file with wallet") - _ = cmdSubnetNode.MarkFlagRequired(flagSubnetWallet) - nodeFlags.String(flagSubnetNode, "", "Hex-encoded public key of the node") - _ = cmdSubnetNode.MarkFlagRequired(flagSubnetNode) - nodeFlags.String(flagSubnetNodeSubnet, "", "ID of the subnet to manage nodes") - _ = cmdSubnetNode.MarkFlagRequired(flagSubnetNodeSubnet) -} - -func initClientManagementFlags() { - clientFlags := cmdSubnetClient.PersistentFlags() - clientFlags.String(flagSubnetClientSubnet, "", "ID of the subnet to be managed") - _ = cmdSubnetClient.MarkFlagRequired(flagSubnetClientSubnet) - clientFlags.String(flagSubnetClientGroup, "", "ID of the client group to work with") - _ = cmdSubnetClient.MarkFlagRequired(flagSubnetClientGroup) - clientFlags.String(flagSubnetClientID, "", "Client's user ID in FrostFS system in text format") - _ = cmdSubnetClient.MarkFlagRequired(flagSubnetClientID) - clientFlags.StringP(flagSubnetWallet, "w", "", "Path to file with wallet") - _ = cmdSubnetClient.MarkFlagRequired(flagSubnetWallet) - clientFlags.StringP(flagSubnetAddress, "a", "", "Address in the wallet, optional") -} - -func initSubnetAdminRemoveFlags() { - cmdSubnetAdminRemoveFlags := cmdSubnetAdminRemove.Flags() - cmdSubnetAdminRemoveFlags.Bool(flagSubnetAdminClient, false, "Remove client admin instead of node one") -} - -func initSubnetAdminAddFlags() { - cmdSubnetAdminAddFlags := cmdSubnetAdminAdd.Flags() - cmdSubnetAdminAddFlags.String(flagSubnetAdminAddGroup, "", fmt.Sprintf( - "Client group ID in text format (needed with --%s only)", flagSubnetAdminClient)) - cmdSubnetAdminAddFlags.Bool(flagSubnetAdminClient, false, "Add client admin instead of node one") -} - -func initSubnetAdminFlags() { - adminFlags := cmdSubnetAdmin.PersistentFlags() - adminFlags.String(flagSubnetAdminSubnet, "", "ID of the subnet to manage administrators") - _ = cmdSubnetAdmin.MarkFlagRequired(flagSubnetAdminSubnet) - adminFlags.String(flagSubnetAdminID, "", "Hex-encoded public key of the admin") - _ = cmdSubnetAdmin.MarkFlagRequired(flagSubnetAdminID) - adminFlags.StringP(flagSubnetWallet, "w", "", "Path to file with wallet") - _ = cmdSubnetAdmin.MarkFlagRequired(flagSubnetWallet) - adminFlags.StringP(flagSubnetAddress, "a", "", "Address in the wallet, optional") -} - -func initRemoveSubnetFlags() { - cmdSubnetRemove.Flags().String(flagSubnetRemoveID, "", "ID of the subnet to remove") - _ = cmdSubnetRemove.MarkFlagRequired(flagSubnetRemoveID) - cmdSubnetRemove.Flags().StringP(flagSubnetWallet, "w", "", "Path to file with wallet") - _ = cmdSubnetRemove.MarkFlagRequired(flagSubnetWallet) - cmdSubnetRemove.Flags().StringP(flagSubnetAddress, "a", "", "Address in the wallet, optional") -} - -func initGetSubnetFlags() { - cmdSubnetGet.Flags().String(flagSubnetGetID, "", "ID of the subnet to read") - _ = cmdSubnetAdminAdd.MarkFlagRequired(flagSubnetGetID) -} - -func initCreateSubnetFlags() { - cmdSubnetCreate.Flags().StringP(flagSubnetWallet, "w", "", "Path to file with wallet") - _ = cmdSubnetCreate.MarkFlagRequired(flagSubnetWallet) - cmdSubnetCreate.Flags().StringP(flagSubnetAddress, "a", "", "Address in the wallet, optional") -} - -func testInvokeMethod(key keys.PrivateKey, method string, args ...any) ([]stackitem.Item, error) { - c, err := getN3Client(viper.GetViper()) - if err != nil { - return nil, fmt.Errorf("morph client creation: %w", err) - } - - nnsCs, err := c.GetContractStateByID(1) - if err != nil { - return nil, fmt.Errorf("NNS contract resolving: %w", err) - } - - cosigner := []transaction.Signer{ - { - Account: key.PublicKey().GetScriptHash(), - Scopes: transaction.Global, - }, - } - - inv := invoker.New(c, cosigner) - - subnetHash, err := nnsResolveHash(inv, nnsCs.Hash, subnetContract+".frostfs") - if err != nil { - return nil, fmt.Errorf("subnet hash resolving: %w", err) - } - - res, err := inv.Call(subnetHash, method, args...) - if err != nil { - return nil, fmt.Errorf("invocation parameters prepararion: %w", err) - } - - err = checkInvocationResults(res) - if err != nil { - return nil, err - } - - return res.Stack, nil -} - -func invokeMethod(key keys.PrivateKey, tryNotary bool, method string, args ...any) error { - c, err := getN3Client(viper.GetViper()) - if err != nil { - return fmt.Errorf("morph client creation: %w", err) - } - - if tryNotary { - cc, err := c.GetNativeContracts() - if err != nil { - return fmt.Errorf("native hashes: %w", err) - } - - var notary bool - var notaryHash util.Uint160 - for _, c := range cc { - if c.Manifest.Name == nativenames.Notary { - notary = len(c.UpdateHistory) > 0 - notaryHash = c.Hash - - break - } - } - - if notary { - err = invokeNotary(c, key, method, notaryHash, args...) - if err != nil { - return fmt.Errorf("notary invocation: %w", err) - } - - return nil - } - } - - err = invokeNonNotary(c, key, method, args...) - if err != nil { - return fmt.Errorf("non-notary invocation: %w", err) - } - - return nil -} - -func invokeNonNotary(c Client, key keys.PrivateKey, method string, args ...any) error { - nnsCs, err := c.GetContractStateByID(1) - if err != nil { - return fmt.Errorf("NNS contract resolving: %w", err) - } - - acc := wallet.NewAccountFromPrivateKey(&key) - - cosigner := []transaction.Signer{ - { - Account: key.PublicKey().GetScriptHash(), - Scopes: transaction.Global, - }, - } - - cosignerAcc := []rpcclient.SignerAccount{ - { - Signer: cosigner[0], - Account: acc, - }, - } - - inv := invoker.New(c, cosigner) - - subnetHash, err := nnsResolveHash(inv, nnsCs.Hash, subnetContract+".frostfs") - if err != nil { - return fmt.Errorf("subnet hash resolving: %w", err) - } - - test, err := inv.Call(subnetHash, method, args...) - if err != nil { - return fmt.Errorf("test invocation: %w", err) - } - - err = checkInvocationResults(test) - if err != nil { - return err - } - - _, err = c.SignAndPushInvocationTx(test.Script, acc, test.GasConsumed, 0, cosignerAcc) - if err != nil { - return fmt.Errorf("sending transaction: %w", err) - } - - return nil -} - -func invokeNotary(c Client, key keys.PrivateKey, method string, notaryHash util.Uint160, args ...any) error { - nnsCs, err := c.GetContractStateByID(1) - if err != nil { - return fmt.Errorf("NNS contract resolving: %w", err) - } - - alphabet, err := c.GetCommittee() - if err != nil { - return fmt.Errorf("alphabet list: %w", err) - } - - multisigScript, err := smartcontract.CreateDefaultMultiSigRedeemScript(alphabet) - if err != nil { - return fmt.Errorf("alphabet multi-signature script: %w", err) - } - - cosigners, err := notaryCosigners(c, notaryHash, nnsCs, key, hash.Hash160(multisigScript)) - if err != nil { - return fmt.Errorf("cosigners collecting: %w", err) - } - - inv := invoker.New(c, cosigners) - - subnetHash, err := nnsResolveHash(inv, nnsCs.Hash, subnetContract+".frostfs") - if err != nil { - return fmt.Errorf("subnet hash resolving: %w", err) - } - - test, err := makeTestInvocation(inv, subnetHash, method, args) - if err != nil { - return err - } - - multisigAccount := &wallet.Account{ - Contract: &wallet.Contract{ - Script: multisigScript, - }, - } - - bc, err := c.GetBlockCount() - if err != nil { - return fmt.Errorf("blockchain height: %w", err) - } - - return createAndPushTransaction(alphabet, test, bc, cosigners, c, key, multisigAccount) -} - -func makeTestInvocation(inv *invoker.Invoker, subnetHash util.Uint160, method string, args []any) (*result.Invoke, error) { - test, err := inv.Call(subnetHash, method, args...) - if err != nil { - return nil, fmt.Errorf("test invocation: %w", err) - } - - err = checkInvocationResults(test) - if err != nil { - return nil, err - } - return test, nil -} - -func createAndPushTransaction(alphabet keys.PublicKeys, test *result.Invoke, blockCount uint32, cosigners []transaction.Signer, - client Client, key keys.PrivateKey, multisigAccount *wallet.Account) error { - // alphabet multisig + key signature - signersNumber := uint8(smartcontract.GetDefaultHonestNodeCount(len(alphabet)) + 1) - - // notaryRequestValidity is number of blocks during - // witch notary request is considered valid - const notaryRequestValidity = 100 - - mainTx := &transaction.Transaction{ - Nonce: rand.Uint32(), - SystemFee: test.GasConsumed, - ValidUntilBlock: blockCount + notaryRequestValidity, - Script: test.Script, - Attributes: []transaction.Attribute{ - { - Type: transaction.NotaryAssistedT, - Value: &transaction.NotaryAssisted{NKeys: signersNumber}, - }, - }, - Signers: cosigners, - } - - notaryFee, err := client.CalculateNotaryFee(signersNumber) - if err != nil { - return err - } - - acc := wallet.NewAccountFromPrivateKey(&key) - aa := notaryAccounts(multisigAccount, acc) - - err = client.AddNetworkFee(mainTx, notaryFee, aa...) - if err != nil { - return fmt.Errorf("notary network fee adding: %w", err) - } - - mainTx.Scripts = notaryWitnesses(client, multisigAccount, acc, mainTx) - - _, err = client.SignAndPushP2PNotaryRequest(mainTx, - []byte{byte(opcode.RET)}, - -1, - 0, - 40, - acc) - if err != nil { - return fmt.Errorf("sending notary request: %w", err) - } - - return nil -} - -func notaryCosigners(c Client, notaryHash util.Uint160, nnsCs *state.Contract, - key keys.PrivateKey, alphabetAccount util.Uint160) ([]transaction.Signer, error) { - proxyHash, err := nnsResolveHash(invoker.New(c, nil), nnsCs.Hash, proxyContract+".frostfs") - if err != nil { - return nil, fmt.Errorf("proxy hash resolving: %w", err) - } - - return []transaction.Signer{ - { - Account: proxyHash, - Scopes: transaction.None, - }, - { - Account: alphabetAccount, - Scopes: transaction.Global, - }, - { - Account: hash.Hash160(key.PublicKey().GetVerificationScript()), - Scopes: transaction.Global, - }, - { - Account: notaryHash, - Scopes: transaction.None, - }, - }, nil -} - -func notaryAccounts(alphabet, acc *wallet.Account) []*wallet.Account { - return []*wallet.Account{ - // proxy - { - Contract: &wallet.Contract{ - Deployed: true, - }, - }, - alphabet, - // caller's account - acc, - // last one is a placeholder for notary contract account - { - Contract: &wallet.Contract{}, - }, - } -} - -func notaryWitnesses(c Client, alphabet, acc *wallet.Account, tx *transaction.Transaction) []transaction.Witness { - ww := make([]transaction.Witness, 0, 4) - - // empty proxy contract witness - ww = append(ww, transaction.Witness{ - InvocationScript: []byte{}, - VerificationScript: []byte{}, - }) - - // alphabet multi-address witness - ww = append(ww, transaction.Witness{ - InvocationScript: append( - []byte{byte(opcode.PUSHDATA1), 64}, - make([]byte, 64)..., - ), - VerificationScript: alphabet.GetVerificationScript(), - }) - - magicNumber, _ := c.GetNetwork() - - // caller's witness - ww = append(ww, transaction.Witness{ - InvocationScript: append( - []byte{byte(opcode.PUSHDATA1), 64}, - acc.PrivateKey().SignHashable(uint32(magicNumber), tx)...), - VerificationScript: acc.GetVerificationScript(), - }) - - // notary contract witness - ww = append(ww, transaction.Witness{ - InvocationScript: append( - []byte{byte(opcode.PUSHDATA1), 64}, - make([]byte, 64)..., - ), - VerificationScript: []byte{}, - }) - - return ww -} - -func checkInvocationResults(res *result.Invoke) error { - if res.State != "HALT" { - return fmt.Errorf("test invocation state: %s, exception %s: ", res.State, res.FaultException) - } - - if len(res.Script) == 0 { - return errors.New("empty invocation script") - } - - return nil -} diff --git a/cmd/frostfs-adm/internal/modules/storagecfg/config.go b/cmd/frostfs-adm/internal/modules/storagecfg/config.go index 435f79df..a07ce32c 100644 --- a/cmd/frostfs-adm/internal/modules/storagecfg/config.go +++ b/cmd/frostfs-adm/internal/modules/storagecfg/config.go @@ -12,9 +12,6 @@ node: - {{ .AnnouncedAddress }} attribute_0: UN-LOCODE:{{ .Attribute.Locode }} relay: {{ .Relay }} # start Storage node in relay mode without bootstrapping into the Network map - subnet: - exit_zero: false # toggle entrance to zero subnet (overrides corresponding attribute and occurrence in entries) - entries: [] # list of IDs of subnets to enter in a text format of FrostFS API protocol (overrides corresponding attributes) grpc: num: 1 # total number of listener endpoints