package morph import ( "errors" "fmt" "os" "path/filepath" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config" "github.com/TrueCloudLab/frostfs-node/pkg/innerring" "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/io" "github.com/nspcc-dev/neo-go/pkg/rpcclient/gas" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/spf13/cobra" "github.com/spf13/viper" ) const ( singleAccountName = "single" committeeAccountName = "committee" consensusAccountName = "consensus" ) func generateAlphabetCreds(cmd *cobra.Command, args []string) error { // alphabet size is not part of the config size, err := cmd.Flags().GetUint(alphabetSizeFlag) if err != nil { return err } if size == 0 { return errors.New("size must be > 0") } v := viper.GetViper() walletDir := config.ResolveHomePath(viper.GetString(alphabetWalletsFlag)) pwds, err := initializeWallets(v, walletDir, int(size)) if err != nil { return err } _, err = initializeContractWallet(v, walletDir) if err != nil { return err } cmd.Println("size:", size) cmd.Println("alphabet-wallets:", walletDir) for i := range pwds { cmd.Printf("wallet[%d]: %s\n", i, pwds[i]) } return nil } func initializeWallets(v *viper.Viper, walletDir string, size int) ([]string, error) { wallets := make([]*wallet.Wallet, size) pubs := make(keys.PublicKeys, size) passwords := make([]string, size) for i := range wallets { password, err := config.GetPassword(v, innerring.GlagoliticLetter(i).String()) if err != nil { return nil, fmt.Errorf("can't fetch password: %w", err) } p := filepath.Join(walletDir, innerring.GlagoliticLetter(i).String()+".json") f, err := os.OpenFile(p, os.O_CREATE, 0644) if err != nil { return nil, fmt.Errorf("can't create wallet file: %w", err) } if err := f.Close(); err != nil { return nil, fmt.Errorf("can't close wallet file: %w", err) } w, err := wallet.NewWallet(p) if err != nil { return nil, fmt.Errorf("can't create wallet: %w", err) } if err := w.CreateAccount(singleAccountName, password); err != nil { return nil, fmt.Errorf("can't create account: %w", err) } passwords[i] = password wallets[i] = w pubs[i] = w.Accounts[0].PrivateKey().PublicKey() } // Create committee account with N/2+1 multi-signature. majCount := smartcontract.GetMajorityHonestNodeCount(size) for i, w := range wallets { if err := addMultisigAccount(w, majCount, committeeAccountName, passwords[i], pubs); err != nil { return nil, fmt.Errorf("can't create committee account: %w", err) } } // Create consensus account with 2*N/3+1 multi-signature. bftCount := smartcontract.GetDefaultHonestNodeCount(size) for i, w := range wallets { if err := addMultisigAccount(w, bftCount, consensusAccountName, passwords[i], pubs); err != nil { return nil, fmt.Errorf("can't create consensus account: %w", err) } } for _, w := range wallets { if err := w.SavePretty(); err != nil { return nil, fmt.Errorf("can't save wallet: %w", err) } } return passwords, nil } func addMultisigAccount(w *wallet.Wallet, m int, name, password string, pubs keys.PublicKeys) error { acc := wallet.NewAccountFromPrivateKey(w.Accounts[0].PrivateKey()) acc.Label = name if err := acc.ConvertMultisig(m, pubs); err != nil { return err } if err := acc.Encrypt(password, keys.NEP2ScryptParams()); err != nil { return err } w.AddAccount(acc) return nil } func generateStorageCreds(cmd *cobra.Command, _ []string) error { return refillGas(cmd, storageGasConfigFlag, true) } func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) (err error) { // storage wallet path is not part of the config storageWalletPath, _ := cmd.Flags().GetString(storageWalletFlag) // wallet address is not part of the config walletAddress, _ := cmd.Flags().GetString(walletAddressFlag) var gasReceiver util.Uint160 if len(walletAddress) != 0 { gasReceiver, err = address.StringToUint160(walletAddress) if err != nil { return fmt.Errorf("invalid wallet address %s: %w", walletAddress, err) } } else { if storageWalletPath == "" { return fmt.Errorf("missing wallet path (use '--%s <out.json>')", storageWalletFlag) } var w *wallet.Wallet if createWallet { w, err = wallet.NewWallet(storageWalletPath) } else { w, err = wallet.NewWalletFromFile(storageWalletPath) } if err != nil { return fmt.Errorf("can't create wallet: %w", err) } if createWallet { var password string label, _ := cmd.Flags().GetString(storageWalletLabelFlag) password, err := config.GetStoragePassword(viper.GetViper(), label) if err != nil { return fmt.Errorf("can't fetch password: %w", err) } if label == "" { label = singleAccountName } if err := w.CreateAccount(label, password); err != nil { return fmt.Errorf("can't create account: %w", err) } } gasReceiver = w.Accounts[0].Contract.ScriptHash() } gasStr := viper.GetString(gasFlag) gasAmount, err := parseGASAmount(gasStr) if err != nil { return err } wCtx, err := newInitializeContext(cmd, viper.GetViper()) if err != nil { return err } bw := io.NewBufBinWriter() emit.AppCall(bw.BinWriter, gas.Hash, "transfer", callflag.All, wCtx.CommitteeAcc.Contract.ScriptHash(), gasReceiver, int64(gasAmount), nil) emit.Opcodes(bw.BinWriter, opcode.ASSERT) if bw.Err != nil { return fmt.Errorf("BUG: invalid transfer arguments: %w", bw.Err) } if err := wCtx.sendCommitteeTx(bw.Bytes(), false); err != nil { return err } return wCtx.awaitTx() } func parseGASAmount(s string) (fixedn.Fixed8, error) { gasAmount, err := fixedn.Fixed8FromString(s) if err != nil { return 0, fmt.Errorf("invalid GAS amount %s: %w", s, err) } if gasAmount <= 0 { return 0, fmt.Errorf("GAS amount must be positive (got %d)", gasAmount) } return gasAmount, nil }