forked from TrueCloudLab/frostfs-node
[#932] adm: Move generate.go
to package generate
Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
This commit is contained in:
parent
b8cf0a6b88
commit
86b2515744
6 changed files with 104 additions and 92 deletions
215
cmd/frostfs-adm/internal/modules/morph/generate/generate.go
Normal file
215
cmd/frostfs-adm/internal/modules/morph/generate/generate.go
Normal file
|
@ -0,0 +1,215 @@
|
|||
package generate
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
morphUtil "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/util"
|
||||
"git.frostfs.info/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/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"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
func AlphabetCreds(cmd *cobra.Command, _ []string) error {
|
||||
// alphabet size is not part of the config
|
||||
size, err := cmd.Flags().GetUint(morphUtil.AlphabetSizeFlag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if size == 0 {
|
||||
return errors.New("size must be > 0")
|
||||
}
|
||||
if size > morphUtil.MaxAlphabetNodes {
|
||||
return morphUtil.ErrTooManyAlphabetNodes
|
||||
}
|
||||
|
||||
v := viper.GetViper()
|
||||
walletDir := config.ResolveHomePath(viper.GetString(morphUtil.AlphabetWalletsFlag))
|
||||
pwds, err := initializeWallets(v, walletDir, int(size))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = morphUtil.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, 0o644)
|
||||
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(morphUtil.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()
|
||||
}
|
||||
|
||||
var errG errgroup.Group
|
||||
|
||||
// Create committee account with N/2+1 multi-signature.
|
||||
majCount := smartcontract.GetMajorityHonestNodeCount(size)
|
||||
// Create consensus account with 2*N/3+1 multi-signature.
|
||||
bftCount := smartcontract.GetDefaultHonestNodeCount(size)
|
||||
for i := range wallets {
|
||||
i := i
|
||||
ps := pubs.Copy()
|
||||
errG.Go(func() error {
|
||||
if err := addMultisigAccount(wallets[i], majCount, morphUtil.CommitteeAccountName, passwords[i], ps); err != nil {
|
||||
return fmt.Errorf("can't create committee account: %w", err)
|
||||
}
|
||||
if err := addMultisigAccount(wallets[i], bftCount, morphUtil.ConsensusAccountName, passwords[i], ps); err != nil {
|
||||
return fmt.Errorf("can't create consentus account: %w", err)
|
||||
}
|
||||
if err := wallets[i].SavePretty(); err != nil {
|
||||
return fmt.Errorf("can't save wallet: %w", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if err := errG.Wait(); err != nil {
|
||||
return nil, 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(morphUtil.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>')", morphUtil.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 = morphUtil.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 := morphUtil.ParseGASAmount(gasStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wCtx, err := morphUtil.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()
|
||||
}
|
120
cmd/frostfs-adm/internal/modules/morph/generate/generate_test.go
Normal file
120
cmd/frostfs-adm/internal/modules/morph/generate/generate_test.go
Normal file
|
@ -0,0 +1,120 @@
|
|||
package generate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/util"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||
"github.com/nspcc-dev/neo-go/cli/input"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
func TestGenerateAlphabet(t *testing.T) {
|
||||
const size = 4
|
||||
|
||||
walletDir := t.TempDir()
|
||||
buf := setupTestTerminal(t)
|
||||
|
||||
cmd := GenerateAlphabetCmd
|
||||
v := viper.GetViper()
|
||||
|
||||
t.Run("zero size", func(t *testing.T) {
|
||||
buf.Reset()
|
||||
v.Set(util.AlphabetWalletsFlag, walletDir)
|
||||
require.NoError(t, cmd.Flags().Set(util.AlphabetSizeFlag, "0"))
|
||||
buf.WriteString("pass\r")
|
||||
require.Error(t, AlphabetCreds(cmd, nil))
|
||||
})
|
||||
t.Run("no password provided", func(t *testing.T) {
|
||||
buf.Reset()
|
||||
v.Set(util.AlphabetWalletsFlag, walletDir)
|
||||
require.NoError(t, cmd.Flags().Set(util.AlphabetSizeFlag, "1"))
|
||||
require.Error(t, AlphabetCreds(cmd, nil))
|
||||
})
|
||||
t.Run("missing directory", func(t *testing.T) {
|
||||
buf.Reset()
|
||||
dir := filepath.Join(os.TempDir(), "notexist."+strconv.FormatUint(rand.Uint64(), 10))
|
||||
v.Set(util.AlphabetWalletsFlag, dir)
|
||||
require.NoError(t, cmd.Flags().Set(util.AlphabetSizeFlag, "1"))
|
||||
buf.WriteString("pass\r")
|
||||
require.Error(t, AlphabetCreds(cmd, nil))
|
||||
})
|
||||
t.Run("no password for contract group wallet", func(t *testing.T) {
|
||||
buf.Reset()
|
||||
v.Set(util.AlphabetWalletsFlag, walletDir)
|
||||
require.NoError(t, cmd.Flags().Set(util.AlphabetSizeFlag, strconv.FormatUint(size, 10)))
|
||||
for i := uint64(0); i < size; i++ {
|
||||
buf.WriteString(strconv.FormatUint(i, 10) + "\r")
|
||||
}
|
||||
require.Error(t, AlphabetCreds(cmd, nil))
|
||||
})
|
||||
|
||||
buf.Reset()
|
||||
v.Set(util.AlphabetWalletsFlag, walletDir)
|
||||
require.NoError(t, GenerateAlphabetCmd.Flags().Set(util.AlphabetSizeFlag, strconv.FormatUint(size, 10)))
|
||||
for i := uint64(0); i < size; i++ {
|
||||
buf.WriteString(strconv.FormatUint(i, 10) + "\r")
|
||||
}
|
||||
|
||||
buf.WriteString(util.TestContractPassword + "\r")
|
||||
require.NoError(t, AlphabetCreds(GenerateAlphabetCmd, nil))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := uint64(0); i < size; i++ {
|
||||
i := i
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
p := filepath.Join(walletDir, innerring.GlagoliticLetter(i).String()+".json")
|
||||
w, err := wallet.NewWalletFromFile(p)
|
||||
require.NoError(t, err, "wallet doesn't exist")
|
||||
require.Equal(t, 3, len(w.Accounts), "not all accounts were created")
|
||||
|
||||
for _, a := range w.Accounts {
|
||||
err := a.Decrypt(strconv.FormatUint(i, 10), keys.NEP2ScryptParams())
|
||||
require.NoError(t, err, "can't decrypt account")
|
||||
switch a.Label {
|
||||
case util.ConsensusAccountName:
|
||||
require.Equal(t, smartcontract.GetDefaultHonestNodeCount(size), len(a.Contract.Parameters))
|
||||
case util.CommitteeAccountName:
|
||||
require.Equal(t, smartcontract.GetMajorityHonestNodeCount(size), len(a.Contract.Parameters))
|
||||
default:
|
||||
require.Equal(t, util.SingleAccountName, a.Label)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
t.Run("check contract group wallet", func(t *testing.T) {
|
||||
p := filepath.Join(walletDir, util.ContractWalletFilename)
|
||||
w, err := wallet.NewWalletFromFile(p)
|
||||
require.NoError(t, err, "contract wallet doesn't exist")
|
||||
require.Equal(t, 1, len(w.Accounts), "contract wallet must have 1 accout")
|
||||
require.NoError(t, w.Accounts[0].Decrypt(util.TestContractPassword, keys.NEP2ScryptParams()))
|
||||
})
|
||||
}
|
||||
|
||||
func setupTestTerminal(t *testing.T) *bytes.Buffer {
|
||||
in := bytes.NewBuffer(nil)
|
||||
input.Terminal = term.NewTerminal(input.ReadWriter{
|
||||
Reader: in,
|
||||
Writer: io.Discard,
|
||||
}, "")
|
||||
|
||||
t.Cleanup(func() { input.Terminal = nil })
|
||||
|
||||
return in
|
||||
}
|
76
cmd/frostfs-adm/internal/modules/morph/generate/root.go
Normal file
76
cmd/frostfs-adm/internal/modules/morph/generate/root.go
Normal file
|
@ -0,0 +1,76 @@
|
|||
package generate
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/util"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
storageWalletLabelFlag = "label"
|
||||
storageGasCLIFlag = "initial-gas"
|
||||
storageGasConfigFlag = "storage.initial_gas"
|
||||
walletAddressFlag = "wallet-address"
|
||||
)
|
||||
|
||||
var (
|
||||
GenerateStorageCmd = &cobra.Command{
|
||||
Use: "generate-storage-wallet",
|
||||
Short: "Generate storage node wallet for the morph network",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(util.AlphabetWalletsFlag, cmd.Flags().Lookup(util.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(util.EndpointFlag, cmd.Flags().Lookup(util.EndpointFlag))
|
||||
_ = viper.BindPFlag(storageGasConfigFlag, cmd.Flags().Lookup(storageGasCLIFlag))
|
||||
},
|
||||
RunE: generateStorageCreds,
|
||||
}
|
||||
RefillGasCmd = &cobra.Command{
|
||||
Use: "refill-gas",
|
||||
Short: "Refill GAS of storage node's wallet in the morph network",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(util.AlphabetWalletsFlag, cmd.Flags().Lookup(util.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(util.EndpointFlag, cmd.Flags().Lookup(util.EndpointFlag))
|
||||
_ = viper.BindPFlag(util.RefillGasAmountFlag, cmd.Flags().Lookup(util.RefillGasAmountFlag))
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return refillGas(cmd, util.RefillGasAmountFlag, false)
|
||||
},
|
||||
}
|
||||
GenerateAlphabetCmd = &cobra.Command{
|
||||
Use: "generate-alphabet",
|
||||
Short: "Generate alphabet wallets for consensus nodes of the morph network",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
// PreRun fixes https://github.com/spf13/viper/issues/233
|
||||
_ = viper.BindPFlag(util.AlphabetWalletsFlag, cmd.Flags().Lookup(util.AlphabetWalletsFlag))
|
||||
},
|
||||
RunE: AlphabetCreds,
|
||||
}
|
||||
)
|
||||
|
||||
func initRefillGasCmd() {
|
||||
RefillGasCmd.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc)
|
||||
RefillGasCmd.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc)
|
||||
RefillGasCmd.Flags().String(util.StorageWalletFlag, "", "Path to storage node wallet")
|
||||
RefillGasCmd.Flags().String(walletAddressFlag, "", "Address of wallet")
|
||||
RefillGasCmd.Flags().String(util.RefillGasAmountFlag, "", "Additional amount of GAS to transfer")
|
||||
RefillGasCmd.MarkFlagsMutuallyExclusive(walletAddressFlag, util.StorageWalletFlag)
|
||||
}
|
||||
|
||||
func initGenerateStorageCmd() {
|
||||
GenerateStorageCmd.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc)
|
||||
GenerateStorageCmd.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc)
|
||||
GenerateStorageCmd.Flags().String(util.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 initGenerateAlphabetCmd() {
|
||||
GenerateAlphabetCmd.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc)
|
||||
GenerateAlphabetCmd.Flags().Uint(util.AlphabetSizeFlag, 7, "Amount of alphabet wallets to generate")
|
||||
}
|
||||
|
||||
func init() {
|
||||
initRefillGasCmd()
|
||||
initGenerateStorageCmd()
|
||||
initGenerateAlphabetCmd()
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue