diff --git a/cmd/frostfs-adm/internal/modules/morph/container.go b/cmd/frostfs-adm/internal/modules/morph/container.go index 1dec61276..9bc6cae41 100644 --- a/cmd/frostfs-adm/internal/modules/morph/container.go +++ b/cmd/frostfs-adm/internal/modules/morph/container.go @@ -152,7 +152,6 @@ func listContainers(cmd *cobra.Command, _ []string) error { return nil } -// nolint: funlen func restoreContainers(cmd *cobra.Command, _ []string) error { filename, err := cmd.Flags().GetString(containerDumpFlag) if err != nil { @@ -165,25 +164,14 @@ func restoreContainers(cmd *cobra.Command, _ []string) error { } defer wCtx.close() - nnsCs, err := wCtx.Client.GetContractStateByID(1) + containers, err := parseContainers(filename) if err != nil { - return fmt.Errorf("can't get NNS contract state: %w", err) + return err } - ch, err := nnsResolveHash(wCtx.ReadOnlyInvoker, nnsCs.Hash, containerContract+".frostfs") + ch, err := fetchContainerContractHash(wCtx) if err != nil { - return fmt.Errorf("can't fetch container contract hash: %w", err) - } - - data, err := os.ReadFile(filename) - if err != nil { - return fmt.Errorf("can't read dump file: %w", err) - } - - var containers []Container - err = json.Unmarshal(data, &containers) - if err != nil { - return fmt.Errorf("can't parse dump file: %w", err) + return err } isOK, err := getCIDFilterFunc(cmd) @@ -191,6 +179,15 @@ func restoreContainers(cmd *cobra.Command, _ []string) error { return err } + err = restoreOrPutContainers(containers, isOK, cmd, wCtx, ch) + if err != nil { + return err + } + + return wCtx.awaitTx() +} + +func restoreOrPutContainers(containers []Container, isOK func([]byte) bool, cmd *cobra.Command, wCtx *initializeContext, ch util.Uint160) error { bw := io.NewBufBinWriter() for _, cnt := range containers { hv := hash.Sha256(cnt.Value) @@ -198,33 +195,18 @@ func restoreContainers(cmd *cobra.Command, _ []string) error { continue } bw.Reset() - emit.AppCall(bw.BinWriter, ch, "get", callflag.All, hv.BytesBE()) - res, err := wCtx.Client.InvokeScript(bw.Bytes(), nil) + restored, err := isContainerRestored(cmd, wCtx, ch, bw, hv) if err != nil { - return fmt.Errorf("can't check if container is already restored: %w", err) + return err } - if len(res.Stack) == 0 { - return errors.New("empty stack") - } - - old := new(Container) - if err := old.FromStackItem(res.Stack[0]); err != nil { - return fmt.Errorf("%w: %v", errInvalidContainerResponse, err) - } - if len(old.Value) != 0 { - var id cid.ID - id.SetSHA256(hv) - cmd.Printf("Container %s is already deployed.\n", id) + if restored { continue } bw.Reset() - emit.AppCall(bw.BinWriter, ch, "put", callflag.All, - cnt.Value, cnt.Signature, cnt.PublicKey, cnt.Token) - if ea := cnt.EACL; ea != nil { - emit.AppCall(bw.BinWriter, ch, "setEACL", callflag.All, - ea.Value, ea.Signature, ea.PublicKey, ea.Token) - } + + putContainer(bw, ch, cnt) + if bw.Err != nil { panic(bw.Err) } @@ -233,8 +215,67 @@ func restoreContainers(cmd *cobra.Command, _ []string) error { return err } } + return nil +} - return wCtx.awaitTx() +func putContainer(bw *io.BufBinWriter, ch util.Uint160, cnt Container) { + emit.AppCall(bw.BinWriter, ch, "put", callflag.All, + cnt.Value, cnt.Signature, cnt.PublicKey, cnt.Token) + if ea := cnt.EACL; ea != nil { + emit.AppCall(bw.BinWriter, ch, "setEACL", callflag.All, + ea.Value, ea.Signature, ea.PublicKey, ea.Token) + } +} + +func isContainerRestored(cmd *cobra.Command, wCtx *initializeContext, containerHash util.Uint160, bw *io.BufBinWriter, hashValue util.Uint256) (bool, error) { + emit.AppCall(bw.BinWriter, containerHash, "get", callflag.All, hashValue.BytesBE()) + res, err := wCtx.Client.InvokeScript(bw.Bytes(), nil) + if err != nil { + return false, fmt.Errorf("can't check if container is already restored: %w", err) + } + if len(res.Stack) == 0 { + return false, errors.New("empty stack") + } + + old := new(Container) + if err := old.FromStackItem(res.Stack[0]); err != nil { + return false, fmt.Errorf("%w: %v", errInvalidContainerResponse, err) + } + if len(old.Value) != 0 { + var id cid.ID + id.SetSHA256(hashValue) + cmd.Printf("Container %s is already deployed.\n", id) + return true, nil + } + + return false, nil +} + +func parseContainers(filename string) ([]Container, error) { + data, err := os.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("can't read dump file: %w", err) + } + + var containers []Container + err = json.Unmarshal(data, &containers) + if err != nil { + return nil, fmt.Errorf("can't parse dump file: %w", err) + } + return containers, nil +} + +func fetchContainerContractHash(wCtx *initializeContext) (util.Uint160, error) { + nnsCs, err := wCtx.Client.GetContractStateByID(1) + if err != nil { + return util.Uint160{}, fmt.Errorf("can't get NNS contract state: %w", err) + } + + ch, err := nnsResolveHash(wCtx.ReadOnlyInvoker, nnsCs.Hash, containerContract+".frostfs") + if err != nil { + return util.Uint160{}, fmt.Errorf("can't fetch container contract hash: %w", err) + } + return ch, nil } // Container represents container struct in contract storage. diff --git a/cmd/frostfs-adm/internal/modules/morph/deploy.go b/cmd/frostfs-adm/internal/modules/morph/deploy.go index 97e216f35..a9098891c 100644 --- a/cmd/frostfs-adm/internal/modules/morph/deploy.go +++ b/cmd/frostfs-adm/internal/modules/morph/deploy.go @@ -57,7 +57,6 @@ func init() { ff.String(customZoneFlag, "frostfs", "Custom zone for NNS") } -// nolint: funlen func deployContractCmd(cmd *cobra.Command, args []string) error { v := viper.GetViper() c, err := newInitializeContext(cmd, v) @@ -101,80 +100,88 @@ func deployContractCmd(cmd *cobra.Command, args []string) error { cs.Manifest.Name) } - w := io.NewBufBinWriter() - if err := emitDeploymentArguments(w.BinWriter, args); err != nil { + writer := io.NewBufBinWriter() + if err := emitDeploymentArguments(writer.BinWriter, args); err != nil { return err } - emit.Bytes(w.BinWriter, cs.RawManifest) - emit.Bytes(w.BinWriter, cs.RawNEF) - emit.Int(w.BinWriter, 3) - emit.Opcodes(w.BinWriter, opcode.PACK) - emit.AppCallNoArgs(w.BinWriter, callHash, method, callflag.All) - emit.Opcodes(w.BinWriter, opcode.DROP) // contract state on stack + emit.Bytes(writer.BinWriter, cs.RawManifest) + emit.Bytes(writer.BinWriter, cs.RawNEF) + emit.Int(writer.BinWriter, 3) + emit.Opcodes(writer.BinWriter, opcode.PACK) + emit.AppCallNoArgs(writer.BinWriter, callHash, method, callflag.All) + emit.Opcodes(writer.BinWriter, opcode.DROP) // contract state on stack if !isUpdate { - bw := io.NewBufBinWriter() - emit.Instruction(bw.BinWriter, opcode.INITSSLOT, []byte{1}) - emit.AppCall(bw.BinWriter, nnsCs.Hash, "getPrice", callflag.All) - emit.Opcodes(bw.BinWriter, opcode.STSFLD0) - emit.AppCall(bw.BinWriter, nnsCs.Hash, "setPrice", callflag.All, 1) - - start := bw.Len() - needRecord := false - - ok, err := c.nnsRootRegistered(nnsCs.Hash, zone) + err := registerNNS(nnsCs, c, zone, domain, cs, writer) if err != nil { return err - } else if !ok { - needRecord = true - - emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All, - zone, c.CommitteeAcc.Contract.ScriptHash(), - "ops@nspcc.ru", int64(3600), int64(600), int64(defaultExpirationTime), int64(3600)) - emit.Opcodes(bw.BinWriter, opcode.ASSERT) - - emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All, - domain, c.CommitteeAcc.Contract.ScriptHash(), - "ops@nspcc.ru", int64(3600), int64(600), int64(defaultExpirationTime), int64(3600)) - emit.Opcodes(bw.BinWriter, opcode.ASSERT) - } else { - s, ok, err := c.nnsRegisterDomainScript(nnsCs.Hash, cs.Hash, domain) - if err != nil { - return err - } - needRecord = !ok - if len(s) != 0 { - bw.WriteBytes(s) - } - } - if needRecord { - emit.AppCall(bw.BinWriter, nnsCs.Hash, "deleteRecords", callflag.All, domain, int64(nns.TXT)) - emit.AppCall(bw.BinWriter, nnsCs.Hash, "addRecord", callflag.All, - domain, int64(nns.TXT), address.Uint160ToString(cs.Hash)) - } - - if bw.Err != nil { - panic(fmt.Errorf("BUG: can't create deployment script: %w", w.Err)) - } else if bw.Len() != start { - w.WriteBytes(bw.Bytes()) - emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.PUSH1, opcode.PACK) - emit.AppCallNoArgs(w.BinWriter, nnsCs.Hash, "setPrice", callflag.All) - - if needRecord { - c.Command.Printf("NNS: Set %s -> %s\n", domain, cs.Hash.StringLE()) - } } } - if w.Err != nil { - panic(fmt.Errorf("BUG: can't create deployment script: %w", w.Err)) + if writer.Err != nil { + panic(fmt.Errorf("BUG: can't create deployment script: %w", writer.Err)) } - if err := c.sendCommitteeTx(w.Bytes(), false); err != nil { + if err := c.sendCommitteeTx(writer.Bytes(), false); err != nil { return err } return c.awaitTx() } +func registerNNS(nnsCs *state.Contract, c *initializeContext, zone string, domain string, cs *contractState, writer *io.BufBinWriter) error { + bw := io.NewBufBinWriter() + emit.Instruction(bw.BinWriter, opcode.INITSSLOT, []byte{1}) + emit.AppCall(bw.BinWriter, nnsCs.Hash, "getPrice", callflag.All) + emit.Opcodes(bw.BinWriter, opcode.STSFLD0) + emit.AppCall(bw.BinWriter, nnsCs.Hash, "setPrice", callflag.All, 1) + + start := bw.Len() + needRecord := false + + ok, err := c.nnsRootRegistered(nnsCs.Hash, zone) + if err != nil { + return err + } else if !ok { + needRecord = true + + emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All, + zone, c.CommitteeAcc.Contract.ScriptHash(), + "ops@nspcc.ru", int64(3600), int64(600), int64(defaultExpirationTime), int64(3600)) + emit.Opcodes(bw.BinWriter, opcode.ASSERT) + + emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All, + domain, c.CommitteeAcc.Contract.ScriptHash(), + "ops@nspcc.ru", int64(3600), int64(600), int64(defaultExpirationTime), int64(3600)) + emit.Opcodes(bw.BinWriter, opcode.ASSERT) + } else { + s, ok, err := c.nnsRegisterDomainScript(nnsCs.Hash, cs.Hash, domain) + if err != nil { + return err + } + needRecord = !ok + if len(s) != 0 { + bw.WriteBytes(s) + } + } + if needRecord { + emit.AppCall(bw.BinWriter, nnsCs.Hash, "deleteRecords", callflag.All, domain, int64(nns.TXT)) + emit.AppCall(bw.BinWriter, nnsCs.Hash, "addRecord", callflag.All, + domain, int64(nns.TXT), address.Uint160ToString(cs.Hash)) + } + + if bw.Err != nil { + panic(fmt.Errorf("BUG: can't create deployment script: %w", writer.Err)) + } else if bw.Len() != start { + writer.WriteBytes(bw.Bytes()) + emit.Opcodes(writer.BinWriter, opcode.LDSFLD0, opcode.PUSH1, opcode.PACK) + emit.AppCallNoArgs(writer.BinWriter, nnsCs.Hash, "setPrice", callflag.All) + + if needRecord { + c.Command.Printf("NNS: Set %s -> %s\n", domain, cs.Hash.StringLE()) + } + } + return nil +} + func emitDeploymentArguments(w *io.BinWriter, args []string) error { _, ps, err := cmdargs.ParseParams(args, true) if err != nil { diff --git a/cmd/frostfs-adm/internal/modules/morph/initialize.go b/cmd/frostfs-adm/internal/modules/morph/initialize.go index 68e02e777..a99a5faff 100644 --- a/cmd/frostfs-adm/internal/modules/morph/initialize.go +++ b/cmd/frostfs-adm/internal/modules/morph/initialize.go @@ -108,7 +108,6 @@ func (c *initializeContext) close() { } } -// nolint: funlen func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContext, error) { walletDir := config.ResolveHomePath(viper.GetString(alphabetWalletsFlag)) wallets, err := openAlphabetWallets(v, walletDir) @@ -119,24 +118,14 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex needContracts := cmd.Name() == "update-contracts" || cmd.Name() == "init" var w *wallet.Wallet - if needContracts { - w, err = openContractWallet(v, cmd, walletDir) - if err != nil { - return nil, err - } + w, err = getWallet(cmd, v, needContracts, walletDir) + if err != nil { + return nil, err } - var c Client - if v.GetString(localDumpFlag) != "" { - if v.GetString(endpointFlag) != "" { - return nil, fmt.Errorf("`%s` and `%s` flags are mutually exclusive", endpointFlag, localDumpFlag) - } - c, err = newLocalClient(cmd, v, wallets) - } else { - c, err = getN3Client(v) - } + c, err := createClient(cmd, v, wallets) if err != nil { - return nil, fmt.Errorf("can't create N3 client: %w", err) + return nil, err } committeeAcc, err := getWalletAccount(wallets[0], committeeAccountName) @@ -149,35 +138,22 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex return nil, fmt.Errorf("can't find consensus account: %w", err) } - var ctrPath string - if cmd.Name() == "init" { - if viper.GetInt64(epochDurationInitFlag) <= 0 { - return nil, fmt.Errorf("epoch duration must be positive") - } - - if viper.GetInt64(maxObjectSizeInitFlag) <= 0 { - return nil, fmt.Errorf("max object size must be positive") - } + if err := validateInit(cmd); err != nil { + return nil, err } - if needContracts { - ctrPath, err = cmd.Flags().GetString(contractsInitFlag) - if err != nil { - return nil, fmt.Errorf("invalid contracts path: %w", err) - } + ctrPath, err := getContractsPath(cmd, v, needContracts) + if err != nil { + return nil, err } if err := checkNotaryEnabled(c); err != nil { return nil, err } - accounts := make([]*wallet.Account, len(wallets)) - for i, w := range wallets { - acc, err := getWalletAccount(w, singleAccountName) - if err != nil { - return nil, fmt.Errorf("wallet %s is invalid (no single account): %w", w.Path(), err) - } - accounts[i] = acc + accounts, err := createWalletAccounts(wallets) + if err != nil { + return nil, err } cliCtx, err := defaultClientContext(c, committeeAcc) @@ -207,6 +183,69 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex return initCtx, nil } +func validateInit(cmd *cobra.Command) error { + if cmd.Name() != "init" { + return nil + } + if viper.GetInt64(epochDurationInitFlag) <= 0 { + return fmt.Errorf("epoch duration must be positive") + } + + if viper.GetInt64(maxObjectSizeInitFlag) <= 0 { + return fmt.Errorf("max object size must be positive") + } + + return nil +} + +func createClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet) (Client, error) { + var c Client + var err error + if v.GetString(localDumpFlag) != "" { + if v.GetString(endpointFlag) != "" { + return nil, fmt.Errorf("`%s` and `%s` flags are mutually exclusive", endpointFlag, localDumpFlag) + } + c, err = newLocalClient(cmd, v, wallets) + } else { + c, err = getN3Client(v) + } + if err != nil { + return nil, fmt.Errorf("can't create N3 client: %w", err) + } + return c, nil +} + +func getWallet(cmd *cobra.Command, v *viper.Viper, needContracts bool, walletDir string) (*wallet.Wallet, error) { + if !needContracts { + return nil, nil + } + return openContractWallet(v, cmd, walletDir) +} + +func getContractsPath(cmd *cobra.Command, v *viper.Viper, needContracts bool) (string, error) { + if !needContracts { + return "", nil + } + + ctrPath, err := cmd.Flags().GetString(contractsInitFlag) + if err != nil { + return "", fmt.Errorf("invalid contracts path: %w", err) + } + return ctrPath, nil +} + +func createWalletAccounts(wallets []*wallet.Wallet) ([]*wallet.Account, error) { + accounts := make([]*wallet.Account, len(wallets)) + for i, w := range wallets { + acc, err := getWalletAccount(w, singleAccountName) + if err != nil { + return nil, fmt.Errorf("wallet %s is invalid (no single account): %w", w.Path(), err) + } + accounts[i] = acc + } + return accounts, nil +} + func openAlphabetWallets(v *viper.Viper, walletDir string) ([]*wallet.Wallet, error) { walletFiles, err := os.ReadDir(walletDir) if err != nil { diff --git a/cmd/frostfs-adm/internal/modules/morph/initialize_deploy.go b/cmd/frostfs-adm/internal/modules/morph/initialize_deploy.go index c74b78e2b..ae80c2ffd 100644 --- a/cmd/frostfs-adm/internal/modules/morph/initialize_deploy.go +++ b/cmd/frostfs-adm/internal/modules/morph/initialize_deploy.go @@ -156,7 +156,6 @@ func (c *initializeContext) deployNNS(method string) error { return c.awaitTx() } -// nolint: funlen func (c *initializeContext) updateContracts() error { alphaCs := c.getContract(alphabetContract) @@ -168,8 +167,6 @@ func (c *initializeContext) updateContracts() error { w := io2.NewBufBinWriter() - var keysParam []any - // Update script size for a single-node committee is close to the maximum allowed size of 65535. // Because of this we want to reuse alphabet contract NEF and manifest for different updates. // The generated script is as following. @@ -183,42 +180,36 @@ func (c *initializeContext) updateContracts() error { emit.Bytes(w.BinWriter, alphaCs.RawNEF) emit.Opcodes(w.BinWriter, opcode.STSFLD0) - baseGroups := alphaCs.Manifest.Groups - - // alphabet contracts should be deployed by individual nodes to get different hashes. - for i, acc := range c.Accounts { - ctrHash, err := nnsResolveHash(c.ReadOnlyInvoker, nnsHash, getAlphabetNNSDomain(i)) - if err != nil { - return fmt.Errorf("can't resolve hash for contract update: %w", err) - } - - keysParam = append(keysParam, acc.PrivateKey().PublicKey().Bytes()) - - params := c.getAlphabetDeployItems(i, len(c.Wallets)) - emit.Array(w.BinWriter, params...) - - alphaCs.Manifest.Groups = baseGroups - err = c.addManifestGroup(ctrHash, alphaCs) - if err != nil { - return fmt.Errorf("can't sign manifest group: %v", err) - } - - emit.Bytes(w.BinWriter, alphaCs.RawManifest) - emit.Opcodes(w.BinWriter, opcode.LDSFLD0) - emit.Int(w.BinWriter, 3) - emit.Opcodes(w.BinWriter, opcode.PACK) - emit.AppCallNoArgs(w.BinWriter, ctrHash, updateMethodName, callflag.All) - } - - if err := c.sendCommitteeTx(w.Bytes(), false); err != nil { - if !strings.Contains(err.Error(), common.ErrAlreadyUpdated) { - return err - } - c.Command.Println("Alphabet contracts are already updated.") + keysParam, err := c.deployAlphabetAccounts(nnsHash, w, alphaCs) + if err != nil { + return err } w.Reset() + if err = c.deployOrUpdateContracts(w, nnsHash, keysParam); err != nil { + return err + } + + groupKey := c.ContractWallet.Accounts[0].PrivateKey().PublicKey() + _, _, err = c.emitUpdateNNSGroupScript(w, nnsHash, groupKey) + if err != nil { + return err + } + c.Command.Printf("NNS: Set %s -> %s\n", morphClient.NNSGroupKeyName, hex.EncodeToString(groupKey.Bytes())) + + emit.Opcodes(w.BinWriter, opcode.LDSFLD0) + emit.Int(w.BinWriter, 1) + emit.Opcodes(w.BinWriter, opcode.PACK) + emit.AppCallNoArgs(w.BinWriter, nnsHash, "setPrice", callflag.All) + + if err := c.sendCommitteeTx(w.Bytes(), false); err != nil { + return err + } + return c.awaitTx() +} + +func (c *initializeContext) deployOrUpdateContracts(w *io2.BufBinWriter, nnsHash util.Uint160, keysParam []any) error { emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1}) emit.AppCall(w.BinWriter, nnsHash, "getPrice", callflag.All) emit.Opcodes(w.BinWriter, opcode.STSFLD0) @@ -278,23 +269,46 @@ func (c *initializeContext) updateContracts() error { c.Command.Printf("NNS: Set %s -> %s\n", domain, cs.Hash.StringLE()) } } + return nil +} - groupKey := c.ContractWallet.Accounts[0].PrivateKey().PublicKey() - _, _, err = c.emitUpdateNNSGroupScript(w, nnsHash, groupKey) - if err != nil { - return err +func (c *initializeContext) deployAlphabetAccounts(nnsHash util.Uint160, w *io2.BufBinWriter, alphaCs *contractState) ([]any, error) { + var keysParam []any + + baseGroups := alphaCs.Manifest.Groups + + // alphabet contracts should be deployed by individual nodes to get different hashes. + for i, acc := range c.Accounts { + ctrHash, err := nnsResolveHash(c.ReadOnlyInvoker, nnsHash, getAlphabetNNSDomain(i)) + if err != nil { + return nil, fmt.Errorf("can't resolve hash for contract update: %w", err) + } + + keysParam = append(keysParam, acc.PrivateKey().PublicKey().Bytes()) + + params := c.getAlphabetDeployItems(i, len(c.Wallets)) + emit.Array(w.BinWriter, params...) + + alphaCs.Manifest.Groups = baseGroups + err = c.addManifestGroup(ctrHash, alphaCs) + if err != nil { + return nil, fmt.Errorf("can't sign manifest group: %v", err) + } + + emit.Bytes(w.BinWriter, alphaCs.RawManifest) + emit.Opcodes(w.BinWriter, opcode.LDSFLD0) + emit.Int(w.BinWriter, 3) + emit.Opcodes(w.BinWriter, opcode.PACK) + emit.AppCallNoArgs(w.BinWriter, ctrHash, updateMethodName, callflag.All) } - c.Command.Printf("NNS: Set %s -> %s\n", morphClient.NNSGroupKeyName, hex.EncodeToString(groupKey.Bytes())) - - emit.Opcodes(w.BinWriter, opcode.LDSFLD0) - emit.Int(w.BinWriter, 1) - emit.Opcodes(w.BinWriter, opcode.PACK) - emit.AppCallNoArgs(w.BinWriter, nnsHash, "setPrice", callflag.All) - if err := c.sendCommitteeTx(w.Bytes(), false); err != nil { - return err + if !strings.Contains(err.Error(), common.ErrAlreadyUpdated) { + return nil, err + } + c.Command.Println("Alphabet contracts are already updated.") } - return c.awaitTx() + + return keysParam, nil } func (c *initializeContext) deployContracts() error { diff --git a/cmd/frostfs-adm/internal/modules/morph/notary.go b/cmd/frostfs-adm/internal/modules/morph/notary.go index 24cfd7d14..2459f127b 100644 --- a/cmd/frostfs-adm/internal/modules/morph/notary.go +++ b/cmd/frostfs-adm/internal/modules/morph/notary.go @@ -9,10 +9,12 @@ import ( "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/encoding/address" + "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" "github.com/nspcc-dev/neo-go/pkg/rpcclient/gas" "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17" "github.com/nspcc-dev/neo-go/pkg/rpcclient/notary" + "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -22,18 +24,10 @@ import ( // https://github.com/nspcc-dev/neo-go/blob/master/pkg/core/native/notary.go#L48 const defaultNotaryDepositLifetime = 5760 -// nolint: funlen func depositNotary(cmd *cobra.Command, _ []string) error { - p, err := cmd.Flags().GetString(storageWalletFlag) + w, err := openWallet(cmd) if err != nil { return err - } else if p == "" { - return fmt.Errorf("missing wallet path (use '--%s ')", storageWalletFlag) - } - - w, err := wallet.NewWalletFromFile(p) - if err != nil { - return fmt.Errorf("can't open wallet: %v", err) } accHash := w.GetChangeAddress() @@ -81,6 +75,10 @@ func depositNotary(cmd *cobra.Command, _ []string) error { } } + return transferGas(cmd, acc, accHash, gasAmount, till) +} + +func transferGas(cmd *cobra.Command, acc *wallet.Account, accHash util.Uint160, gasAmount fixedn.Fixed8, till int64) error { c, err := getN3Client(viper.GetViper()) if err != nil { return err @@ -120,3 +118,18 @@ func depositNotary(cmd *cobra.Command, _ []string) error { return awaitTx(cmd, c, []hashVUBPair{{hash: txHash, vub: vub}}) } + +func openWallet(cmd *cobra.Command) (*wallet.Wallet, error) { + p, err := cmd.Flags().GetString(storageWalletFlag) + if err != nil { + return nil, err + } else if p == "" { + return nil, fmt.Errorf("missing wallet path (use '--%s ')", storageWalletFlag) + } + + w, err := wallet.NewWalletFromFile(p) + if err != nil { + return nil, fmt.Errorf("can't open wallet: %v", err) + } + return w, nil +} diff --git a/cmd/frostfs-adm/internal/modules/morph/root.go b/cmd/frostfs-adm/internal/modules/morph/root.go index 0b3ee7568..e92fd2160 100644 --- a/cmd/frostfs-adm/internal/modules/morph/root.go +++ b/cmd/frostfs-adm/internal/modules/morph/root.go @@ -238,12 +238,140 @@ var ( } ) -// nolint: funlen func init() { - RootCmd.AddCommand(generateAlphabetCmd) - generateAlphabetCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir") - generateAlphabetCmd.Flags().Uint(alphabetSizeFlag, 7, "Amount of alphabet wallets to generate") + initGenerateAlphabetCmd() + initInitCmd() + initDeployCmd() + initGenerateStorageCmd() + initForceNewEpochCmd() + initRemoveNodesCmd() + initSetPolicyCmd() + initDumpContractHashesCmd() + initDumpNetworkConfigCmd() + initSetConfigCmd() + initDumpBalancesCmd() + initUpdateContractsCmd() + initDumpContainersCmd() + initRestoreContainersCmd() + initListContainersCmd() + initRefillGasCmd() + initSubnetCmd() + initDepositoryNotaryCmd() + initNetmapCandidatesCmd() +} +func initNetmapCandidatesCmd() { + RootCmd.AddCommand(netmapCandidatesCmd) + netmapCandidatesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") +} + +func initDepositoryNotaryCmd() { + RootCmd.AddCommand(depositNotaryCmd) + depositNotaryCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") + depositNotaryCmd.Flags().String(storageWalletFlag, "", "Path to storage node wallet") + depositNotaryCmd.Flags().String(walletAccountFlag, "", "Wallet account address") + depositNotaryCmd.Flags().String(refillGasAmountFlag, "", "Amount of GAS to deposit") + 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") + refillGasCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") + refillGasCmd.Flags().String(storageWalletFlag, "", "Path to storage node wallet") + refillGasCmd.Flags().String(walletAddressFlag, "", "Address of wallet") + refillGasCmd.Flags().String(refillGasAmountFlag, "", "Additional amount of GAS to transfer") + refillGasCmd.MarkFlagsMutuallyExclusive(walletAddressFlag, storageWalletFlag) +} + +func initListContainersCmd() { + RootCmd.AddCommand(listContainersCmd) + listContainersCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") + listContainersCmd.Flags().String(containerContractFlag, "", "Container contract hash (for networks without NNS)") +} + +func initRestoreContainersCmd() { + RootCmd.AddCommand(restoreContainersCmd) + restoreContainersCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir") + restoreContainersCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") + restoreContainersCmd.Flags().String(containerDumpFlag, "", "File to restore containers from") + restoreContainersCmd.Flags().StringSlice(containerIDsFlag, nil, "Containers to restore") +} + +func initDumpContainersCmd() { + RootCmd.AddCommand(dumpContainersCmd) + dumpContainersCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") + dumpContainersCmd.Flags().String(containerDumpFlag, "", "File where to save dumped containers") + dumpContainersCmd.Flags().String(containerContractFlag, "", "Container contract hash (for networks without NNS)") + dumpContainersCmd.Flags().StringSlice(containerIDsFlag, nil, "Containers to dump") +} + +func initUpdateContractsCmd() { + RootCmd.AddCommand(updateContractsCmd) + updateContractsCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir") + updateContractsCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") + updateContractsCmd.Flags().String(contractsInitFlag, "", "Path to archive with compiled FrostFS contracts (default fetched from latest github release)") +} + +func initDumpBalancesCmd() { + RootCmd.AddCommand(dumpBalancesCmd) + dumpBalancesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") + dumpBalancesCmd.Flags().BoolP(dumpBalancesStorageFlag, "s", false, "Dump balances of storage nodes from the current netmap") + dumpBalancesCmd.Flags().BoolP(dumpBalancesAlphabetFlag, "a", false, "Dump balances of alphabet contracts") + dumpBalancesCmd.Flags().BoolP(dumpBalancesProxyFlag, "p", false, "Dump balances of the proxy contract") + dumpBalancesCmd.Flags().Bool(dumpBalancesUseScriptHashFlag, false, "Use script-hash format for addresses") +} + +func initSetConfigCmd() { + RootCmd.AddCommand(setConfig) + setConfig.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir") + setConfig.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") + setConfig.Flags().Bool(forceConfigSet, false, "Force setting not well-known configuration key") +} + +func initDumpNetworkConfigCmd() { + RootCmd.AddCommand(dumpNetworkConfigCmd) + dumpNetworkConfigCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") +} + +func initDumpContractHashesCmd() { + RootCmd.AddCommand(dumpContractHashesCmd) + dumpContractHashesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") + dumpContractHashesCmd.Flags().String(customZoneFlag, "", "Custom zone to search.") +} + +func initSetPolicyCmd() { + RootCmd.AddCommand(setPolicy) + setPolicy.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir") + setPolicy.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") +} + +func initRemoveNodesCmd() { + RootCmd.AddCommand(removeNodes) + removeNodes.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir") + removeNodes.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") +} + +func initForceNewEpochCmd() { + RootCmd.AddCommand(forceNewEpoch) + forceNewEpoch.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir") + forceNewEpoch.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") +} + +func initGenerateStorageCmd() { + RootCmd.AddCommand(generateStorageCmd) + generateStorageCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir") + generateStorageCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") + generateStorageCmd.Flags().String(storageWalletFlag, "", "Path to new storage node wallet") + generateStorageCmd.Flags().String(storageGasCLIFlag, "", "Initial amount of GAS to transfer") + generateStorageCmd.Flags().StringP(storageWalletLabelFlag, "l", "", "Wallet label") +} + +func initInitCmd() { RootCmd.AddCommand(initCmd) initCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir") initCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") @@ -256,85 +384,14 @@ func init() { initCmd.Flags().Uint64(containerAliasFeeCLIFlag, 500, "Container alias fee") initCmd.Flags().String(protoConfigPath, "", "Path to the consensus node configuration") initCmd.Flags().String(localDumpFlag, "", "Path to the blocks dump file") - - RootCmd.AddCommand(deployCmd) - - RootCmd.AddCommand(generateStorageCmd) - generateStorageCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir") - generateStorageCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") - generateStorageCmd.Flags().String(storageWalletFlag, "", "Path to new storage node wallet") - generateStorageCmd.Flags().String(storageGasCLIFlag, "", "Initial amount of GAS to transfer") - generateStorageCmd.Flags().StringP(storageWalletLabelFlag, "l", "", "Wallet label") - - RootCmd.AddCommand(forceNewEpoch) - forceNewEpoch.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir") - forceNewEpoch.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") - - RootCmd.AddCommand(removeNodes) - removeNodes.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir") - removeNodes.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") - - RootCmd.AddCommand(setPolicy) - setPolicy.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir") - setPolicy.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") - - RootCmd.AddCommand(dumpContractHashesCmd) - dumpContractHashesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") - dumpContractHashesCmd.Flags().String(customZoneFlag, "", "Custom zone to search.") - - RootCmd.AddCommand(dumpNetworkConfigCmd) - dumpNetworkConfigCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") - - RootCmd.AddCommand(setConfig) - setConfig.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir") - setConfig.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") - setConfig.Flags().Bool(forceConfigSet, false, "Force setting not well-known configuration key") - - RootCmd.AddCommand(dumpBalancesCmd) - dumpBalancesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") - dumpBalancesCmd.Flags().BoolP(dumpBalancesStorageFlag, "s", false, "Dump balances of storage nodes from the current netmap") - dumpBalancesCmd.Flags().BoolP(dumpBalancesAlphabetFlag, "a", false, "Dump balances of alphabet contracts") - dumpBalancesCmd.Flags().BoolP(dumpBalancesProxyFlag, "p", false, "Dump balances of the proxy contract") - dumpBalancesCmd.Flags().Bool(dumpBalancesUseScriptHashFlag, false, "Use script-hash format for addresses") - - RootCmd.AddCommand(updateContractsCmd) - updateContractsCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir") - updateContractsCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") - updateContractsCmd.Flags().String(contractsInitFlag, "", "Path to archive with compiled FrostFS contracts (default fetched from latest github release)") - - RootCmd.AddCommand(dumpContainersCmd) - dumpContainersCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") - dumpContainersCmd.Flags().String(containerDumpFlag, "", "File where to save dumped containers") - dumpContainersCmd.Flags().String(containerContractFlag, "", "Container contract hash (for networks without NNS)") - dumpContainersCmd.Flags().StringSlice(containerIDsFlag, nil, "Containers to dump") - - RootCmd.AddCommand(restoreContainersCmd) - restoreContainersCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir") - restoreContainersCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") - restoreContainersCmd.Flags().String(containerDumpFlag, "", "File to restore containers from") - restoreContainersCmd.Flags().StringSlice(containerIDsFlag, nil, "Containers to restore") - - RootCmd.AddCommand(listContainersCmd) - listContainersCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") - listContainersCmd.Flags().String(containerContractFlag, "", "Container contract hash (for networks without NNS)") - - RootCmd.AddCommand(refillGasCmd) - refillGasCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir") - refillGasCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") - refillGasCmd.Flags().String(storageWalletFlag, "", "Path to storage node wallet") - refillGasCmd.Flags().String(walletAddressFlag, "", "Address of wallet") - refillGasCmd.Flags().String(refillGasAmountFlag, "", "Additional amount of GAS to transfer") - refillGasCmd.MarkFlagsMutuallyExclusive(walletAddressFlag, storageWalletFlag) - - RootCmd.AddCommand(cmdSubnet) - - RootCmd.AddCommand(depositNotaryCmd) - depositNotaryCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") - depositNotaryCmd.Flags().String(storageWalletFlag, "", "Path to storage node wallet") - depositNotaryCmd.Flags().String(walletAccountFlag, "", "Wallet account address") - depositNotaryCmd.Flags().String(refillGasAmountFlag, "", "Amount of GAS to deposit") - depositNotaryCmd.Flags().String(notaryDepositTillFlag, "", "Notary deposit duration in blocks") - - RootCmd.AddCommand(netmapCandidatesCmd) - netmapCandidatesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") +} + +func initGenerateAlphabetCmd() { + RootCmd.AddCommand(generateAlphabetCmd) + generateAlphabetCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir") + generateAlphabetCmd.Flags().Uint(alphabetSizeFlag, 7, "Amount of alphabet wallets to generate") +} + +func initDeployCmd() { + RootCmd.AddCommand(deployCmd) } diff --git a/cmd/frostfs-adm/internal/modules/morph/subnet.go b/cmd/frostfs-adm/internal/modules/morph/subnet.go index f289400ab..bdead7730 100644 --- a/cmd/frostfs-adm/internal/modules/morph/subnet.go +++ b/cmd/frostfs-adm/internal/modules/morph/subnet.go @@ -307,8 +307,6 @@ const ( ) // common executor cmdSubnetAdminAdd and cmdSubnetAdminRemove commands. -// -// nolint: funlen func manageSubnetAdmins(cmd *cobra.Command, rm bool) error { // read private key var key keys.PrivateKey @@ -341,17 +339,19 @@ func manageSubnetAdmins(cmd *cobra.Command, rm bool) error { return fmt.Errorf("admin key format: %w", err) } - // prepare call parameters + 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) { - // read group ID and encode it var groupID internal.SubnetClientGroupID - err = groupID.UnmarshalText([]byte(viper.GetString(flagSubnetAdminAddGroup))) + err := groupID.UnmarshalText([]byte(viper.GetString(flagSubnetAdminAddGroup))) if err != nil { return fmt.Errorf("decode group ID text: %w", err) } @@ -378,7 +378,7 @@ func manageSubnetAdmins(cmd *cobra.Command, rm bool) error { prm = append(prm, binAdminKey) - err = invokeMethod(key, false, method, prm...) + err := invokeMethod(key, false, method, prm...) if err != nil { return fmt.Errorf("morph invocation: %w", err) } @@ -653,55 +653,14 @@ func addCommandInheritPreRun(par *cobra.Command, subs ...*cobra.Command) { } // registers flags and binds sub-commands for subnet commands. -// -// nolint: funlen func init() { - cmdSubnetCreate.Flags().StringP(flagSubnetWallet, "w", "", "Path to file with wallet") - _ = cmdSubnetCreate.MarkFlagRequired(flagSubnetWallet) - cmdSubnetCreate.Flags().StringP(flagSubnetAddress, "a", "", "Address in the wallet, optional") - - // get subnet flags - cmdSubnetGet.Flags().String(flagSubnetGetID, "", "ID of the subnet to read") - _ = cmdSubnetAdminAdd.MarkFlagRequired(flagSubnetGetID) - - // remove subnet flags - 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") - - // subnet administer flags - 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") - - // add admin flags - 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") - - // remove admin flags - cmdSubnetAdminRemoveFlags := cmdSubnetAdminRemove.Flags() - cmdSubnetAdminRemoveFlags.Bool(flagSubnetAdminClient, false, "Remove client admin instead of node one") - - // client managements flags - 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") + initCreateSubnetFlags() + initGetSubnetFlags() + initRemoveSubnetFlags() + initSubnetAdminFlags() + initSubnetAdminAddFlags() + initSubnetAdminRemoveFlags() + initClientManagementFlags() // add all admin managing commands to corresponding command section addCommandInheritPreRun(cmdSubnetAdmin, @@ -715,14 +674,7 @@ func init() { cmdSubnetClientRemove, ) - // subnet node flags - 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) + initSubnetNodeFlags() // add all node managing commands to corresponding command section addCommandInheritPreRun(cmdSubnetNode, @@ -730,10 +682,7 @@ func init() { cmdSubnetNodeRemove, ) - // subnet global flags - cmdSubnetFlags := cmdSubnet.PersistentFlags() - cmdSubnetFlags.StringP(endpointFlag, "r", "", "N3 RPC node endpoint") - _ = cmdSubnet.MarkFlagRequired(endpointFlag) + initSubnetGlobalFlags() // add all subnet commands to corresponding command section addCommandInheritPreRun(cmdSubnet, @@ -746,6 +695,77 @@ func init() { ) } +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 { @@ -872,7 +892,6 @@ func invokeNonNotary(c Client, key keys.PrivateKey, method string, args ...any) return nil } -// nolint: funlen func invokeNotary(c Client, key keys.PrivateKey, method string, notaryHash util.Uint160, args ...any) error { nnsCs, err := c.GetContractStateByID(1) if err != nil { @@ -901,13 +920,7 @@ func invokeNotary(c Client, key keys.PrivateKey, method string, notaryHash util. return fmt.Errorf("subnet hash resolving: %w", err) } - // make test invocation of the method - test, err := inv.Call(subnetHash, method, args...) - if err != nil { - return fmt.Errorf("test invocation: %w", err) - } - - err = checkInvocationResults(test) + test, err := makeTestInvocation(inv, subnetHash, method, args) if err != nil { return err } @@ -923,7 +936,26 @@ func invokeNotary(c Client, key keys.PrivateKey, method string, notaryHash util. return fmt.Errorf("blockchain height: %w", err) } - signersNumber := uint8(smartcontract.GetDefaultHonestNodeCount(len(alphabet)) + 1) // alphabet multisig + key signature + 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 @@ -932,7 +964,7 @@ func invokeNotary(c Client, key keys.PrivateKey, method string, notaryHash util. mainTx := &transaction.Transaction{ Nonce: rand.Uint32(), SystemFee: test.GasConsumed, - ValidUntilBlock: bc + notaryRequestValidity, + ValidUntilBlock: blockCount + notaryRequestValidity, Script: test.Script, Attributes: []transaction.Attribute{ { @@ -943,7 +975,7 @@ func invokeNotary(c Client, key keys.PrivateKey, method string, notaryHash util. Signers: cosigners, } - notaryFee, err := c.CalculateNotaryFee(signersNumber) + notaryFee, err := client.CalculateNotaryFee(signersNumber) if err != nil { return err } @@ -951,14 +983,14 @@ func invokeNotary(c Client, key keys.PrivateKey, method string, notaryHash util. acc := wallet.NewAccountFromPrivateKey(&key) aa := notaryAccounts(multisigAccount, acc) - err = c.AddNetworkFee(mainTx, notaryFee, aa...) + err = client.AddNetworkFee(mainTx, notaryFee, aa...) if err != nil { return fmt.Errorf("notary network fee adding: %w", err) } - mainTx.Scripts = notaryWitnesses(c, multisigAccount, acc, mainTx) + mainTx.Scripts = notaryWitnesses(client, multisigAccount, acc, mainTx) - _, err = c.SignAndPushP2PNotaryRequest(mainTx, + _, err = client.SignAndPushP2PNotaryRequest(mainTx, []byte{byte(opcode.RET)}, -1, 0, diff --git a/cmd/frostfs-adm/internal/modules/storagecfg/root.go b/cmd/frostfs-adm/internal/modules/storagecfg/root.go index d08eb6cc9..90edf7d75 100644 --- a/cmd/frostfs-adm/internal/modules/storagecfg/root.go +++ b/cmd/frostfs-adm/internal/modules/storagecfg/root.go @@ -79,17 +79,8 @@ type config struct { MetabasePath string } -// nolint: funlen func storageConfig(cmd *cobra.Command, args []string) { - var outPath string - if len(args) != 0 { - outPath = args[0] - } else { - outPath = getPath("File to write config at [./config.yml]: ") - if outPath == "" { - outPath = "./config.yml" - } - } + outPath := getOutputPath(args) historyPath := filepath.Join(os.TempDir(), "frostfs-adm.history") readline.SetHistoryPath(historyPath) @@ -104,14 +95,7 @@ func storageConfig(cmd *cobra.Command, args []string) { w, err := wallet.NewWalletFromFile(c.Wallet.Path) fatalOnErr(err) - c.Wallet.Account, _ = cmd.Flags().GetString(accountFlag) - if c.Wallet.Account == "" { - addr := address.Uint160ToString(w.GetChangeAddress()) - c.Wallet.Account = getWalletAccount(w, fmt.Sprintf("Wallet account [%s]: ", addr)) - if c.Wallet.Account == "" { - c.Wallet.Account = addr - } - } + fillWalletAccount(cmd, &c, w) accH, err := flags.ParseAddress(c.Wallet.Account) fatalOnErr(err) @@ -129,25 +113,44 @@ func storageConfig(cmd *cobra.Command, args []string) { c.AuthorizedKeys = append(c.AuthorizedKeys, hex.EncodeToString(acc.PrivateKey().PublicKey().Bytes())) - var network string - for { - network = getString("Choose network [mainnet]/testnet: ") - switch network { - case "": - network = "mainnet" - case "testnet", "mainnet": - default: - cmd.Println(`Network must be either "mainnet" or "testnet"`) - continue - } - break - } + network := readNetwork(cmd) c.MorphRPC = n3config[network].MorphRPC depositGas(cmd, acc, network) c.Attribute.Locode = getString("UN-LOCODE attribute in [XX YYY] format: ") + + endpoint := getDefaultEndpoint(cmd, &c) + c.Endpoint = getString(fmt.Sprintf("Listening address [%s]: ", endpoint)) + if c.Endpoint == "" { + c.Endpoint = endpoint + } + + c.ControlEndpoint = getString(fmt.Sprintf("Listening address (control endpoint) [%s]: ", defaultControlEndpoint)) + if c.ControlEndpoint == "" { + c.ControlEndpoint = defaultControlEndpoint + } + + c.TLSCert = getPath("TLS Certificate (optional): ") + if c.TLSCert != "" { + c.TLSKey = getPath("TLS Key: ") + } + + c.Relay = getConfirmation(false, "Use node as a relay? yes/[no]: ") + if !c.Relay { + p := getPath("Path to the storage directory (all available storage will be used): ") + c.BlobstorPath = filepath.Join(p, "blob") + c.MetabasePath = filepath.Join(p, "meta") + } + + out := applyTemplate(c) + fatalOnErr(os.WriteFile(outPath, out, 0644)) + + cmd.Println("Node is ready for work! Run `frostfs-node -config " + outPath + "`") +} + +func getDefaultEndpoint(cmd *cobra.Command, c *config) string { var addr, port string for { c.AnnouncedAddress = getString("Publicly announced address: ") @@ -183,34 +186,46 @@ func storageConfig(cmd *cobra.Command, args []string) { break } + return net.JoinHostPort(defaultDataEndpoint, port) +} - defaultAddr := net.JoinHostPort(defaultDataEndpoint, port) - c.Endpoint = getString(fmt.Sprintf("Listening address [%s]: ", defaultAddr)) - if c.Endpoint == "" { - c.Endpoint = defaultAddr +func fillWalletAccount(cmd *cobra.Command, c *config, w *wallet.Wallet) { + c.Wallet.Account, _ = cmd.Flags().GetString(accountFlag) + if c.Wallet.Account == "" { + addr := address.Uint160ToString(w.GetChangeAddress()) + c.Wallet.Account = getWalletAccount(w, fmt.Sprintf("Wallet account [%s]: ", addr)) + if c.Wallet.Account == "" { + c.Wallet.Account = addr + } } +} - c.ControlEndpoint = getString(fmt.Sprintf("Listening address (control endpoint) [%s]: ", defaultControlEndpoint)) - if c.ControlEndpoint == "" { - c.ControlEndpoint = defaultControlEndpoint +func readNetwork(cmd *cobra.Command) string { + var network string + for { + network = getString("Choose network [mainnet]/testnet: ") + switch network { + case "": + network = "mainnet" + case "testnet", "mainnet": + default: + cmd.Println(`Network must be either "mainnet" or "testnet"`) + continue + } + break } + return network +} - c.TLSCert = getPath("TLS Certificate (optional): ") - if c.TLSCert != "" { - c.TLSKey = getPath("TLS Key: ") +func getOutputPath(args []string) string { + if len(args) != 0 { + return args[0] } - - c.Relay = getConfirmation(false, "Use node as a relay? yes/[no]: ") - if !c.Relay { - p := getPath("Path to the storage directory (all available storage will be used): ") - c.BlobstorPath = filepath.Join(p, "blob") - c.MetabasePath = filepath.Join(p, "meta") + outPath := getPath("File to write config at [./config.yml]: ") + if outPath == "" { + outPath = "./config.yml" } - - out := applyTemplate(c) - fatalOnErr(os.WriteFile(outPath, out, 0644)) - - cmd.Println("Node is ready for work! Run `frostfs-node -config " + outPath + "`") + return outPath } func getWalletAccount(w *wallet.Wallet, prompt string) string {