adm: Refactor morph
command #950
62 changed files with 3005 additions and 2735 deletions
|
@ -11,4 +11,29 @@ const (
|
|||
Verbose = "verbose"
|
||||
VerboseShorthand = "v"
|
||||
VerboseUsage = "Verbose output"
|
||||
|
||||
EndpointFlag = "rpc-endpoint"
|
||||
EndpointFlagDesc = "N3 RPC node endpoint"
|
||||
EndpointFlagShort = "r"
|
||||
|
||||
AlphabetWalletsFlag = "alphabet-wallets"
|
||||
AlphabetWalletsFlagDesc = "Path to alphabet wallets dir"
|
||||
|
||||
LocalDumpFlag = "local-dump"
|
||||
ContractsInitFlag = "contracts"
|
||||
ContractsInitFlagDesc = "Path to archive with compiled FrostFS contracts (the default is to fetch the latest release from the official repository)"
|
||||
ContractsURLFlag = "contracts-url"
|
||||
ContractsURLFlagDesc = "URL to archive with compiled FrostFS contracts"
|
||||
EpochDurationInitFlag = "network.epoch_duration"
|
||||
MaxObjectSizeInitFlag = "network.max_object_size"
|
||||
RefillGasAmountFlag = "gas"
|
||||
StorageWalletFlag = "storage-wallet"
|
||||
ContainerFeeInitFlag = "network.fee.container"
|
||||
ContainerAliasFeeInitFlag = "network.fee.container_alias"
|
||||
CandidateFeeInitFlag = "network.fee.candidate"
|
||||
WithdrawFeeInitFlag = "network.fee.withdraw"
|
||||
MaintenanceModeAllowedInitFlag = "network.maintenance_mode_allowed"
|
||||
HomomorphicHashDisabledInitFlag = "network.homomorphic_hash_disabled"
|
||||
CustomZoneFlag = "domain"
|
||||
AlphabetSizeFlag = "size"
|
||||
)
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package morph
|
||||
package ape
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
parseutil "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
|
@ -32,17 +33,12 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
apeCmd = &cobra.Command{
|
||||
Use: "ape",
|
||||
Short: "Section for APE configuration commands",
|
||||
}
|
||||
|
||||
addRuleChainCmd = &cobra.Command{
|
||||
Use: "add-rule-chain",
|
||||
Short: "Add rule chain",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
},
|
||||
Run: addRuleChain,
|
||||
}
|
||||
|
@ -51,8 +47,8 @@ var (
|
|||
Use: "rm-rule-chain",
|
||||
Short: "Remove rule chain",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
},
|
||||
Run: removeRuleChain,
|
||||
}
|
||||
|
@ -61,8 +57,8 @@ var (
|
|||
Use: "list-rule-chains",
|
||||
Short: "List rule chains",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
},
|
||||
Run: listRuleChains,
|
||||
}
|
||||
|
@ -71,8 +67,8 @@ var (
|
|||
Use: "set-admin",
|
||||
Short: "Set admin",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
},
|
||||
Run: setAdmin,
|
||||
}
|
||||
|
@ -81,18 +77,18 @@ var (
|
|||
Use: "get-admin",
|
||||
Short: "Get admin",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
},
|
||||
Run: getAdmin,
|
||||
}
|
||||
)
|
||||
|
||||
func initAddRuleChainCmd() {
|
||||
apeCmd.AddCommand(addRuleChainCmd)
|
||||
Cmd.AddCommand(addRuleChainCmd)
|
||||
|
||||
addRuleChainCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
addRuleChainCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
addRuleChainCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
addRuleChainCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
|
||||
addRuleChainCmd.Flags().String(targetTypeFlag, "", targetTypeDesc)
|
||||
_ = addRuleChainCmd.MarkFlagRequired(targetTypeFlag)
|
||||
|
@ -108,10 +104,10 @@ func initAddRuleChainCmd() {
|
|||
}
|
||||
|
||||
func initRemoveRuleChainCmd() {
|
||||
apeCmd.AddCommand(removeRuleChainCmd)
|
||||
Cmd.AddCommand(removeRuleChainCmd)
|
||||
|
||||
removeRuleChainCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
removeRuleChainCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
removeRuleChainCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
removeRuleChainCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
|
||||
removeRuleChainCmd.Flags().String(targetTypeFlag, "", targetTypeDesc)
|
||||
_ = removeRuleChainCmd.MarkFlagRequired(targetTypeFlag)
|
||||
|
@ -122,10 +118,10 @@ func initRemoveRuleChainCmd() {
|
|||
}
|
||||
|
||||
func initListRuleChainsCmd() {
|
||||
apeCmd.AddCommand(listRuleChainsCmd)
|
||||
Cmd.AddCommand(listRuleChainsCmd)
|
||||
|
||||
listRuleChainsCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
listRuleChainsCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
listRuleChainsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
listRuleChainsCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
listRuleChainsCmd.Flags().StringP(targetTypeFlag, "t", "", targetTypeDesc)
|
||||
_ = listRuleChainsCmd.MarkFlagRequired(targetTypeFlag)
|
||||
listRuleChainsCmd.Flags().String(targetNameFlag, "", targetNameDesc)
|
||||
|
@ -134,19 +130,19 @@ func initListRuleChainsCmd() {
|
|||
}
|
||||
|
||||
func initSetAdminCmd() {
|
||||
apeCmd.AddCommand(setAdminCmd)
|
||||
Cmd.AddCommand(setAdminCmd)
|
||||
|
||||
setAdminCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
setAdminCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
setAdminCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
setAdminCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
setAdminCmd.Flags().String(addrAdminFlag, "", addrAdminDesc)
|
||||
_ = setAdminCmd.MarkFlagRequired(addrAdminFlag)
|
||||
}
|
||||
|
||||
func initGetAdminCmd() {
|
||||
apeCmd.AddCommand(getAdminCmd)
|
||||
Cmd.AddCommand(getAdminCmd)
|
||||
|
||||
getAdminCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
getAdminCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
getAdminCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
getAdminCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func addRuleChain(cmd *cobra.Command, _ []string) {
|
|
@ -1,11 +1,14 @@
|
|||
package morph
|
||||
package ape
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
parseutil "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
|
@ -77,17 +80,17 @@ func parseChain(cmd *cobra.Command) *apechain.Chain {
|
|||
|
||||
func newPolicyContractInterface(cmd *cobra.Command) (*morph.ContractStorage, *actor.Actor) {
|
||||
v := viper.GetViper()
|
||||
c, err := getN3Client(v)
|
||||
c, err := helper.GetN3Client(v)
|
||||
commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err)
|
||||
|
||||
walletDir := config.ResolveHomePath(viper.GetString(alphabetWalletsFlag))
|
||||
wallets, err := getAlphabetWallets(v, walletDir)
|
||||
walletDir := config.ResolveHomePath(viper.GetString(commonflags.AlphabetWalletsFlag))
|
||||
wallets, err := helper.GetAlphabetWallets(v, walletDir)
|
||||
commonCmd.ExitOnErr(cmd, "unable to get alphabet wallets: %w", err)
|
||||
|
||||
committeeAcc, err := getWalletAccount(wallets[0], committeeAccountName)
|
||||
committeeAcc, err := helper.GetWalletAccount(wallets[0], constants.CommitteeAccountName)
|
||||
commonCmd.ExitOnErr(cmd, "can't find committee account: %w", err)
|
||||
|
||||
ac, err := newActor(c, committeeAcc)
|
||||
ac, err := helper.NewActor(c, committeeAcc)
|
||||
commonCmd.ExitOnErr(cmd, "can't create actor: %w", err)
|
||||
|
||||
inv := &ac.Invoker
|
||||
|
@ -96,7 +99,7 @@ func newPolicyContractInterface(cmd *cobra.Command) (*morph.ContractStorage, *ac
|
|||
nnsCs, err := r.GetContractByID(1)
|
||||
commonCmd.ExitOnErr(cmd, "can't get NNS contract state: %w", err)
|
||||
|
||||
ch, err = nnsResolveHash(inv, nnsCs.Hash, domainOf(policyContract))
|
||||
ch, err = helper.NNSResolveHash(inv, nnsCs.Hash, helper.DomainOf(constants.PolicyContract))
|
||||
commonCmd.ExitOnErr(cmd, "unable to resolve policy contract hash: %w", err)
|
||||
|
||||
return morph.NewContractStorage(ac, ch), ac
|
16
cmd/frostfs-adm/internal/modules/morph/ape/root.go
Normal file
16
cmd/frostfs-adm/internal/modules/morph/ape/root.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package ape
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "ape",
|
||||
Short: "Section for APE configuration commands",
|
||||
}
|
||||
|
||||
func init() {
|
||||
initAddRuleChainCmd()
|
||||
initRemoveRuleChainCmd()
|
||||
initListRuleChainsCmd()
|
||||
initSetAdminCmd()
|
||||
initGetAdminCmd()
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package morph
|
||||
package balance
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
|
@ -7,6 +7,8 @@ import (
|
|||
"math/big"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
|
@ -49,7 +51,7 @@ func dumpBalances(cmd *cobra.Command, _ []string) error {
|
|||
nmHash util.Uint160
|
||||
)
|
||||
|
||||
c, err := getN3Client(viper.GetViper())
|
||||
c, err := helper.GetN3Client(viper.GetViper())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -63,7 +65,7 @@ func dumpBalances(cmd *cobra.Command, _ []string) error {
|
|||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||
}
|
||||
|
||||
nmHash, err = nnsResolveHash(inv, nnsCs.Hash, domainOf(netmapContract))
|
||||
nmHash, err = helper.NNSResolveHash(inv, nnsCs.Hash, helper.DomainOf(constants.NetmapContract))
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||
}
|
||||
|
@ -136,7 +138,7 @@ func printStorageNodeBalances(cmd *cobra.Command, inv *invoker.Invoker, nmHash u
|
|||
}
|
||||
|
||||
func printProxyContractBalance(cmd *cobra.Command, inv *invoker.Invoker, nnsHash util.Uint160) error {
|
||||
h, err := nnsResolveHash(inv, nnsHash, domainOf(proxyContract))
|
||||
h, err := helper.NNSResolveHash(inv, nnsHash, helper.DomainOf(constants.ProxyContract))
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get hash of the proxy contract: %w", err)
|
||||
}
|
||||
|
@ -150,13 +152,13 @@ func printProxyContractBalance(cmd *cobra.Command, inv *invoker.Invoker, nnsHash
|
|||
return nil
|
||||
}
|
||||
|
||||
func printAlphabetContractBalances(cmd *cobra.Command, c Client, inv *invoker.Invoker, count int, nnsHash util.Uint160) error {
|
||||
func printAlphabetContractBalances(cmd *cobra.Command, c helper.Client, inv *invoker.Invoker, count int, nnsHash util.Uint160) error {
|
||||
alphaList := make([]accBalancePair, count)
|
||||
|
||||
w := io.NewBufBinWriter()
|
||||
for i := range alphaList {
|
||||
emit.AppCall(w.BinWriter, nnsHash, "resolve", callflag.ReadOnly,
|
||||
getAlphabetNNSDomain(i),
|
||||
helper.GetAlphabetNNSDomain(i),
|
||||
int64(nns.TXT))
|
||||
}
|
||||
if w.Err != nil {
|
||||
|
@ -169,7 +171,7 @@ func printAlphabetContractBalances(cmd *cobra.Command, c Client, inv *invoker.In
|
|||
}
|
||||
|
||||
for i := range alphaList {
|
||||
h, err := parseNNSResolveResult(alphaRes.Stack[i])
|
||||
h, err := helper.ParseNNSResolveResult(alphaRes.Stack[i])
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch the alphabet contract #%d hash: %w", i, err)
|
||||
}
|
||||
|
@ -184,7 +186,7 @@ func printAlphabetContractBalances(cmd *cobra.Command, c Client, inv *invoker.In
|
|||
return nil
|
||||
}
|
||||
|
||||
func fetchIRNodes(c Client, desigHash util.Uint160) ([]accBalancePair, error) {
|
||||
func fetchIRNodes(c helper.Client, desigHash util.Uint160) ([]accBalancePair, error) {
|
||||
inv := invoker.New(c, nil)
|
||||
|
||||
height, err := c.GetBlockCount()
|
||||
|
@ -192,7 +194,7 @@ func fetchIRNodes(c Client, desigHash util.Uint160) ([]accBalancePair, error) {
|
|||
return nil, fmt.Errorf("can't get block height: %w", err)
|
||||
}
|
||||
|
||||
arr, err := getDesignatedByRole(inv, desigHash, noderoles.NeoFSAlphabet, height)
|
||||
arr, err := helper.GetDesignatedByRole(inv, desigHash, noderoles.NeoFSAlphabet, height)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't fetch list of IR nodes from the netmap contract")
|
||||
}
|
28
cmd/frostfs-adm/internal/modules/morph/balance/root.go
Normal file
28
cmd/frostfs-adm/internal/modules/morph/balance/root.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package balance
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var DumpCmd = &cobra.Command{
|
||||
Use: "dump-balances",
|
||||
Short: "Dump GAS balances",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: dumpBalances,
|
||||
}
|
||||
|
||||
func initDumpBalancesCmd() {
|
||||
DumpCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
DumpCmd.Flags().BoolP(dumpBalancesStorageFlag, "s", false, "Dump balances of storage nodes from the current netmap")
|
||||
DumpCmd.Flags().BoolP(dumpBalancesAlphabetFlag, "a", false, "Dump balances of alphabet contracts")
|
||||
DumpCmd.Flags().BoolP(dumpBalancesProxyFlag, "p", false, "Dump balances of the proxy contract")
|
||||
DumpCmd.Flags().Bool(dumpBalancesUseScriptHashFlag, false, "Use script-hash format for addresses")
|
||||
}
|
||||
|
||||
func init() {
|
||||
initDumpBalancesCmd()
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package morph
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -10,6 +10,8 @@ import (
|
|||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
fyrchik marked this conversation as resolved
Outdated
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
|
@ -24,7 +26,7 @@ import (
|
|||
const forceConfigSet = "force"
|
||||
|
||||
func dumpNetworkConfig(cmd *cobra.Command, _ []string) error {
|
||||
c, err := getN3Client(viper.GetViper())
|
||||
c, err := helper.GetN3Client(viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create N3 client: %w", err)
|
||||
}
|
||||
|
@ -37,7 +39,7 @@ func dumpNetworkConfig(cmd *cobra.Command, _ []string) error {
|
|||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||
}
|
||||
|
||||
nmHash, err := nnsResolveHash(inv, cs.Hash, domainOf(netmapContract))
|
||||
nmHash, err := helper.NNSResolveHash(inv, cs.Hash, helper.DomainOf(constants.NetmapContract))
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||
}
|
||||
|
@ -50,7 +52,7 @@ func dumpNetworkConfig(cmd *cobra.Command, _ []string) error {
|
|||
buf := bytes.NewBuffer(nil)
|
||||
tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
|
||||
|
||||
m, err := parseConfigFromNetmapContract(arr)
|
||||
m, err := helper.ParseConfigFromNetmapContract(arr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -65,7 +67,7 @@ func dumpNetworkConfig(cmd *cobra.Command, _ []string) error {
|
|||
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%d (int)\n", k, n)))
|
||||
case netmap.HomomorphicHashingDisabledKey, netmap.MaintenanceModeAllowedConfig:
|
||||
if len(v) == 0 || len(v) > 1 {
|
||||
return invalidConfigValueErr(k)
|
||||
return helper.InvalidConfigValueErr(k)
|
||||
}
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%t (bool)\n", k, v[0] == 1)))
|
||||
default:
|
||||
|
@ -79,12 +81,12 @@ func dumpNetworkConfig(cmd *cobra.Command, _ []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func setConfigCmd(cmd *cobra.Command, args []string) error {
|
||||
func SetConfigCmd(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("empty config pairs")
|
||||
}
|
||||
|
||||
wCtx, err := newInitializeContext(cmd, viper.GetViper())
|
||||
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't initialize context: %w", err)
|
||||
}
|
||||
|
@ -95,7 +97,7 @@ func setConfigCmd(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||
}
|
||||
|
||||
nmHash, err := nnsResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, domainOf(netmapContract))
|
||||
nmHash, err := helper.NNSResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, helper.DomainOf(constants.NetmapContract))
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||
}
|
||||
|
@ -118,12 +120,12 @@ func setConfigCmd(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
}
|
||||
|
||||
err = wCtx.sendConsensusTx(bw.Bytes())
|
||||
err = wCtx.SendConsensusTx(bw.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return wCtx.awaitTx()
|
||||
return wCtx.AwaitTx()
|
||||
}
|
||||
|
||||
func parseConfigPair(kvStr string, force bool) (key string, val any, err error) {
|
||||
|
@ -161,7 +163,3 @@ func parseConfigPair(kvStr string, force bool) (key string, val any, err error)
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
func invalidConfigValueErr(key string) error {
|
||||
return fmt.Errorf("invalid %s config value from netmap contract", key)
|
||||
}
|
46
cmd/frostfs-adm/internal/modules/morph/config/root.go
Normal file
46
cmd/frostfs-adm/internal/modules/morph/config/root.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
SetCmd = &cobra.Command{
|
||||
Use: "set-config key1=val1 [key2=val2 ...]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: "Add/update global config value in the FrostFS network",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: SetConfigCmd,
|
||||
}
|
||||
|
||||
DumpCmd = &cobra.Command{
|
||||
Use: "dump-config",
|
||||
Short: "Dump FrostFS network config",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: dumpNetworkConfig,
|
||||
}
|
||||
)
|
||||
|
||||
func initSetConfigCmd() {
|
||||
SetCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
SetCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
SetCmd.Flags().Bool(forceConfigSet, false, "Force setting not well-known configuration key")
|
||||
SetCmd.Flags().String(commonflags.LocalDumpFlag, "", "Path to the blocks dump file")
|
||||
}
|
||||
|
||||
func initDumpNetworkConfigCmd() {
|
||||
DumpCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
}
|
||||
|
||||
func init() {
|
||||
initSetConfigCmd()
|
||||
initDumpNetworkConfigCmd()
|
||||
}
|
57
cmd/frostfs-adm/internal/modules/morph/constants/const.go
Normal file
57
cmd/frostfs-adm/internal/modules/morph/constants/const.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package constants
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
ConsensusAccountName = "consensus"
|
||||
ProtoConfigPath = "protocol"
|
||||
|
||||
// MaxAlphabetNodes is the maximum number of candidates allowed, which is currently limited by the size
|
||||
// of the invocation script.
|
||||
// See: https://github.com/nspcc-dev/neo-go/blob/740488f7f35e367eaa99a71c0a609c315fe2b0fc/pkg/core/transaction/witness.go#L10
|
||||
MaxAlphabetNodes = 22
|
||||
fyrchik marked this conversation as resolved
Outdated
fyrchik
commented
It is used in multiple packages? It is used in multiple packages?
fyrchik
commented
It is used in multiple packages? It is used in multiple packages?
acid-ant
commented
Yes, it is used in Yes, it is used in `helper`, `generate` and `initialize`.
|
||||
|
||||
SingleAccountName = "single"
|
||||
CommitteeAccountName = "committee"
|
||||
|
||||
NNSContract = "nns"
|
||||
FrostfsContract = "frostfs" // not deployed in side-chain.
|
||||
ProcessingContract = "processing" // not deployed in side-chain.
|
||||
AlphabetContract = "alphabet"
|
||||
BalanceContract = "balance"
|
||||
ContainerContract = "container"
|
||||
FrostfsIDContract = "frostfsid"
|
||||
NetmapContract = "netmap"
|
||||
PolicyContract = "policy"
|
||||
ProxyContract = "proxy"
|
||||
|
||||
ContractWalletFilename = "contract.json"
|
||||
ContractWalletPasswordKey = "contract"
|
||||
|
||||
FrostfsOpsEmail = "ops@frostfs.info"
|
||||
|
||||
DefaultExpirationTime = 10 * 365 * 24 * time.Hour / time.Second
|
||||
|
||||
DeployMethodName = "deploy"
|
||||
UpdateMethodName = "update"
|
||||
|
||||
TestContractPassword = "grouppass"
|
||||
)
|
||||
|
||||
var (
|
||||
ContractList = []string{
|
||||
BalanceContract,
|
||||
ContainerContract,
|
||||
FrostfsIDContract,
|
||||
NetmapContract,
|
||||
PolicyContract,
|
||||
ProxyContract,
|
||||
}
|
||||
|
||||
FullContractList = append([]string{
|
||||
FrostfsContract,
|
||||
ProcessingContract,
|
||||
NNSContract,
|
||||
AlphabetContract,
|
||||
}, ContractList...)
|
||||
)
|
|
@ -1,4 +1,4 @@
|
|||
package morph
|
||||
package container
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
@ -7,6 +7,8 @@ import (
|
|||
"os"
|
||||
"sort"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
|
@ -35,7 +37,7 @@ func getContainerContractHash(cmd *cobra.Command, inv *invoker.Invoker) (util.Ui
|
|||
if err != nil {
|
||||
return util.Uint160{}, fmt.Errorf("can't get NNS contract state: %w", err)
|
||||
}
|
||||
ch, err = nnsResolveHash(inv, nnsCs.Hash, domainOf(containerContract))
|
||||
ch, err = helper.NNSResolveHash(inv, nnsCs.Hash, helper.DomainOf(constants.ContainerContract))
|
||||
if err != nil {
|
||||
return util.Uint160{}, err
|
||||
}
|
||||
|
@ -73,7 +75,7 @@ func dumpContainers(cmd *cobra.Command, _ []string) error {
|
|||
return fmt.Errorf("invalid filename: %w", err)
|
||||
}
|
||||
|
||||
c, err := getN3Client(viper.GetViper())
|
||||
c, err := helper.GetN3Client(viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create N3 client: %w", err)
|
||||
}
|
||||
|
@ -163,7 +165,7 @@ func dumpSingleContainer(bw *io.BufBinWriter, ch util.Uint160, inv *invoker.Invo
|
|||
}
|
||||
|
||||
func listContainers(cmd *cobra.Command, _ []string) error {
|
||||
c, err := getN3Client(viper.GetViper())
|
||||
c, err := helper.GetN3Client(viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create N3 client: %w", err)
|
||||
}
|
||||
|
@ -192,11 +194,11 @@ func restoreContainers(cmd *cobra.Command, _ []string) error {
|
|||
return fmt.Errorf("invalid filename: %w", err)
|
||||
}
|
||||
|
||||
wCtx, err := newInitializeContext(cmd, viper.GetViper())
|
||||
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer wCtx.close()
|
||||
defer wCtx.Close()
|
||||
|
||||
containers, err := parseContainers(filename)
|
||||
if err != nil {
|
||||
|
@ -218,10 +220,10 @@ func restoreContainers(cmd *cobra.Command, _ []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
return wCtx.awaitTx()
|
||||
return wCtx.AwaitTx()
|
||||
}
|
||||
|
||||
func restoreOrPutContainers(containers []Container, isOK func([]byte) bool, cmd *cobra.Command, wCtx *initializeContext, ch util.Uint160) error {
|
||||
func restoreOrPutContainers(containers []Container, isOK func([]byte) bool, cmd *cobra.Command, wCtx *helper.InitializeContext, ch util.Uint160) error {
|
||||
bw := io.NewBufBinWriter()
|
||||
for _, cnt := range containers {
|
||||
hv := hash.Sha256(cnt.Value)
|
||||
|
@ -245,7 +247,7 @@ func restoreOrPutContainers(containers []Container, isOK func([]byte) bool, cmd
|
|||
panic(bw.Err)
|
||||
}
|
||||
|
||||
if err := wCtx.sendConsensusTx(bw.Bytes()); err != nil {
|
||||
if err := wCtx.SendConsensusTx(bw.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -261,7 +263,7 @@ func putContainer(bw *io.BufBinWriter, ch util.Uint160, cnt Container) {
|
|||
}
|
||||
}
|
||||
|
||||
func isContainerRestored(cmd *cobra.Command, wCtx *initializeContext, containerHash util.Uint160, bw *io.BufBinWriter, hashValue util.Uint256) (bool, error) {
|
||||
func isContainerRestored(cmd *cobra.Command, wCtx *helper.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 {
|
||||
|
@ -299,14 +301,14 @@ func parseContainers(filename string) ([]Container, error) {
|
|||
return containers, nil
|
||||
}
|
||||
|
||||
func fetchContainerContractHash(wCtx *initializeContext) (util.Uint160, error) {
|
||||
func fetchContainerContractHash(wCtx *helper.InitializeContext) (util.Uint160, error) {
|
||||
r := management.NewReader(wCtx.ReadOnlyInvoker)
|
||||
nnsCs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return util.Uint160{}, fmt.Errorf("can't get NNS contract state: %w", err)
|
||||
}
|
||||
|
||||
ch, err := nnsResolveHash(wCtx.ReadOnlyInvoker, nnsCs.Hash, domainOf(containerContract))
|
||||
ch, err := helper.NNSResolveHash(wCtx.ReadOnlyInvoker, nnsCs.Hash, helper.DomainOf(constants.ContainerContract))
|
||||
if err != nil {
|
||||
return util.Uint160{}, fmt.Errorf("can't fetch container contract hash: %w", err)
|
||||
}
|
68
cmd/frostfs-adm/internal/modules/morph/container/root.go
Normal file
68
cmd/frostfs-adm/internal/modules/morph/container/root.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
containerDumpFlag = "dump"
|
||||
containerContractFlag = "container-contract"
|
||||
containerIDsFlag = "cid"
|
||||
)
|
||||
|
||||
var (
|
||||
DumpCmd = &cobra.Command{
|
||||
Use: "dump-containers",
|
||||
Short: "Dump FrostFS containers to file",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: dumpContainers,
|
||||
}
|
||||
|
||||
RestoreCmd = &cobra.Command{
|
||||
Use: "restore-containers",
|
||||
Short: "Restore FrostFS containers from file",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: restoreContainers,
|
||||
}
|
||||
|
||||
ListCmd = &cobra.Command{
|
||||
Use: "list-containers",
|
||||
Short: "List FrostFS containers",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: listContainers,
|
||||
}
|
||||
)
|
||||
|
||||
func initListContainersCmd() {
|
||||
ListCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
ListCmd.Flags().String(containerContractFlag, "", "Container contract hash (for networks without NNS)")
|
||||
}
|
||||
|
||||
func initRestoreContainersCmd() {
|
||||
RestoreCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
RestoreCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
RestoreCmd.Flags().String(containerDumpFlag, "", "File to restore containers from")
|
||||
RestoreCmd.Flags().StringSlice(containerIDsFlag, nil, "Containers to restore")
|
||||
}
|
||||
|
||||
func initDumpContainersCmd() {
|
||||
DumpCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
DumpCmd.Flags().String(containerDumpFlag, "", "File where to save dumped containers")
|
||||
DumpCmd.Flags().String(containerContractFlag, "", "Container contract hash (for networks without NNS)")
|
||||
DumpCmd.Flags().StringSlice(containerIDsFlag, nil, "Containers to dump")
|
||||
}
|
||||
|
||||
func init() {
|
||||
initDumpContainersCmd()
|
||||
initRestoreContainersCmd()
|
||||
initListContainersCmd()
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package morph
|
||||
package contract
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
@ -7,6 +7,9 @@ import (
|
|||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
|
@ -23,10 +26,9 @@ import (
|
|||
const (
|
||||
contractPathFlag = "contract"
|
||||
updateFlag = "update"
|
||||
customZoneFlag = "domain"
|
||||
)
|
||||
|
||||
var deployCmd = &cobra.Command{
|
||||
var DeployCmd = &cobra.Command{
|
||||
Use: "deploy",
|
||||
Short: "Deploy additional smart-contracts",
|
||||
Long: `Deploy additional smart-contract which are not related to core.
|
||||
|
@ -37,33 +39,33 @@ Compiled contract file name must contain '_contract.nef' suffix.
|
|||
Contract's manifest file name must be 'config.json'.
|
||||
NNS name is taken by stripping '_contract.nef' from the NEF file (similar to frostfs contracts).`,
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: deployContractCmd,
|
||||
}
|
||||
|
||||
func init() {
|
||||
ff := deployCmd.Flags()
|
||||
ff := DeployCmd.Flags()
|
||||
|
||||
ff.String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
_ = deployCmd.MarkFlagFilename(alphabetWalletsFlag)
|
||||
ff.String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
_ = DeployCmd.MarkFlagFilename(commonflags.AlphabetWalletsFlag)
|
||||
|
||||
ff.StringP(endpointFlag, "r", "", endpointFlagDesc)
|
||||
ff.StringP(commonflags.EndpointFlag, "r", "", commonflags.EndpointFlagDesc)
|
||||
ff.String(contractPathFlag, "", "Path to the contract directory")
|
||||
_ = deployCmd.MarkFlagFilename(contractPathFlag)
|
||||
_ = DeployCmd.MarkFlagFilename(contractPathFlag)
|
||||
|
||||
ff.Bool(updateFlag, false, "Update an existing contract")
|
||||
ff.String(customZoneFlag, "frostfs", "Custom zone for NNS")
|
||||
ff.String(commonflags.CustomZoneFlag, "frostfs", "Custom zone for NNS")
|
||||
}
|
||||
|
||||
func deployContractCmd(cmd *cobra.Command, args []string) error {
|
||||
v := viper.GetViper()
|
||||
c, err := newInitializeContext(cmd, v)
|
||||
c, err := helper.NewInitializeContext(cmd, v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("initialization error: %w", err)
|
||||
}
|
||||
defer c.close()
|
||||
defer c.Close()
|
||||
|
||||
ctrPath, _ := cmd.Flags().GetString(contractPathFlag)
|
||||
ctrName, err := probeContractName(ctrPath)
|
||||
|
@ -71,7 +73,7 @@ func deployContractCmd(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
cs, err := readContract(ctrPath, ctrName)
|
||||
cs, err := helper.ReadContract(ctrPath, ctrName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -83,17 +85,17 @@ func deployContractCmd(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
|
||||
callHash := management.Hash
|
||||
method := deployMethodName
|
||||
zone, _ := cmd.Flags().GetString(customZoneFlag)
|
||||
method := constants.DeployMethodName
|
||||
zone, _ := cmd.Flags().GetString(commonflags.CustomZoneFlag)
|
||||
domain := ctrName + "." + zone
|
||||
isUpdate, _ := cmd.Flags().GetBool(updateFlag)
|
||||
if isUpdate {
|
||||
cs.Hash, err = nnsResolveHash(c.ReadOnlyInvoker, nnsCs.Hash, domain)
|
||||
cs.Hash, err = helper.NNSResolveHash(c.ReadOnlyInvoker, nnsCs.Hash, domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch contract hash from NNS: %w", err)
|
||||
}
|
||||
callHash = cs.Hash
|
||||
method = updateMethodName
|
||||
method = constants.UpdateMethodName
|
||||
} else {
|
||||
cs.Hash = state.CreateContractHash(
|
||||
c.CommitteeAcc.Contract.ScriptHash(),
|
||||
|
@ -122,13 +124,13 @@ func deployContractCmd(cmd *cobra.Command, args []string) error {
|
|||
panic(fmt.Errorf("BUG: can't create deployment script: %w", writer.Err))
|
||||
}
|
||||
|
||||
if err := c.sendCommitteeTx(writer.Bytes(), false); err != nil {
|
||||
if err := c.SendCommitteeTx(writer.Bytes(), false); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.awaitTx()
|
||||
return c.AwaitTx()
|
||||
}
|
||||
|
||||
func registerNNS(nnsCs *state.Contract, c *initializeContext, zone string, domain string, cs *contractState, writer *io.BufBinWriter) error {
|
||||
func registerNNS(nnsCs *state.Contract, c *helper.InitializeContext, zone string, domain string, cs *helper.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)
|
||||
|
@ -138,7 +140,7 @@ func registerNNS(nnsCs *state.Contract, c *initializeContext, zone string, domai
|
|||
start := bw.Len()
|
||||
needRecord := false
|
||||
|
||||
ok, err := c.nnsRootRegistered(nnsCs.Hash, zone)
|
||||
ok, err := c.NNSRootRegistered(nnsCs.Hash, zone)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
|
@ -146,15 +148,15 @@ func registerNNS(nnsCs *state.Contract, c *initializeContext, zone string, domai
|
|||
|
||||
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
|
||||
zone, c.CommitteeAcc.Contract.ScriptHash(),
|
||||
frostfsOpsEmail, int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
||||
constants.FrostfsOpsEmail, int64(3600), int64(600), int64(constants.DefaultExpirationTime), int64(3600))
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
|
||||
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
|
||||
domain, c.CommitteeAcc.Contract.ScriptHash(),
|
||||
frostfsOpsEmail, int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
||||
constants.FrostfsOpsEmail, int64(3600), int64(600), int64(constants.DefaultExpirationTime), int64(3600))
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
} else {
|
||||
s, ok, err := c.nnsRegisterDomainScript(nnsCs.Hash, cs.Hash, domain)
|
||||
s, ok, err := c.NNSRegisterDomainScript(nnsCs.Hash, cs.Hash, domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package morph
|
||||
package contract
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -8,6 +8,9 @@ import (
|
|||
"text/tabwriter"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
|
@ -33,7 +36,7 @@ type contractDumpInfo struct {
|
|||
}
|
||||
|
||||
func dumpContractHashes(cmd *cobra.Command, _ []string) error {
|
||||
c, err := getN3Client(viper.GetViper())
|
||||
c, err := helper.GetN3Client(viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create N3 client: %w", err)
|
||||
}
|
||||
|
@ -44,16 +47,16 @@ func dumpContractHashes(cmd *cobra.Command, _ []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
zone, _ := cmd.Flags().GetString(customZoneFlag)
|
||||
zone, _ := cmd.Flags().GetString(commonflags.CustomZoneFlag)
|
||||
if zone != "" {
|
||||
return dumpCustomZoneHashes(cmd, cs.Hash, zone, c)
|
||||
}
|
||||
|
||||
infos := []contractDumpInfo{{name: nnsContract, hash: cs.Hash}}
|
||||
infos := []contractDumpInfo{{name: constants.NNSContract, hash: cs.Hash}}
|
||||
|
||||
irSize := 0
|
||||
for ; irSize < lastGlagoliticLetter; irSize++ {
|
||||
ok, err := nnsIsAvailable(c, cs.Hash, getAlphabetNNSDomain(irSize))
|
||||
ok, err := helper.NNSIsAvailable(c, cs.Hash, helper.GetAlphabetNNSDomain(irSize))
|
||||
if err != nil {
|
||||
return err
|
||||
} else if ok {
|
||||
|
@ -67,7 +70,7 @@ func dumpContractHashes(cmd *cobra.Command, _ []string) error {
|
|||
bw.Reset()
|
||||
for i := 0; i < irSize; i++ {
|
||||
emit.AppCall(bw.BinWriter, cs.Hash, "resolve", callflag.ReadOnly,
|
||||
getAlphabetNNSDomain(i),
|
||||
helper.GetAlphabetNNSDomain(i),
|
||||
int64(nns.TXT))
|
||||
}
|
||||
|
||||
|
@ -78,17 +81,17 @@ func dumpContractHashes(cmd *cobra.Command, _ []string) error {
|
|||
|
||||
for i := 0; i < irSize; i++ {
|
||||
info := contractDumpInfo{name: fmt.Sprintf("alphabet %d", i)}
|
||||
if h, err := parseNNSResolveResult(alphaRes.Stack[i]); err == nil {
|
||||
if h, err := helper.ParseNNSResolveResult(alphaRes.Stack[i]); err == nil {
|
||||
info.hash = h
|
||||
}
|
||||
infos = append(infos, info)
|
||||
}
|
||||
}
|
||||
|
||||
for _, ctrName := range contractList {
|
||||
for _, ctrName := range constants.ContractList {
|
||||
bw.Reset()
|
||||
emit.AppCall(bw.BinWriter, cs.Hash, "resolve", callflag.ReadOnly,
|
||||
domainOf(ctrName), int64(nns.TXT))
|
||||
helper.DomainOf(ctrName), int64(nns.TXT))
|
||||
|
||||
res, err := c.InvokeScript(bw.Bytes(), nil)
|
||||
if err != nil {
|
||||
|
@ -97,7 +100,7 @@ func dumpContractHashes(cmd *cobra.Command, _ []string) error {
|
|||
|
||||
info := contractDumpInfo{name: ctrName}
|
||||
if len(res.Stack) != 0 {
|
||||
if h, err := parseNNSResolveResult(res.Stack[0]); err == nil {
|
||||
if h, err := helper.ParseNNSResolveResult(res.Stack[0]); err == nil {
|
||||
info.hash = h
|
||||
}
|
||||
}
|
||||
|
@ -110,7 +113,7 @@ func dumpContractHashes(cmd *cobra.Command, _ []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func dumpCustomZoneHashes(cmd *cobra.Command, nnsHash util.Uint160, zone string, c Client) error {
|
||||
func dumpCustomZoneHashes(cmd *cobra.Command, nnsHash util.Uint160, zone string, c helper.Client) error {
|
||||
const nnsMaxTokens = 100
|
||||
|
||||
inv := invoker.New(c, nil)
|
||||
|
@ -132,7 +135,7 @@ func dumpCustomZoneHashes(cmd *cobra.Command, nnsHash util.Uint160, zone string,
|
|||
return
|
||||
}
|
||||
|
||||
h, err := nnsResolveHash(inv, nnsHash, string(bs))
|
||||
h, err := helper.NNSResolveHash(inv, nnsHash, string(bs))
|
||||
if err != nil {
|
||||
cmd.PrintErrf("Could not resolve name %s: %v\n", string(bs), err)
|
||||
return
|
||||
|
@ -224,7 +227,7 @@ func printContractInfo(cmd *cobra.Command, infos []contractDumpInfo) {
|
|||
cmd.Print(buf.String())
|
||||
}
|
||||
|
||||
func fillContractVersion(cmd *cobra.Command, c Client, infos []contractDumpInfo) {
|
||||
func fillContractVersion(cmd *cobra.Command, c helper.Client, infos []contractDumpInfo) {
|
||||
bw := io.NewBufBinWriter()
|
||||
sub := io.NewBufBinWriter()
|
||||
for i := range infos {
|
45
cmd/frostfs-adm/internal/modules/morph/contract/root.go
Normal file
45
cmd/frostfs-adm/internal/modules/morph/contract/root.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package contract
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
DumpHashesCmd = &cobra.Command{
|
||||
Use: "dump-hashes",
|
||||
Short: "Dump deployed contract hashes",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: dumpContractHashes,
|
||||
}
|
||||
UpdateCmd = &cobra.Command{
|
||||
Use: "update-contracts",
|
||||
Short: "Update FrostFS contracts",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: updateContracts,
|
||||
}
|
||||
)
|
||||
|
||||
func initDumpContractHashesCmd() {
|
||||
DumpHashesCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
DumpHashesCmd.Flags().String(commonflags.CustomZoneFlag, "", "Custom zone to search.")
|
||||
}
|
||||
|
||||
func initUpdateContractsCmd() {
|
||||
UpdateCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
UpdateCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
UpdateCmd.Flags().String(commonflags.ContractsInitFlag, "", commonflags.ContractsInitFlagDesc)
|
||||
UpdateCmd.Flags().String(commonflags.ContractsURLFlag, "", commonflags.ContractsURLFlagDesc)
|
||||
UpdateCmd.MarkFlagsMutuallyExclusive(commonflags.ContractsInitFlag, commonflags.ContractsURLFlag)
|
||||
}
|
||||
|
||||
func init() {
|
||||
initDumpContractHashesCmd()
|
||||
initUpdateContractsCmd()
|
||||
}
|
197
cmd/frostfs-adm/internal/modules/morph/contract/update.go
Normal file
197
cmd/frostfs-adm/internal/modules/morph/contract/update.go
Normal file
|
@ -0,0 +1,197 @@
|
|||
package contract
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
io2 "github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
neoUtil "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/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var errMissingNNSRecord = errors.New("missing NNS record")
|
||||
|
||||
func updateContracts(cmd *cobra.Command, _ []string) error {
|
||||
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("initialization error: %w", err)
|
||||
}
|
||||
|
||||
if err := helper.DeployNNS(wCtx, constants.UpdateMethodName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return updateContractsInternal(wCtx)
|
||||
}
|
||||
|
||||
func updateContractsInternal(c *helper.InitializeContext) error {
|
||||
alphaCs := c.GetContract(constants.AlphabetContract)
|
||||
|
||||
nnsCs, err := c.NNSContractState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nnsHash := nnsCs.Hash
|
||||
|
||||
w := io2.NewBufBinWriter()
|
||||
|
||||
// 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.
|
||||
// 1. Initialize static slot for alphabet NEF.
|
||||
// 2. Store NEF into the static slot.
|
||||
// 3. Push parameters for each alphabet contract on stack.
|
||||
// 4. Add contract group to the manifest.
|
||||
// 5. For each alphabet contract, invoke `update` using parameters on stack and
|
||||
// NEF from step 2 and manifest from step 4.
|
||||
emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1})
|
||||
emit.Bytes(w.BinWriter, alphaCs.RawNEF)
|
||||
emit.Opcodes(w.BinWriter, opcode.STSFLD0)
|
||||
|
||||
keysParam, err := deployAlphabetAccounts(c, nnsHash, w, alphaCs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Reset()
|
||||
|
||||
if err = deployOrUpdateContracts(c, 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 deployAlphabetAccounts(c *helper.InitializeContext, nnsHash neoUtil.Uint160, w *io2.BufBinWriter, alphaCs *helper.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 := helper.NNSResolveHash(c.ReadOnlyInvoker, nnsHash, helper.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 = helper.AddManifestGroup(c.ContractWallet, 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, constants.UpdateMethodName, callflag.All)
|
||||
}
|
||||
if err := c.SendCommitteeTx(w.Bytes(), false); err != nil {
|
||||
if !strings.Contains(err.Error(), common.ErrAlreadyUpdated) {
|
||||
return nil, err
|
||||
}
|
||||
c.Command.Println("Alphabet contracts are already updated.")
|
||||
}
|
||||
|
||||
return keysParam, nil
|
||||
}
|
||||
|
||||
func deployOrUpdateContracts(c *helper.InitializeContext, w *io2.BufBinWriter, nnsHash neoUtil.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)
|
||||
emit.AppCall(w.BinWriter, nnsHash, "setPrice", callflag.All, 1)
|
||||
|
||||
for _, ctrName := range constants.ContractList {
|
||||
cs := c.GetContract(ctrName)
|
||||
|
||||
method := constants.UpdateMethodName
|
||||
ctrHash, err := helper.NNSResolveHash(c.ReadOnlyInvoker, nnsHash, helper.DomainOf(ctrName))
|
||||
if err != nil {
|
||||
if errors.Is(err, errMissingNNSRecord) {
|
||||
// if contract not found we deploy it instead of update
|
||||
method = constants.DeployMethodName
|
||||
} else {
|
||||
return fmt.Errorf("can't resolve hash for contract update: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = helper.AddManifestGroup(c.ContractWallet, ctrHash, cs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't sign manifest group: %v", err)
|
||||
}
|
||||
|
||||
invokeHash := management.Hash
|
||||
if method == constants.UpdateMethodName {
|
||||
invokeHash = ctrHash
|
||||
}
|
||||
|
||||
args, err := helper.GetContractDeployData(c, ctrName, keysParam, constants.UpdateMethodName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: getting update params: %v", ctrName, err)
|
||||
}
|
||||
params := helper.GetContractDeployParameters(cs, args)
|
||||
res, err := c.CommitteeAct.MakeCall(invokeHash, method, params...)
|
||||
if err != nil {
|
||||
if method != constants.UpdateMethodName || !strings.Contains(err.Error(), common.ErrAlreadyUpdated) {
|
||||
return fmt.Errorf("deploy contract: %w", err)
|
||||
}
|
||||
c.Command.Printf("%s contract is already updated.\n", ctrName)
|
||||
continue
|
||||
}
|
||||
|
||||
w.WriteBytes(res.Script)
|
||||
|
||||
if method == constants.DeployMethodName {
|
||||
// same actions are done in InitializeContext.setNNS, can be unified
|
||||
domain := ctrName + ".frostfs"
|
||||
script, ok, err := c.NNSRegisterDomainScript(nnsHash, cs.Hash, domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
w.WriteBytes(script)
|
||||
emit.AppCall(w.BinWriter, nnsHash, "deleteRecords", callflag.All, domain, int64(nns.TXT))
|
||||
emit.AppCall(w.BinWriter, nnsHash, "addRecord", callflag.All,
|
||||
domain, int64(nns.TXT), cs.Hash.StringLE())
|
||||
emit.AppCall(w.BinWriter, nnsHash, "addRecord", callflag.All,
|
||||
domain, int64(nns.TXT), address.Uint160ToString(cs.Hash))
|
||||
}
|
||||
c.Command.Printf("NNS: Set %s -> %s\n", domain, cs.Hash.StringLE())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"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/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func forceNewEpochCmd(cmd *cobra.Command, _ []string) error {
|
||||
wCtx, err := newInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't to initialize context: %w", err)
|
||||
}
|
||||
|
||||
r := management.NewReader(wCtx.ReadOnlyInvoker)
|
||||
cs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||
}
|
||||
|
||||
nmHash, err := nnsResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, domainOf(netmapContract))
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||
}
|
||||
|
||||
bw := io.NewBufBinWriter()
|
||||
if err := emitNewEpochCall(bw, wCtx, nmHash); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = wCtx.sendConsensusTx(bw.Bytes()); err == nil {
|
||||
err = wCtx.awaitTx()
|
||||
}
|
||||
if err != nil && strings.Contains(err.Error(), "invalid epoch") {
|
||||
cmd.Println("Epoch has already ticked.")
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func emitNewEpochCall(bw *io.BufBinWriter, wCtx *initializeContext, nmHash util.Uint160) error {
|
||||
curr, err := unwrap.Int64(wCtx.ReadOnlyInvoker.Call(nmHash, "epoch"))
|
||||
if err != nil {
|
||||
return errors.New("can't fetch current epoch from the netmap contract")
|
||||
}
|
||||
|
||||
newEpoch := curr + 1
|
||||
wCtx.Command.Printf("Current epoch: %d, increase to %d.\n", curr, newEpoch)
|
||||
|
||||
// In NeoFS this is done via Notary contract. Here, however, we can form the
|
||||
// transaction locally.
|
||||
emit.AppCall(bw.BinWriter, nmHash, "newEpoch", callflag.All, newEpoch)
|
||||
return bw.Err
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package morph
|
||||
package frostfsid
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
@ -6,6 +6,9 @@ import (
|
|||
"sort"
|
||||
|
||||
frostfsidclient "git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
|
@ -26,12 +29,12 @@ const (
|
|||
includeNamesFlag = "include-names"
|
||||
groupNameFlag = "group-name"
|
||||
groupIDFlag = "group-id"
|
||||
|
||||
rootNamespacePlaceholder = "<root>"
|
||||
)
|
||||
|
||||
const rootNamespacePlaceholder = "<root>"
|
||||
|
||||
var (
|
||||
frostfsidCmd = &cobra.Command{
|
||||
Cmd = &cobra.Command{
|
||||
Use: "frostfsid",
|
||||
Short: "Section for frostfsid interactions commands",
|
||||
}
|
||||
|
@ -40,8 +43,8 @@ var (
|
|||
Use: "create-namespace",
|
||||
Short: "Create new namespace in frostfsid contract",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: frostfsidCreateNamespace,
|
||||
}
|
||||
|
@ -50,8 +53,8 @@ var (
|
|||
Use: "list-namespaces",
|
||||
Short: "List all namespaces in frostfsid",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: frostfsidListNamespaces,
|
||||
}
|
||||
|
@ -60,8 +63,8 @@ var (
|
|||
Use: "create-subject",
|
||||
Short: "Create subject in frostfsid contract",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: frostfsidCreateSubject,
|
||||
}
|
||||
|
@ -70,8 +73,8 @@ var (
|
|||
Use: "delete-subject",
|
||||
Short: "Delete subject from frostfsid contract",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: frostfsidDeleteSubject,
|
||||
}
|
||||
|
@ -80,8 +83,8 @@ var (
|
|||
Use: "list-subjects",
|
||||
Short: "List subjects in namespace",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: frostfsidListSubjects,
|
||||
}
|
||||
|
@ -90,8 +93,8 @@ var (
|
|||
Use: "create-group",
|
||||
Short: "Create group in frostfsid contract",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: frostfsidCreateGroup,
|
||||
}
|
||||
|
@ -100,8 +103,8 @@ var (
|
|||
Use: "delete-group",
|
||||
Short: "Delete group from frostfsid contract",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: frostfsidDeleteGroup,
|
||||
}
|
||||
|
@ -110,8 +113,8 @@ var (
|
|||
Use: "list-groups",
|
||||
Short: "List groups in namespace",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: frostfsidListGroups,
|
||||
}
|
||||
|
@ -120,8 +123,8 @@ var (
|
|||
Use: "add-subject-to-group",
|
||||
Short: "Add subject to group",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: frostfsidAddSubjectToGroup,
|
||||
}
|
||||
|
@ -130,8 +133,8 @@ var (
|
|||
Use: "remove-subject-from-group",
|
||||
Short: "Remove subject from group",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: frostfsidRemoveSubjectFromGroup,
|
||||
}
|
||||
|
@ -140,96 +143,96 @@ var (
|
|||
Use: "list-group-subjects",
|
||||
Short: "List subjects in group",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: frostfsidListGroupSubjects,
|
||||
}
|
||||
)
|
||||
|
||||
func initFrostfsIDCreateNamespaceCmd() {
|
||||
frostfsidCmd.AddCommand(frostfsidCreateNamespaceCmd)
|
||||
frostfsidCreateNamespaceCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
Cmd.AddCommand(frostfsidCreateNamespaceCmd)
|
||||
frostfsidCreateNamespaceCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidCreateNamespaceCmd.Flags().String(namespaceFlag, "", "Namespace name to create")
|
||||
frostfsidCreateNamespaceCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
frostfsidCreateNamespaceCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDListNamespacesCmd() {
|
||||
frostfsidCmd.AddCommand(frostfsidListNamespacesCmd)
|
||||
frostfsidListNamespacesCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
frostfsidListNamespacesCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
Cmd.AddCommand(frostfsidListNamespacesCmd)
|
||||
frostfsidListNamespacesCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidListNamespacesCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDCreateSubjectCmd() {
|
||||
frostfsidCmd.AddCommand(frostfsidCreateSubjectCmd)
|
||||
frostfsidCreateSubjectCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
Cmd.AddCommand(frostfsidCreateSubjectCmd)
|
||||
frostfsidCreateSubjectCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidCreateSubjectCmd.Flags().String(namespaceFlag, "", "Namespace where create subject")
|
||||
frostfsidCreateSubjectCmd.Flags().String(subjectNameFlag, "", "Subject name, must be unique in namespace")
|
||||
frostfsidCreateSubjectCmd.Flags().String(subjectKeyFlag, "", "Subject hex-encoded public key")
|
||||
frostfsidCreateSubjectCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
frostfsidCreateSubjectCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDDeleteSubjectCmd() {
|
||||
frostfsidCmd.AddCommand(frostfsidDeleteSubjectCmd)
|
||||
frostfsidDeleteSubjectCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
Cmd.AddCommand(frostfsidDeleteSubjectCmd)
|
||||
frostfsidDeleteSubjectCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidDeleteSubjectCmd.Flags().String(subjectAddressFlag, "", "Subject address")
|
||||
frostfsidDeleteSubjectCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
frostfsidDeleteSubjectCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDListSubjectsCmd() {
|
||||
frostfsidCmd.AddCommand(frostfsidListSubjectsCmd)
|
||||
frostfsidListSubjectsCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
Cmd.AddCommand(frostfsidListSubjectsCmd)
|
||||
frostfsidListSubjectsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidListSubjectsCmd.Flags().String(namespaceFlag, "", "Namespace to list subjects")
|
||||
frostfsidListSubjectsCmd.Flags().Bool(includeNamesFlag, false, "Whether include subject name (require additional requests)")
|
||||
frostfsidListSubjectsCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
frostfsidListSubjectsCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDCreateGroupCmd() {
|
||||
frostfsidCmd.AddCommand(frostfsidCreateGroupCmd)
|
||||
frostfsidCreateGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
Cmd.AddCommand(frostfsidCreateGroupCmd)
|
||||
frostfsidCreateGroupCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidCreateGroupCmd.Flags().String(namespaceFlag, "", "Namespace where create group")
|
||||
frostfsidCreateGroupCmd.Flags().String(groupNameFlag, "", "Group name, must be unique in namespace")
|
||||
frostfsidCreateGroupCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
frostfsidCreateGroupCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDDeleteGroupCmd() {
|
||||
frostfsidCmd.AddCommand(frostfsidDeleteGroupCmd)
|
||||
frostfsidDeleteGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
Cmd.AddCommand(frostfsidDeleteGroupCmd)
|
||||
frostfsidDeleteGroupCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidDeleteGroupCmd.Flags().String(namespaceFlag, "", "Namespace to delete group")
|
||||
frostfsidDeleteGroupCmd.Flags().Int64(groupIDFlag, 0, "Group id")
|
||||
frostfsidDeleteGroupCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
frostfsidDeleteGroupCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDListGroupsCmd() {
|
||||
frostfsidCmd.AddCommand(frostfsidListGroupsCmd)
|
||||
frostfsidListGroupsCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
Cmd.AddCommand(frostfsidListGroupsCmd)
|
||||
frostfsidListGroupsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidListGroupsCmd.Flags().String(namespaceFlag, "", "Namespace to list groups")
|
||||
frostfsidListGroupsCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
frostfsidListGroupsCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDAddSubjectToGroupCmd() {
|
||||
frostfsidCmd.AddCommand(frostfsidAddSubjectToGroupCmd)
|
||||
frostfsidAddSubjectToGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
Cmd.AddCommand(frostfsidAddSubjectToGroupCmd)
|
||||
frostfsidAddSubjectToGroupCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidAddSubjectToGroupCmd.Flags().String(subjectAddressFlag, "", "Subject address")
|
||||
frostfsidAddSubjectToGroupCmd.Flags().Int64(groupIDFlag, 0, "Group id")
|
||||
frostfsidAddSubjectToGroupCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
frostfsidAddSubjectToGroupCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDRemoveSubjectFromGroupCmd() {
|
||||
frostfsidCmd.AddCommand(frostfsidRemoveSubjectFromGroupCmd)
|
||||
frostfsidRemoveSubjectFromGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
Cmd.AddCommand(frostfsidRemoveSubjectFromGroupCmd)
|
||||
frostfsidRemoveSubjectFromGroupCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidRemoveSubjectFromGroupCmd.Flags().String(subjectAddressFlag, "", "Subject address")
|
||||
frostfsidRemoveSubjectFromGroupCmd.Flags().Int64(groupIDFlag, 0, "Group id")
|
||||
frostfsidRemoveSubjectFromGroupCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
frostfsidRemoveSubjectFromGroupCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDListGroupSubjectsCmd() {
|
||||
frostfsidCmd.AddCommand(frostfsidListGroupSubjectsCmd)
|
||||
frostfsidListGroupSubjectsCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
Cmd.AddCommand(frostfsidListGroupSubjectsCmd)
|
||||
frostfsidListGroupSubjectsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidListGroupSubjectsCmd.Flags().String(namespaceFlag, "", "Namespace name")
|
||||
frostfsidListGroupSubjectsCmd.Flags().Int64(groupIDFlag, 0, "Group id")
|
||||
frostfsidListGroupSubjectsCmd.Flags().Bool(includeNamesFlag, false, "Whether include subject name (require additional requests)")
|
||||
frostfsidListGroupSubjectsCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
frostfsidListGroupSubjectsCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func frostfsidCreateNamespace(cmd *cobra.Command, _ []string) {
|
||||
|
@ -415,11 +418,11 @@ type frostfsidClient struct {
|
|||
bw *io.BufBinWriter
|
||||
contractHash util.Uint160
|
||||
roCli *frostfsidclient.Client // client can be used only for waiting tx, parsing and forming method params
|
||||
wCtx *initializeContext
|
||||
wCtx *helper.InitializeContext
|
||||
}
|
||||
|
||||
func newFrostfsIDClient(cmd *cobra.Command) (*frostfsidClient, error) {
|
||||
wCtx, err := newInitializeContext(cmd, viper.GetViper())
|
||||
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't to initialize context: %w", err)
|
||||
}
|
||||
|
@ -430,7 +433,7 @@ func newFrostfsIDClient(cmd *cobra.Command) (*frostfsidClient, error) {
|
|||
return nil, fmt.Errorf("can't get NNS contract info: %w", err)
|
||||
}
|
||||
|
||||
ffsidHash, err := nnsResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, domainOf(frostfsIDContract))
|
||||
ffsidHash, err := helper.NNSResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, helper.DomainOf(constants.FrostfsIDContract))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't get proxy contract hash: %w", err)
|
||||
}
|
||||
|
@ -448,16 +451,16 @@ func (f *frostfsidClient) addCall(method string, args []any) {
|
|||
}
|
||||
|
||||
func (f *frostfsidClient) sendWait() error {
|
||||
if err := f.wCtx.sendConsensusTx(f.bw.Bytes()); err != nil {
|
||||
if err := f.wCtx.SendConsensusTx(f.bw.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
f.bw.Reset()
|
||||
|
||||
return f.wCtx.awaitTx()
|
||||
return f.wCtx.AwaitTx()
|
||||
}
|
||||
|
||||
func (f *frostfsidClient) sendWaitRes() (*state.AppExecResult, error) {
|
||||
if err := f.wCtx.sendConsensusTx(f.bw.Bytes()); err != nil {
|
||||
if err := f.wCtx.SendConsensusTx(f.bw.Bytes()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.bw.Reset()
|
||||
|
@ -467,5 +470,5 @@ func (f *frostfsidClient) sendWaitRes() (*state.AppExecResult, error) {
|
|||
}
|
||||
|
||||
f.wCtx.Command.Println("Waiting for transactions to persist...")
|
||||
return f.roCli.Wait(f.wCtx.SentTxs[0].hash, f.wCtx.SentTxs[0].vub, nil)
|
||||
return f.roCli.Wait(f.wCtx.SentTxs[0].Hash, f.wCtx.SentTxs[0].Vub, nil)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package morph
|
||||
package frostfsid
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
@ -10,32 +10,8 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func getFrostfsIDAdmin(v *viper.Viper) (util.Uint160, bool, error) {
|
||||
admin := v.GetString(frostfsIDAdminConfigKey)
|
||||
if admin == "" {
|
||||
return util.Uint160{}, false, nil
|
||||
}
|
||||
|
||||
h, err := address.StringToUint160(admin)
|
||||
if err == nil {
|
||||
return h, true, nil
|
||||
}
|
||||
|
||||
h, err = util.Uint160DecodeStringLE(admin)
|
||||
if err == nil {
|
||||
return h, true, nil
|
||||
}
|
||||
|
||||
pk, err := keys.NewPublicKeyFromString(admin)
|
||||
if err == nil {
|
||||
return pk.GetScriptHash(), true, nil
|
||||
}
|
||||
return util.Uint160{}, true, fmt.Errorf("frostfsid: admin is invalid: '%s'", admin)
|
||||
}
|
||||
|
||||
func getFrostfsIDSubjectKey(cmd *cobra.Command) *keys.PublicKey {
|
||||
subjKeyHex, _ := cmd.Flags().GetString(subjectKeyFlag)
|
||||
subjKey, err := keys.NewPublicKeyFromString(subjKeyHex)
|
|
@ -1,9 +1,10 @@
|
|||
package morph
|
||||
package frostfsid
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/ape"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
|
@ -30,7 +31,7 @@ func TestFrostfsIDConfig(t *testing.T) {
|
|||
v := viper.New()
|
||||
v.Set("frostfsid.admin", fmts[i])
|
||||
|
||||
actual, found, err := getFrostfsIDAdmin(v)
|
||||
actual, found, err := helper.GetFrostfsIDAdmin(v)
|
||||
require.NoError(t, err)
|
||||
require.True(t, found)
|
||||
require.Equal(t, pks[i].GetScriptHash(), actual)
|
||||
|
@ -40,14 +41,14 @@ func TestFrostfsIDConfig(t *testing.T) {
|
|||
v := viper.New()
|
||||
v.Set("frostfsid.admin", "abc")
|
||||
|
||||
_, found, err := getFrostfsIDAdmin(v)
|
||||
_, found, err := helper.GetFrostfsIDAdmin(v)
|
||||
require.Error(t, err)
|
||||
require.True(t, found)
|
||||
})
|
||||
t.Run("missing key", func(t *testing.T) {
|
||||
v := viper.New()
|
||||
|
||||
_, found, err := getFrostfsIDAdmin(v)
|
||||
_, found, err := helper.GetFrostfsIDAdmin(v)
|
||||
require.NoError(t, err)
|
||||
require.False(t, found)
|
||||
})
|
15
cmd/frostfs-adm/internal/modules/morph/frostfsid/root.go
Normal file
15
cmd/frostfs-adm/internal/modules/morph/frostfsid/root.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package frostfsid
|
||||
|
||||
func init() {
|
||||
initFrostfsIDCreateNamespaceCmd()
|
||||
initFrostfsIDListNamespacesCmd()
|
||||
initFrostfsIDCreateSubjectCmd()
|
||||
initFrostfsIDDeleteSubjectCmd()
|
||||
initFrostfsIDListSubjectsCmd()
|
||||
initFrostfsIDCreateGroupCmd()
|
||||
initFrostfsIDDeleteGroupCmd()
|
||||
initFrostfsIDListGroupsCmd()
|
||||
initFrostfsIDAddSubjectToGroupCmd()
|
||||
initFrostfsIDRemoveSubjectFromGroupCmd()
|
||||
initFrostfsIDListGroupSubjectsCmd()
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package morph
|
||||
package generate
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
@ -6,11 +6,13 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"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/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"
|
||||
|
@ -24,33 +26,27 @@ import (
|
|||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
const (
|
||||
singleAccountName = "single"
|
||||
committeeAccountName = "committee"
|
||||
consensusAccountName = "consensus"
|
||||
)
|
||||
|
||||
func generateAlphabetCreds(cmd *cobra.Command, _ []string) error {
|
||||
func AlphabetCreds(cmd *cobra.Command, _ []string) error {
|
||||
// alphabet size is not part of the config
|
||||
size, err := cmd.Flags().GetUint(alphabetSizeFlag)
|
||||
size, err := cmd.Flags().GetUint(commonflags.AlphabetSizeFlag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if size == 0 {
|
||||
return errors.New("size must be > 0")
|
||||
}
|
||||
if size > maxAlphabetNodes {
|
||||
return ErrTooManyAlphabetNodes
|
||||
if size > constants.MaxAlphabetNodes {
|
||||
return helper.ErrTooManyAlphabetNodes
|
||||
}
|
||||
|
||||
v := viper.GetViper()
|
||||
walletDir := config.ResolveHomePath(viper.GetString(alphabetWalletsFlag))
|
||||
walletDir := config.ResolveHomePath(viper.GetString(commonflags.AlphabetWalletsFlag))
|
||||
pwds, err := initializeWallets(v, walletDir, int(size))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = initializeContractWallet(v, walletDir)
|
||||
_, err = helper.InitializeContractWallet(v, walletDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -87,7 +83,7 @@ func initializeWallets(v *viper.Viper, walletDir string, size int) ([]string, er
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("can't create wallet: %w", err)
|
||||
}
|
||||
if err := w.CreateAccount(singleAccountName, password); err != nil {
|
||||
if err := w.CreateAccount(constants.SingleAccountName, password); err != nil {
|
||||
return nil, fmt.Errorf("can't create account: %w", err)
|
||||
}
|
||||
|
||||
|
@ -106,10 +102,10 @@ func initializeWallets(v *viper.Viper, walletDir string, size int) ([]string, er
|
|||
i := i
|
||||
ps := pubs.Copy()
|
||||
errG.Go(func() error {
|
||||
if err := addMultisigAccount(wallets[i], majCount, committeeAccountName, passwords[i], ps); err != nil {
|
||||
if err := addMultisigAccount(wallets[i], majCount, constants.CommitteeAccountName, passwords[i], ps); err != nil {
|
||||
return fmt.Errorf("can't create committee account: %w", err)
|
||||
}
|
||||
if err := addMultisigAccount(wallets[i], bftCount, consensusAccountName, passwords[i], ps); err != nil {
|
||||
if err := addMultisigAccount(wallets[i], bftCount, constants.ConsensusAccountName, passwords[i], ps); err != nil {
|
||||
return fmt.Errorf("can't create consentus account: %w", err)
|
||||
}
|
||||
if err := wallets[i].SavePretty(); err != nil {
|
||||
|
@ -144,7 +140,7 @@ func generateStorageCreds(cmd *cobra.Command, _ []string) error {
|
|||
|
||||
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)
|
||||
storageWalletPath, _ := cmd.Flags().GetString(commonflags.StorageWalletFlag)
|
||||
// wallet address is not part of the config
|
||||
walletAddress, _ := cmd.Flags().GetString(walletAddressFlag)
|
||||
|
||||
|
@ -157,7 +153,7 @@ func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) (err error
|
|||
}
|
||||
} else {
|
||||
if storageWalletPath == "" {
|
||||
return fmt.Errorf("missing wallet path (use '--%s <out.json>')", storageWalletFlag)
|
||||
return fmt.Errorf("missing wallet path (use '--%s <out.json>')", commonflags.StorageWalletFlag)
|
||||
}
|
||||
|
||||
var w *wallet.Wallet
|
||||
|
@ -182,7 +178,7 @@ func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) (err error
|
|||
}
|
||||
|
||||
if label == "" {
|
||||
label = singleAccountName
|
||||
label = constants.SingleAccountName
|
||||
}
|
||||
|
||||
if err := w.CreateAccount(label, password); err != nil {
|
||||
|
@ -195,12 +191,12 @@ func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) (err error
|
|||
|
||||
gasStr := viper.GetString(gasFlag)
|
||||
|
||||
gasAmount, err := parseGASAmount(gasStr)
|
||||
gasAmount, err := helper.ParseGASAmount(gasStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wCtx, err := newInitializeContext(cmd, viper.GetViper())
|
||||
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -213,20 +209,9 @@ func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) (err error
|
|||
return fmt.Errorf("BUG: invalid transfer arguments: %w", bw.Err)
|
||||
}
|
||||
|
||||
if err := wCtx.sendCommitteeTx(bw.Bytes(), false); err != nil {
|
||||
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
|
||||
return wCtx.AwaitTx()
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package morph
|
||||
package generate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -10,6 +10,8 @@ import (
|
|||
"sync"
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"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"
|
||||
|
@ -20,57 +22,55 @@ import (
|
|||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
const testContractPassword = "grouppass"
|
||||
|
||||
func TestGenerateAlphabet(t *testing.T) {
|
||||
const size = 4
|
||||
|
||||
walletDir := t.TempDir()
|
||||
buf := setupTestTerminal(t)
|
||||
|
||||
cmd := generateAlphabetCmd
|
||||
cmd := GenerateAlphabetCmd
|
||||
v := viper.GetViper()
|
||||
|
||||
t.Run("zero size", func(t *testing.T) {
|
||||
buf.Reset()
|
||||
v.Set(alphabetWalletsFlag, walletDir)
|
||||
require.NoError(t, cmd.Flags().Set(alphabetSizeFlag, "0"))
|
||||
v.Set(commonflags.AlphabetWalletsFlag, walletDir)
|
||||
require.NoError(t, cmd.Flags().Set(commonflags.AlphabetSizeFlag, "0"))
|
||||
buf.WriteString("pass\r")
|
||||
require.Error(t, generateAlphabetCreds(cmd, nil))
|
||||
require.Error(t, AlphabetCreds(cmd, nil))
|
||||
})
|
||||
t.Run("no password provided", func(t *testing.T) {
|
||||
buf.Reset()
|
||||
v.Set(alphabetWalletsFlag, walletDir)
|
||||
require.NoError(t, cmd.Flags().Set(alphabetSizeFlag, "1"))
|
||||
require.Error(t, generateAlphabetCreds(cmd, nil))
|
||||
v.Set(commonflags.AlphabetWalletsFlag, walletDir)
|
||||
require.NoError(t, cmd.Flags().Set(commonflags.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(alphabetWalletsFlag, dir)
|
||||
require.NoError(t, cmd.Flags().Set(alphabetSizeFlag, "1"))
|
||||
v.Set(commonflags.AlphabetWalletsFlag, dir)
|
||||
require.NoError(t, cmd.Flags().Set(commonflags.AlphabetSizeFlag, "1"))
|
||||
buf.WriteString("pass\r")
|
||||
require.Error(t, generateAlphabetCreds(cmd, nil))
|
||||
require.Error(t, AlphabetCreds(cmd, nil))
|
||||
})
|
||||
t.Run("no password for contract group wallet", func(t *testing.T) {
|
||||
buf.Reset()
|
||||
v.Set(alphabetWalletsFlag, walletDir)
|
||||
require.NoError(t, cmd.Flags().Set(alphabetSizeFlag, strconv.FormatUint(size, 10)))
|
||||
v.Set(commonflags.AlphabetWalletsFlag, walletDir)
|
||||
require.NoError(t, cmd.Flags().Set(commonflags.AlphabetSizeFlag, strconv.FormatUint(size, 10)))
|
||||
for i := uint64(0); i < size; i++ {
|
||||
buf.WriteString(strconv.FormatUint(i, 10) + "\r")
|
||||
}
|
||||
require.Error(t, generateAlphabetCreds(cmd, nil))
|
||||
require.Error(t, AlphabetCreds(cmd, nil))
|
||||
})
|
||||
|
||||
buf.Reset()
|
||||
v.Set(alphabetWalletsFlag, walletDir)
|
||||
require.NoError(t, generateAlphabetCmd.Flags().Set(alphabetSizeFlag, strconv.FormatUint(size, 10)))
|
||||
v.Set(commonflags.AlphabetWalletsFlag, walletDir)
|
||||
require.NoError(t, GenerateAlphabetCmd.Flags().Set(commonflags.AlphabetSizeFlag, strconv.FormatUint(size, 10)))
|
||||
for i := uint64(0); i < size; i++ {
|
||||
buf.WriteString(strconv.FormatUint(i, 10) + "\r")
|
||||
}
|
||||
|
||||
buf.WriteString(testContractPassword + "\r")
|
||||
require.NoError(t, generateAlphabetCreds(generateAlphabetCmd, nil))
|
||||
buf.WriteString(constants.TestContractPassword + "\r")
|
||||
require.NoError(t, AlphabetCreds(GenerateAlphabetCmd, nil))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := uint64(0); i < size; i++ {
|
||||
|
@ -87,12 +87,12 @@ func TestGenerateAlphabet(t *testing.T) {
|
|||
err := a.Decrypt(strconv.FormatUint(i, 10), keys.NEP2ScryptParams())
|
||||
require.NoError(t, err, "can't decrypt account")
|
||||
switch a.Label {
|
||||
case consensusAccountName:
|
||||
case constants.ConsensusAccountName:
|
||||
require.Equal(t, smartcontract.GetDefaultHonestNodeCount(size), len(a.Contract.Parameters))
|
||||
case committeeAccountName:
|
||||
case constants.CommitteeAccountName:
|
||||
require.Equal(t, smartcontract.GetMajorityHonestNodeCount(size), len(a.Contract.Parameters))
|
||||
default:
|
||||
require.Equal(t, singleAccountName, a.Label)
|
||||
require.Equal(t, constants.SingleAccountName, a.Label)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
@ -100,11 +100,11 @@ func TestGenerateAlphabet(t *testing.T) {
|
|||
wg.Wait()
|
||||
|
||||
t.Run("check contract group wallet", func(t *testing.T) {
|
||||
p := filepath.Join(walletDir, contractWalletFilename)
|
||||
p := filepath.Join(walletDir, constants.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(testContractPassword, keys.NEP2ScryptParams()))
|
||||
require.NoError(t, w.Accounts[0].Decrypt(constants.TestContractPassword, keys.NEP2ScryptParams()))
|
||||
})
|
||||
}
|
||||
|
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/commonflags"
|
||||
"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(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.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(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.RefillGasAmountFlag, cmd.Flags().Lookup(commonflags.RefillGasAmountFlag))
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return refillGas(cmd, commonflags.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(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
},
|
||||
RunE: AlphabetCreds,
|
||||
}
|
||||
)
|
||||
|
||||
func initRefillGasCmd() {
|
||||
RefillGasCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
RefillGasCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
RefillGasCmd.Flags().String(commonflags.StorageWalletFlag, "", "Path to storage node wallet")
|
||||
RefillGasCmd.Flags().String(walletAddressFlag, "", "Address of wallet")
|
||||
RefillGasCmd.Flags().String(commonflags.RefillGasAmountFlag, "", "Additional amount of GAS to transfer")
|
||||
RefillGasCmd.MarkFlagsMutuallyExclusive(walletAddressFlag, commonflags.StorageWalletFlag)
|
||||
}
|
||||
|
||||
func initGenerateStorageCmd() {
|
||||
GenerateStorageCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
GenerateStorageCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
GenerateStorageCmd.Flags().String(commonflags.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(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
GenerateAlphabetCmd.Flags().Uint(commonflags.AlphabetSizeFlag, 7, "Amount of alphabet wallets to generate")
|
||||
}
|
||||
|
||||
func init() {
|
||||
initRefillGasCmd()
|
||||
initGenerateStorageCmd()
|
||||
initGenerateAlphabetCmd()
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
contractWalletFilename = "contract.json"
|
||||
contractWalletPasswordKey = "contract"
|
||||
)
|
||||
|
||||
func initializeContractWallet(v *viper.Viper, walletDir string) (*wallet.Wallet, error) {
|
||||
password, err := config.GetPassword(v, contractWalletPasswordKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w, err := wallet.NewWallet(filepath.Join(walletDir, contractWalletFilename))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
acc, err := wallet.NewAccount()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = acc.Encrypt(password, keys.NEP2ScryptParams())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w.AddAccount(acc)
|
||||
if err := w.SavePretty(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func openContractWallet(v *viper.Viper, cmd *cobra.Command, walletDir string) (*wallet.Wallet, error) {
|
||||
p := filepath.Join(walletDir, contractWalletFilename)
|
||||
w, err := wallet.NewWalletFromFile(p)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("can't open wallet: %w", err)
|
||||
}
|
||||
|
||||
cmd.Printf("Contract group wallet is missing, initialize at %s\n", p)
|
||||
return initializeContractWallet(v, walletDir)
|
||||
}
|
||||
|
||||
password, err := config.GetPassword(v, contractWalletPasswordKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range w.Accounts {
|
||||
if err := w.Accounts[i].Decrypt(password, keys.NEP2ScryptParams()); err != nil {
|
||||
return nil, fmt.Errorf("can't unlock wallet: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func (c *initializeContext) addManifestGroup(h util.Uint160, cs *contractState) error {
|
||||
priv := c.ContractWallet.Accounts[0].PrivateKey()
|
||||
pub := priv.PublicKey()
|
||||
|
||||
sig := priv.Sign(h.BytesBE())
|
||||
found := false
|
||||
|
||||
for i := range cs.Manifest.Groups {
|
||||
if cs.Manifest.Groups[i].PublicKey.Equal(pub) {
|
||||
cs.Manifest.Groups[i].Signature = sig
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
cs.Manifest.Groups = append(cs.Manifest.Groups, manifest.Group{
|
||||
PublicKey: pub,
|
||||
Signature: sig,
|
||||
})
|
||||
}
|
||||
|
||||
data, err := json.Marshal(cs.Manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cs.RawManifest = data
|
||||
return nil
|
||||
}
|
170
cmd/frostfs-adm/internal/modules/morph/helper/contract.go
Normal file
170
cmd/frostfs-adm/internal/modules/morph/helper/contract.go
Normal file
|
@ -0,0 +1,170 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func getFrostfsIDAdminFromContract(roInvoker *invoker.Invoker) (util.Uint160, bool, error) {
|
||||
r := management.NewReader(roInvoker)
|
||||
cs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return util.Uint160{}, false, fmt.Errorf("get nns contract: %w", err)
|
||||
}
|
||||
fidHash, err := NNSResolveHash(roInvoker, cs.Hash, DomainOf(constants.FrostfsIDContract))
|
||||
if err != nil {
|
||||
return util.Uint160{}, false, fmt.Errorf("resolve frostfsid contract hash: %w", err)
|
||||
}
|
||||
item, err := unwrap.Item(roInvoker.Call(fidHash, "getAdmin"))
|
||||
if err != nil {
|
||||
return util.Uint160{}, false, fmt.Errorf("getAdmin: %w", err)
|
||||
}
|
||||
if _, ok := item.(stackitem.Null); ok {
|
||||
return util.Uint160{}, false, nil
|
||||
}
|
||||
|
||||
bs, err := item.TryBytes()
|
||||
if err != nil {
|
||||
return util.Uint160{}, true, fmt.Errorf("getAdmin: decode result: %w", err)
|
||||
}
|
||||
h, err := util.Uint160DecodeBytesBE(bs)
|
||||
if err != nil {
|
||||
return util.Uint160{}, true, fmt.Errorf("getAdmin: decode result: %w", err)
|
||||
}
|
||||
return h, true, nil
|
||||
}
|
||||
|
||||
func GetContractDeployData(c *InitializeContext, ctrName string, keysParam []any, method string) ([]any, error) {
|
||||
items := make([]any, 0, 6)
|
||||
|
||||
switch ctrName {
|
||||
case constants.FrostfsContract:
|
||||
items = append(items,
|
||||
c.Contracts[constants.ProcessingContract].Hash,
|
||||
keysParam,
|
||||
smartcontract.Parameter{})
|
||||
case constants.ProcessingContract:
|
||||
items = append(items, c.Contracts[constants.FrostfsContract].Hash)
|
||||
return items[1:], nil // no notary info
|
||||
case constants.BalanceContract:
|
||||
items = append(items,
|
||||
c.Contracts[constants.NetmapContract].Hash,
|
||||
c.Contracts[constants.ContainerContract].Hash)
|
||||
case constants.ContainerContract:
|
||||
// In case if NNS is updated multiple times, we can't calculate
|
||||
// it's actual hash based on local data, thus query chain.
|
||||
r := management.NewReader(c.ReadOnlyInvoker)
|
||||
nnsCs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get nns contract: %w", err)
|
||||
}
|
||||
items = append(items,
|
||||
c.Contracts[constants.NetmapContract].Hash,
|
||||
c.Contracts[constants.BalanceContract].Hash,
|
||||
c.Contracts[constants.FrostfsIDContract].Hash,
|
||||
nnsCs.Hash,
|
||||
"container")
|
||||
case constants.FrostfsIDContract:
|
||||
var (
|
||||
h util.Uint160
|
||||
found bool
|
||||
err error
|
||||
)
|
||||
if method == constants.UpdateMethodName {
|
||||
h, found, err = getFrostfsIDAdminFromContract(c.ReadOnlyInvoker)
|
||||
}
|
||||
if method != constants.UpdateMethodName || err == nil && !found {
|
||||
h, found, err = GetFrostfsIDAdmin(viper.GetViper())
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if found {
|
||||
items = append(items, h)
|
||||
} else {
|
||||
items = append(items, c.Contracts[constants.ProxyContract].Hash)
|
||||
}
|
||||
case constants.NetmapContract:
|
||||
md := GetDefaultNetmapContractConfigMap()
|
||||
if method == constants.UpdateMethodName {
|
||||
if err := MergeNetmapConfig(c.ReadOnlyInvoker, md); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var configParam []any
|
||||
for k, v := range md {
|
||||
configParam = append(configParam, k, v)
|
||||
}
|
||||
|
||||
items = append(items,
|
||||
c.Contracts[constants.BalanceContract].Hash,
|
||||
c.Contracts[constants.ContainerContract].Hash,
|
||||
keysParam,
|
||||
configParam)
|
||||
case constants.ProxyContract:
|
||||
items = nil
|
||||
case constants.PolicyContract:
|
||||
items = append(items, c.Contracts[constants.ProxyContract].Hash)
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid contract name: %s", ctrName))
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func GetContractDeployParameters(cs *ContractState, deployData []any) []any {
|
||||
return []any{cs.RawNEF, cs.RawManifest, deployData}
|
||||
}
|
||||
|
||||
func DeployNNS(c *InitializeContext, method string) error {
|
||||
cs := c.GetContract(constants.NNSContract)
|
||||
h := cs.Hash
|
||||
|
||||
nnsCs, err := c.NNSContractState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if nnsCs != nil {
|
||||
if nnsCs.NEF.Checksum == cs.NEF.Checksum {
|
||||
if method == constants.DeployMethodName {
|
||||
c.Command.Println("NNS contract is already deployed.")
|
||||
} else {
|
||||
c.Command.Println("NNS contract is already updated.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
h = nnsCs.Hash
|
||||
}
|
||||
|
||||
err = AddManifestGroup(c.ContractWallet, h, cs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't sign manifest group: %v", err)
|
||||
}
|
||||
|
||||
params := GetContractDeployParameters(cs, nil)
|
||||
|
||||
invokeHash := management.Hash
|
||||
if method == constants.UpdateMethodName {
|
||||
invokeHash = nnsCs.Hash
|
||||
}
|
||||
|
||||
tx, err := c.CommitteeAct.MakeCall(invokeHash, method, params...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create deploy tx for %s: %w", constants.NNSContract, err)
|
||||
}
|
||||
|
||||
if err := c.MultiSignAndSend(tx, constants.CommitteeAccountName); err != nil {
|
||||
return fmt.Errorf("can't send deploy transaction: %w", err)
|
||||
}
|
||||
|
||||
return c.AwaitTx()
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package morph
|
||||
package helper
|
||||
|
||||
import (
|
||||
"context"
|
35
cmd/frostfs-adm/internal/modules/morph/helper/frostfsid.go
Normal file
35
cmd/frostfs-adm/internal/modules/morph/helper/frostfsid.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"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/util"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const frostfsIDAdminConfigKey = "frostfsid.admin"
|
||||
|
||||
func GetFrostfsIDAdmin(v *viper.Viper) (util.Uint160, bool, error) {
|
||||
admin := v.GetString(frostfsIDAdminConfigKey)
|
||||
if admin == "" {
|
||||
return util.Uint160{}, false, nil
|
||||
}
|
||||
|
||||
h, err := address.StringToUint160(admin)
|
||||
if err == nil {
|
||||
return h, true, nil
|
||||
}
|
||||
|
||||
h, err = util.Uint160DecodeStringLE(admin)
|
||||
if err == nil {
|
||||
return h, true, nil
|
||||
}
|
||||
|
||||
pk, err := keys.NewPublicKeyFromString(admin)
|
||||
if err == nil {
|
||||
return pk.GetScriptHash(), true, nil
|
||||
}
|
||||
return util.Uint160{}, true, fmt.Errorf("frostfsid: admin is invalid: '%s'", admin)
|
||||
}
|
39
cmd/frostfs-adm/internal/modules/morph/helper/group.go
Normal file
39
cmd/frostfs-adm/internal/modules/morph/helper/group.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
)
|
||||
|
||||
func AddManifestGroup(cw *wallet.Wallet, h util.Uint160, cs *ContractState) error {
|
||||
fyrchik marked this conversation as resolved
fyrchik
commented
It is used in multiple packages? It is used in multiple packages?
acid-ant
commented
Yes, in Yes, in `contract` and `initialize`
|
||||
priv := cw.Accounts[0].PrivateKey()
|
||||
pub := priv.PublicKey()
|
||||
|
||||
sig := priv.Sign(h.BytesBE())
|
||||
found := false
|
||||
|
||||
for i := range cs.Manifest.Groups {
|
||||
if cs.Manifest.Groups[i].PublicKey.Equal(pub) {
|
||||
cs.Manifest.Groups[i].Signature = sig
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
cs.Manifest.Groups = append(cs.Manifest.Groups, manifest.Group{
|
||||
PublicKey: pub,
|
||||
Signature: sig,
|
||||
})
|
||||
}
|
||||
|
||||
data, err := json.Marshal(cs.Manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cs.RawManifest = data
|
||||
return nil
|
||||
}
|
211
cmd/frostfs-adm/internal/modules/morph/helper/initialize.go
Normal file
211
cmd/frostfs-adm/internal/modules/morph/helper/initialize.go
Normal file
|
@ -0,0 +1,211 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
nns2 "github.com/nspcc-dev/neo-go/pkg/rpcclient/nns"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var ErrTooManyAlphabetNodes = fmt.Errorf("too many alphabet nodes (maximum allowed is %d)", constants.MaxAlphabetNodes)
|
||||
|
||||
func AwaitTx(cmd *cobra.Command, c Client, txs []HashVUBPair) error {
|
||||
cmd.Println("Waiting for transactions to persist...")
|
||||
|
||||
at := trigger.Application
|
||||
|
||||
var retErr error
|
||||
|
||||
loop:
|
||||
for i := range txs {
|
||||
var it int
|
||||
var pollInterval time.Duration
|
||||
var pollIntervalChanged bool
|
||||
for {
|
||||
// We must fetch current height before application log, to avoid race condition.
|
||||
currBlock, err := c.GetBlockCount()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch current block height: %w", err)
|
||||
}
|
||||
res, err := c.GetApplicationLog(txs[i].Hash, &at)
|
||||
if err == nil {
|
||||
if retErr == nil && len(res.Executions) > 0 && res.Executions[0].VMState != vmstate.Halt {
|
||||
retErr = fmt.Errorf("tx %d persisted in %s state: %s",
|
||||
i, res.Executions[0].VMState, res.Executions[0].FaultException)
|
||||
}
|
||||
continue loop
|
||||
}
|
||||
if txs[i].Vub < currBlock {
|
||||
return fmt.Errorf("tx was not persisted: Vub=%d, height=%d", txs[i].Vub, currBlock)
|
||||
}
|
||||
|
||||
pollInterval, pollIntervalChanged = NextPollInterval(it, pollInterval)
|
||||
if pollIntervalChanged && viper.GetBool(commonflags.Verbose) {
|
||||
cmd.Printf("Pool interval to check transaction persistence changed: %s\n", pollInterval.String())
|
||||
}
|
||||
|
||||
timer := time.NewTimer(pollInterval)
|
||||
select {
|
||||
case <-cmd.Context().Done():
|
||||
return cmd.Context().Err()
|
||||
case <-timer.C:
|
||||
}
|
||||
|
||||
it++
|
||||
}
|
||||
}
|
||||
|
||||
return retErr
|
||||
}
|
||||
|
||||
func NextPollInterval(it int, previous time.Duration) (time.Duration, bool) {
|
||||
const minPollInterval = 1 * time.Second
|
||||
const maxPollInterval = 16 * time.Second
|
||||
const changeAfter = 5
|
||||
if it == 0 {
|
||||
return minPollInterval, true
|
||||
}
|
||||
if it%changeAfter != 0 {
|
||||
return previous, false
|
||||
}
|
||||
nextInterval := previous * 2
|
||||
if nextInterval > maxPollInterval {
|
||||
return maxPollInterval, previous != maxPollInterval
|
||||
}
|
||||
return nextInterval, true
|
||||
}
|
||||
|
||||
func GetWalletAccount(w *wallet.Wallet, typ string) (*wallet.Account, error) {
|
||||
for i := range w.Accounts {
|
||||
if w.Accounts[i].Label == typ {
|
||||
return w.Accounts[i], nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("account for '%s' not found", typ)
|
||||
}
|
||||
|
||||
func NNSResolve(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (stackitem.Item, error) {
|
||||
return unwrap.Item(inv.Call(nnsHash, "resolve", domain, int64(nns.TXT)))
|
||||
}
|
||||
|
||||
// ParseNNSResolveResult parses the result of resolving NNS record.
|
||||
// It works with multiple formats (corresponding to multiple NNS versions).
|
||||
// If array of hashes is provided, it returns only the first one.
|
||||
func ParseNNSResolveResult(res stackitem.Item) (util.Uint160, error) {
|
||||
arr, ok := res.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
arr = []stackitem.Item{res}
|
||||
}
|
||||
if _, ok := res.Value().(stackitem.Null); ok || len(arr) == 0 {
|
||||
return util.Uint160{}, errors.New("NNS record is missing")
|
||||
}
|
||||
for i := range arr {
|
||||
bs, err := arr[i].TryBytes()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// We support several formats for hash encoding, this logic should be maintained in sync
|
||||
// with NNSResolve from pkg/morph/client/nns.go
|
||||
h, err := util.Uint160DecodeStringLE(string(bs))
|
||||
if err == nil {
|
||||
return h, nil
|
||||
}
|
||||
|
||||
h, err = address.StringToUint160(string(bs))
|
||||
if err == nil {
|
||||
return h, nil
|
||||
}
|
||||
}
|
||||
return util.Uint160{}, errors.New("no valid hashes are found")
|
||||
}
|
||||
|
||||
// NNSResolveHash Returns errMissingNNSRecord if invocation fault exception contains "token not found".
|
||||
func NNSResolveHash(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (util.Uint160, error) {
|
||||
item, err := NNSResolve(inv, nnsHash, domain)
|
||||
if err != nil {
|
||||
return util.Uint160{}, err
|
||||
}
|
||||
return ParseNNSResolveResult(item)
|
||||
}
|
||||
|
||||
func DomainOf(contract string) string {
|
||||
return contract + ".frostfs"
|
||||
}
|
||||
|
||||
func NNSResolveKey(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (*keys.PublicKey, error) {
|
||||
res, err := NNSResolve(inv, nnsHash, domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := res.Value().(stackitem.Null); ok {
|
||||
return nil, errors.New("NNS record is missing")
|
||||
}
|
||||
arr, ok := res.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
return nil, errors.New("API of the NNS contract method `resolve` has changed")
|
||||
}
|
||||
for i := range arr {
|
||||
var bs []byte
|
||||
bs, err = arr[i].TryBytes()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
return keys.NewPublicKeyFromString(string(bs))
|
||||
}
|
||||
return nil, errors.New("no valid keys are found")
|
||||
}
|
||||
|
||||
func NNSIsAvailable(c Client, nnsHash util.Uint160, name string) (bool, error) {
|
||||
switch c.(type) {
|
||||
case *rpcclient.Client:
|
||||
inv := invoker.New(c, nil)
|
||||
reader := nns2.NewReader(inv, nnsHash)
|
||||
return reader.IsAvailable(name)
|
||||
default:
|
||||
b, err := unwrap.Bool(InvokeFunction(c, nnsHash, "isAvailable", []any{name}, nil))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("`isAvailable`: invalid response: %w", err)
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
}
|
||||
|
||||
func CheckNotaryEnabled(c Client) error {
|
||||
ns, err := c.GetNativeContracts()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get native contract hashes: %w", err)
|
||||
}
|
||||
|
||||
notaryEnabled := false
|
||||
nativeHashes := make(map[string]util.Uint160, len(ns))
|
||||
for i := range ns {
|
||||
if ns[i].Manifest.Name == nativenames.Notary {
|
||||
notaryEnabled = true
|
||||
}
|
||||
nativeHashes[ns[i].Manifest.Name] = ns[i].Hash
|
||||
}
|
||||
if !notaryEnabled {
|
||||
return errors.New("notary contract must be enabled")
|
||||
}
|
||||
return nil
|
||||
}
|
545
cmd/frostfs-adm/internal/modules/morph/helper/initialize_ctx.go
Normal file
545
cmd/frostfs-adm/internal/modules/morph/helper/initialize_ctx.go
Normal file
|
@ -0,0 +1,545 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
io2 "io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||
"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/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/context"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
||||
"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/vm/vmstate"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type ContractState struct {
|
||||
NEF *nef.File
|
||||
RawNEF []byte
|
||||
Manifest *manifest.Manifest
|
||||
RawManifest []byte
|
||||
Hash util.Uint160
|
||||
}
|
||||
|
||||
type Cache struct {
|
||||
NNSCs *state.Contract
|
||||
GroupKey *keys.PublicKey
|
||||
}
|
||||
|
||||
type InitializeContext struct {
|
||||
ClientContext
|
||||
Cache
|
||||
// CommitteeAcc is used for retrieving the committee address and the verification script.
|
||||
CommitteeAcc *wallet.Account
|
||||
// ConsensusAcc is used for retrieving the committee address and the verification script.
|
||||
ConsensusAcc *wallet.Account
|
||||
Wallets []*wallet.Wallet
|
||||
// ContractWallet is a wallet for providing the contract group signature.
|
||||
ContractWallet *wallet.Wallet
|
||||
// Accounts contains simple signature accounts in the same order as in Wallets.
|
||||
Accounts []*wallet.Account
|
||||
Contracts map[string]*ContractState
|
||||
Command *cobra.Command
|
||||
ContractPath string
|
||||
ContractURL string
|
||||
}
|
||||
|
||||
func (cs *ContractState) Parse() error {
|
||||
nf, err := nef.FileFromBytes(cs.RawNEF)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't parse NEF file: %w", err)
|
||||
}
|
||||
|
||||
m := new(manifest.Manifest)
|
||||
if err := json.Unmarshal(cs.RawManifest, m); err != nil {
|
||||
return fmt.Errorf("can't parse manifest file: %w", err)
|
||||
}
|
||||
|
||||
cs.NEF = &nf
|
||||
cs.Manifest = m
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewInitializeContext(cmd *cobra.Command, v *viper.Viper) (*InitializeContext, error) {
|
||||
walletDir := config.ResolveHomePath(viper.GetString(commonflags.AlphabetWalletsFlag))
|
||||
wallets, err := GetAlphabetWallets(v, walletDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
needContracts := cmd.Name() == "update-contracts" || cmd.Name() == "init"
|
||||
|
||||
var w *wallet.Wallet
|
||||
w, err = getWallet(cmd, v, needContracts, walletDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := createClient(cmd, v, wallets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
committeeAcc, err := GetWalletAccount(wallets[0], constants.CommitteeAccountName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't find committee account: %w", err)
|
||||
}
|
||||
|
||||
consensusAcc, err := GetWalletAccount(wallets[0], constants.ConsensusAccountName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't find consensus account: %w", err)
|
||||
}
|
||||
|
||||
if err := validateInit(cmd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctrPath, err := getContractsPath(cmd, needContracts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ctrURL string
|
||||
if needContracts {
|
||||
ctrURL, _ = cmd.Flags().GetString(commonflags.ContractsURLFlag)
|
||||
}
|
||||
|
||||
if err := CheckNotaryEnabled(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accounts, err := createWalletAccounts(wallets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cliCtx, err := DefaultClientContext(c, committeeAcc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("client context: %w", err)
|
||||
}
|
||||
|
||||
initCtx := &InitializeContext{
|
||||
ClientContext: *cliCtx,
|
||||
ConsensusAcc: consensusAcc,
|
||||
CommitteeAcc: committeeAcc,
|
||||
ContractWallet: w,
|
||||
Wallets: wallets,
|
||||
Accounts: accounts,
|
||||
Command: cmd,
|
||||
Contracts: make(map[string]*ContractState),
|
||||
ContractPath: ctrPath,
|
||||
ContractURL: ctrURL,
|
||||
}
|
||||
|
||||
if needContracts {
|
||||
err := readContracts(initCtx, constants.FullContractList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return initCtx, nil
|
||||
}
|
||||
|
||||
func validateInit(cmd *cobra.Command) error {
|
||||
if cmd.Name() != "init" {
|
||||
return nil
|
||||
}
|
||||
if viper.GetInt64(commonflags.EpochDurationInitFlag) <= 0 {
|
||||
return fmt.Errorf("epoch duration must be positive")
|
||||
}
|
||||
|
||||
if viper.GetInt64(commonflags.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 ldf := cmd.Flags().Lookup(commonflags.LocalDumpFlag); ldf != nil && ldf.Changed {
|
||||
if cmd.Flags().Changed(commonflags.EndpointFlag) {
|
||||
return nil, fmt.Errorf("`%s` and `%s` flags are mutually exclusive", commonflags.EndpointFlag, commonflags.LocalDumpFlag)
|
||||
}
|
||||
c, err = NewLocalClient(cmd, v, wallets, ldf.Value.String())
|
||||
} else {
|
||||
c, err = GetN3Client(v)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't create N3 client: %w", err)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func getContractsPath(cmd *cobra.Command, needContracts bool) (string, error) {
|
||||
if !needContracts {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
ctrPath, err := cmd.Flags().GetString(commonflags.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, constants.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 readContracts(c *InitializeContext, names []string) error {
|
||||
var (
|
||||
fi os.FileInfo
|
||||
err error
|
||||
)
|
||||
if c.ContractPath != "" {
|
||||
fi, err = os.Stat(c.ContractPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid contracts path: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if c.ContractPath != "" && fi.IsDir() {
|
||||
for _, ctrName := range names {
|
||||
cs, err := ReadContract(filepath.Join(c.ContractPath, ctrName), ctrName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Contracts[ctrName] = cs
|
||||
}
|
||||
} else {
|
||||
var r io2.ReadCloser
|
||||
if c.ContractPath != "" {
|
||||
r, err = os.Open(c.ContractPath)
|
||||
} else if c.ContractURL != "" {
|
||||
r, err = downloadContracts(c.Command, c.ContractURL)
|
||||
} else {
|
||||
r, err = downloadContractsFromRepository(c.Command)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't open contracts archive: %w", err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
m, err := readContractsFromArchive(r, names)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, name := range names {
|
||||
if err := m[name].Parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Contracts[name] = m[name]
|
||||
}
|
||||
}
|
||||
|
||||
for _, ctrName := range names {
|
||||
if ctrName != constants.AlphabetContract {
|
||||
cs := c.Contracts[ctrName]
|
||||
cs.Hash = state.CreateContractHash(c.CommitteeAcc.Contract.ScriptHash(),
|
||||
cs.NEF.Checksum, cs.Manifest.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *InitializeContext) Close() {
|
||||
if local, ok := c.Client.(*LocalClient); ok {
|
||||
err := local.Dump()
|
||||
if err != nil {
|
||||
c.Command.PrintErrf("Can't write dump: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *InitializeContext) AwaitTx() error {
|
||||
return c.ClientContext.AwaitTx(c.Command)
|
||||
}
|
||||
|
||||
func (c *InitializeContext) NNSContractState() (*state.Contract, error) {
|
||||
if c.NNSCs != nil {
|
||||
return c.NNSCs, nil
|
||||
}
|
||||
|
||||
r := management.NewReader(c.ReadOnlyInvoker)
|
||||
cs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.NNSCs = cs
|
||||
return cs, nil
|
||||
}
|
||||
|
||||
func (c *InitializeContext) GetSigner(tryGroup bool, acc *wallet.Account) transaction.Signer {
|
||||
if tryGroup && c.GroupKey != nil {
|
||||
return transaction.Signer{
|
||||
Account: acc.Contract.ScriptHash(),
|
||||
Scopes: transaction.CustomGroups,
|
||||
AllowedGroups: keys.PublicKeys{c.GroupKey},
|
||||
}
|
||||
}
|
||||
|
||||
signer := transaction.Signer{
|
||||
Account: acc.Contract.ScriptHash(),
|
||||
Scopes: transaction.Global, // Scope is important, as we have nested call to container contract.
|
||||
}
|
||||
|
||||
if !tryGroup {
|
||||
return signer
|
||||
}
|
||||
|
||||
nnsCs, err := c.NNSContractState()
|
||||
if err != nil {
|
||||
return signer
|
||||
}
|
||||
|
||||
groupKey, err := NNSResolveKey(c.ReadOnlyInvoker, nnsCs.Hash, client.NNSGroupKeyName)
|
||||
if err == nil {
|
||||
c.GroupKey = groupKey
|
||||
|
||||
signer.Scopes = transaction.CustomGroups
|
||||
signer.AllowedGroups = keys.PublicKeys{groupKey}
|
||||
}
|
||||
return signer
|
||||
}
|
||||
|
||||
// SendCommitteeTx creates transaction from script, signs it by committee nodes and sends it to RPC.
|
||||
// If tryGroup is false, global scope is used for the signer (useful when
|
||||
// working with native contracts).
|
||||
func (c *InitializeContext) SendCommitteeTx(script []byte, tryGroup bool) error {
|
||||
return c.sendMultiTx(script, tryGroup, false)
|
||||
}
|
||||
|
||||
// SendConsensusTx creates transaction from script, signs it by alphabet nodes and sends it to RPC.
|
||||
// Not that because this is used only after the contracts were initialized and deployed,
|
||||
// we always try to have a group scope.
|
||||
func (c *InitializeContext) SendConsensusTx(script []byte) error {
|
||||
return c.sendMultiTx(script, true, true)
|
||||
}
|
||||
|
||||
func (c *InitializeContext) sendMultiTx(script []byte, tryGroup bool, withConsensus bool) error {
|
||||
var act *actor.Actor
|
||||
var err error
|
||||
|
||||
withConsensus = withConsensus && !c.ConsensusAcc.Contract.ScriptHash().Equals(c.CommitteeAcc.ScriptHash())
|
||||
if tryGroup {
|
||||
// Even for consensus signatures we need the committee to pay.
|
||||
signers := make([]actor.SignerAccount, 1, 2)
|
||||
signers[0] = actor.SignerAccount{
|
||||
Signer: c.GetSigner(tryGroup, c.CommitteeAcc),
|
||||
Account: c.CommitteeAcc,
|
||||
}
|
||||
if withConsensus {
|
||||
signers = append(signers, actor.SignerAccount{
|
||||
Signer: c.GetSigner(tryGroup, c.ConsensusAcc),
|
||||
Account: c.ConsensusAcc,
|
||||
})
|
||||
}
|
||||
act, err = actor.New(c.Client, signers)
|
||||
} else {
|
||||
if withConsensus {
|
||||
panic("BUG: should never happen")
|
||||
}
|
||||
act, err = c.CommitteeAct, nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create actor: %w", err)
|
||||
}
|
||||
|
||||
tx, err := act.MakeUnsignedRun(script, []transaction.Attribute{{Type: transaction.HighPriority}})
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not perform test invocation: %w", err)
|
||||
}
|
||||
|
||||
if err := c.MultiSign(tx, constants.CommitteeAccountName); err != nil {
|
||||
return err
|
||||
}
|
||||
if withConsensus {
|
||||
if err := c.MultiSign(tx, constants.ConsensusAccountName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return c.SendTx(tx, c.Command, false)
|
||||
}
|
||||
|
||||
func (c *InitializeContext) MultiSignAndSend(tx *transaction.Transaction, accType string) error {
|
||||
if err := c.MultiSign(tx, accType); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.SendTx(tx, c.Command, false)
|
||||
}
|
||||
|
||||
func (c *InitializeContext) MultiSign(tx *transaction.Transaction, accType string) error {
|
||||
version, err := c.Client.GetVersion()
|
||||
if err != nil {
|
||||
// error appears only if client
|
||||
// has not been initialized
|
||||
panic(err)
|
||||
}
|
||||
network := version.Protocol.Network
|
||||
|
||||
// Use parameter context to avoid dealing with signature order.
|
||||
pc := context.NewParameterContext("", network, tx)
|
||||
h := c.CommitteeAcc.Contract.ScriptHash()
|
||||
if accType == constants.ConsensusAccountName {
|
||||
h = c.ConsensusAcc.Contract.ScriptHash()
|
||||
}
|
||||
for _, w := range c.Wallets {
|
||||
acc, err := GetWalletAccount(w, accType)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't find %s wallet account: %w", accType, err)
|
||||
}
|
||||
|
||||
priv := acc.PrivateKey()
|
||||
sign := priv.SignHashable(uint32(network), tx)
|
||||
if err := pc.AddSignature(h, acc.Contract, priv.PublicKey(), sign); err != nil {
|
||||
return fmt.Errorf("can't add signature: %w", err)
|
||||
}
|
||||
if len(pc.Items[h].Signatures) == len(acc.Contract.Parameters) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
w, err := pc.GetWitness(h)
|
||||
if err != nil {
|
||||
return fmt.Errorf("incomplete signature: %w", err)
|
||||
}
|
||||
|
||||
for i := range tx.Signers {
|
||||
if tx.Signers[i].Account == h {
|
||||
if i < len(tx.Scripts) {
|
||||
tx.Scripts[i] = *w
|
||||
} else if i == len(tx.Scripts) {
|
||||
tx.Scripts = append(tx.Scripts, *w)
|
||||
} else {
|
||||
panic("BUG: invalid signing order")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("%s account was not found among transaction signers", accType)
|
||||
}
|
||||
|
||||
// EmitUpdateNNSGroupScript emits script for updating group key stored in NNS.
|
||||
// First return value is true iff the key is already there and nothing should be done.
|
||||
// Second return value is true iff a domain registration code was emitted.
|
||||
func (c *InitializeContext) EmitUpdateNNSGroupScript(bw *io.BufBinWriter, nnsHash util.Uint160, pub *keys.PublicKey) (bool, bool, error) {
|
||||
isAvail, err := NNSIsAvailable(c.Client, nnsHash, client.NNSGroupKeyName)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
if !isAvail {
|
||||
currentPub, err := NNSResolveKey(c.ReadOnlyInvoker, nnsHash, client.NNSGroupKeyName)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
if pub.Equal(currentPub) {
|
||||
return true, false, nil
|
||||
}
|
||||
}
|
||||
|
||||
if isAvail {
|
||||
emit.AppCall(bw.BinWriter, nnsHash, "register", callflag.All,
|
||||
client.NNSGroupKeyName, c.CommitteeAcc.Contract.ScriptHash(),
|
||||
constants.FrostfsOpsEmail, int64(3600), int64(600), int64(constants.DefaultExpirationTime), int64(3600))
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
}
|
||||
|
||||
emit.AppCall(bw.BinWriter, nnsHash, "deleteRecords", callflag.All, "group.frostfs", int64(nns.TXT))
|
||||
emit.AppCall(bw.BinWriter, nnsHash, "addRecord", callflag.All,
|
||||
"group.frostfs", int64(nns.TXT), hex.EncodeToString(pub.Bytes()))
|
||||
|
||||
return false, isAvail, nil
|
||||
}
|
||||
|
||||
func (c *InitializeContext) NNSRegisterDomainScript(nnsHash, expectedHash util.Uint160, domain string) ([]byte, bool, error) {
|
||||
ok, err := NNSIsAvailable(c.Client, nnsHash, domain)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if ok {
|
||||
bw := io.NewBufBinWriter()
|
||||
emit.AppCall(bw.BinWriter, nnsHash, "register", callflag.All,
|
||||
domain, c.CommitteeAcc.Contract.ScriptHash(),
|
||||
constants.FrostfsOpsEmail, int64(3600), int64(600), int64(constants.DefaultExpirationTime), int64(3600))
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
|
||||
if bw.Err != nil {
|
||||
panic(bw.Err)
|
||||
}
|
||||
return bw.Bytes(), false, nil
|
||||
}
|
||||
|
||||
s, err := NNSResolveHash(c.ReadOnlyInvoker, nnsHash, domain)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return nil, s == expectedHash, nil
|
||||
}
|
||||
|
||||
func (c *InitializeContext) NNSRootRegistered(nnsHash util.Uint160, zone string) (bool, error) {
|
||||
res, err := c.CommitteeAct.Call(nnsHash, "isAvailable", "name."+zone)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return res.State == vmstate.Halt.String(), nil
|
||||
}
|
||||
|
||||
func (c *InitializeContext) IsUpdated(ctrHash util.Uint160, cs *ContractState) bool {
|
||||
r := management.NewReader(c.ReadOnlyInvoker)
|
||||
realCs, err := r.GetContract(ctrHash)
|
||||
return err == nil && realCs != nil && realCs.NEF.Checksum == cs.NEF.Checksum
|
||||
}
|
||||
|
||||
func (c *InitializeContext) GetContract(ctrName string) *ContractState {
|
||||
return c.Contracts[ctrName]
|
||||
}
|
||||
|
||||
func (c *InitializeContext) GetAlphabetDeployItems(i, n int) []any {
|
||||
items := make([]any, 5)
|
||||
items[0] = c.Contracts[constants.NetmapContract].Hash
|
||||
items[1] = c.Contracts[constants.ProxyContract].Hash
|
||||
items[2] = innerring.GlagoliticLetter(i).String()
|
||||
items[3] = int64(i)
|
||||
items[4] = int64(n)
|
||||
return items
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNextPollInterval(t *testing.T) {
|
||||
var pollInterval time.Duration
|
||||
var iteration int
|
||||
|
||||
pollInterval, hasChanged := NextPollInterval(iteration, pollInterval)
|
||||
require.True(t, hasChanged)
|
||||
require.Equal(t, time.Second, pollInterval)
|
||||
|
||||
iteration = 4
|
||||
pollInterval, hasChanged = NextPollInterval(iteration, pollInterval)
|
||||
require.False(t, hasChanged)
|
||||
require.Equal(t, time.Second, pollInterval)
|
||||
|
||||
iteration = 5
|
||||
pollInterval, hasChanged = NextPollInterval(iteration, pollInterval)
|
||||
require.True(t, hasChanged)
|
||||
require.Equal(t, 2*time.Second, pollInterval)
|
||||
|
||||
iteration = 10
|
||||
pollInterval, hasChanged = NextPollInterval(iteration, pollInterval)
|
||||
require.True(t, hasChanged)
|
||||
require.Equal(t, 4*time.Second, pollInterval)
|
||||
|
||||
iteration = 20
|
||||
pollInterval = 32 * time.Second
|
||||
pollInterval, hasChanged = NextPollInterval(iteration, pollInterval)
|
||||
require.True(t, hasChanged) // from 32s to 16s
|
||||
require.Equal(t, 16*time.Second, pollInterval)
|
||||
|
||||
pollInterval = 16 * time.Second
|
||||
pollInterval, hasChanged = NextPollInterval(iteration, pollInterval)
|
||||
require.False(t, hasChanged)
|
||||
require.Equal(t, 16*time.Second, pollInterval)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package morph
|
||||
package helper
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
|
@ -8,6 +8,7 @@ import (
|
|||
"sort"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||
|
@ -38,7 +39,7 @@ import (
|
|||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type localClient struct {
|
||||
type LocalClient struct {
|
||||
bc *core.Blockchain
|
||||
transactions []*transaction.Transaction
|
||||
dumpPath string
|
||||
|
@ -46,8 +47,8 @@ type localClient struct {
|
|||
maxGasInvoke int64
|
||||
}
|
||||
|
||||
func newLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet, dumpPath string) (*localClient, error) {
|
||||
cfg, err := config.LoadFile(v.GetString(protoConfigPath))
|
||||
func NewLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet, dumpPath string) (*LocalClient, error) {
|
||||
cfg, err := config.LoadFile(v.GetString(constants.ProtoConfigPath))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -60,7 +61,7 @@ func newLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet
|
|||
m := smartcontract.GetDefaultHonestNodeCount(int(cfg.ProtocolConfiguration.ValidatorsCount))
|
||||
accounts := make([]*wallet.Account, len(wallets))
|
||||
for i := range accounts {
|
||||
accounts[i], err = getWalletAccount(wallets[i], consensusAccountName)
|
||||
accounts[i], err = GetWalletAccount(wallets[i], constants.ConsensusAccountName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -102,7 +103,7 @@ func newLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet
|
|||
}
|
||||
}
|
||||
|
||||
return &localClient{
|
||||
return &LocalClient{
|
||||
bc: bc,
|
||||
dumpPath: dumpPath,
|
||||
accounts: accounts[:m],
|
||||
|
@ -110,15 +111,15 @@ func newLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (l *localClient) GetBlockCount() (uint32, error) {
|
||||
func (l *LocalClient) GetBlockCount() (uint32, error) {
|
||||
return l.bc.BlockHeight(), nil
|
||||
}
|
||||
|
||||
func (l *localClient) GetNativeContracts() ([]state.NativeContract, error) {
|
||||
func (l *LocalClient) GetNativeContracts() ([]state.NativeContract, error) {
|
||||
return l.bc.GetNatives(), nil
|
||||
}
|
||||
|
||||
func (l *localClient) GetApplicationLog(h util.Uint256, t *trigger.Type) (*result.ApplicationLog, error) {
|
||||
func (l *LocalClient) GetApplicationLog(h util.Uint256, t *trigger.Type) (*result.ApplicationLog, error) {
|
||||
aer, err := l.bc.GetAppExecResults(h, *t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -128,13 +129,13 @@ func (l *localClient) GetApplicationLog(h util.Uint256, t *trigger.Type) (*resul
|
|||
return &a, nil
|
||||
}
|
||||
|
||||
func (l *localClient) GetCommittee() (keys.PublicKeys, error) {
|
||||
func (l *LocalClient) GetCommittee() (keys.PublicKeys, error) {
|
||||
// not used by `morph init` command
|
||||
panic("unexpected call")
|
||||
}
|
||||
|
||||
// InvokeFunction is implemented via `InvokeScript`.
|
||||
func (l *localClient) InvokeFunction(h util.Uint160, method string, sPrm []smartcontract.Parameter, ss []transaction.Signer) (*result.Invoke, error) {
|
||||
func (l *LocalClient) InvokeFunction(h util.Uint160, method string, sPrm []smartcontract.Parameter, ss []transaction.Signer) (*result.Invoke, error) {
|
||||
var err error
|
||||
|
||||
pp := make([]any, len(sPrm))
|
||||
|
@ -145,21 +146,21 @@ func (l *localClient) InvokeFunction(h util.Uint160, method string, sPrm []smart
|
|||
}
|
||||
}
|
||||
|
||||
return invokeFunction(l, h, method, pp, ss)
|
||||
return InvokeFunction(l, h, method, pp, ss)
|
||||
}
|
||||
|
||||
func (l *localClient) TerminateSession(_ uuid.UUID) (bool, error) {
|
||||
func (l *LocalClient) TerminateSession(_ uuid.UUID) (bool, error) {
|
||||
// not used by `morph init` command
|
||||
panic("unexpected call")
|
||||
}
|
||||
|
||||
func (l *localClient) TraverseIterator(_, _ uuid.UUID, _ int) ([]stackitem.Item, error) {
|
||||
func (l *LocalClient) TraverseIterator(_, _ uuid.UUID, _ int) ([]stackitem.Item, error) {
|
||||
// not used by `morph init` command
|
||||
panic("unexpected call")
|
||||
}
|
||||
|
||||
// GetVersion return default version.
|
||||
func (l *localClient) GetVersion() (*result.Version, error) {
|
||||
func (l *LocalClient) GetVersion() (*result.Version, error) {
|
||||
c := l.bc.GetConfig()
|
||||
return &result.Version{
|
||||
Protocol: result.Protocol{
|
||||
|
@ -180,7 +181,7 @@ func (l *localClient) GetVersion() (*result.Version, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (l *localClient) InvokeContractVerify(util.Uint160, []smartcontract.Parameter, []transaction.Signer, ...transaction.Witness) (*result.Invoke, error) {
|
||||
func (l *LocalClient) InvokeContractVerify(util.Uint160, []smartcontract.Parameter, []transaction.Signer, ...transaction.Witness) (*result.Invoke, error) {
|
||||
// not used by `morph init` command
|
||||
panic("unexpected call")
|
||||
}
|
||||
|
@ -188,7 +189,7 @@ func (l *localClient) InvokeContractVerify(util.Uint160, []smartcontract.Paramet
|
|||
// CalculateNetworkFee calculates network fee for the given transaction.
|
||||
// Copied from neo-go with minor corrections (no need to support non-notary mode):
|
||||
// https://github.com/nspcc-dev/neo-go/blob/v0.103.0/pkg/services/rpcsrv/server.go#L911
|
||||
func (l *localClient) CalculateNetworkFee(tx *transaction.Transaction) (int64, error) {
|
||||
func (l *LocalClient) CalculateNetworkFee(tx *transaction.Transaction) (int64, error) {
|
||||
// Avoid setting hash for this tx: server code doesn't touch client transaction.
|
||||
data := tx.Bytes()
|
||||
tx, err := transaction.NewTransactionFromBytes(data)
|
||||
|
@ -259,7 +260,7 @@ func (l *localClient) CalculateNetworkFee(tx *transaction.Transaction) (int64, e
|
|||
return netFee, nil
|
||||
}
|
||||
|
||||
func (l *localClient) InvokeScript(script []byte, signers []transaction.Signer) (*result.Invoke, error) {
|
||||
func (l *LocalClient) InvokeScript(script []byte, signers []transaction.Signer) (*result.Invoke, error) {
|
||||
lastBlock, err := l.bc.GetBlock(l.bc.CurrentBlockHash())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -295,7 +296,7 @@ func (l *localClient) InvokeScript(script []byte, signers []transaction.Signer)
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (l *localClient) SendRawTransaction(tx *transaction.Transaction) (util.Uint256, error) {
|
||||
func (l *LocalClient) SendRawTransaction(tx *transaction.Transaction) (util.Uint256, error) {
|
||||
// We need to test that transaction was formed correctly to catch as many errors as we can.
|
||||
bs := tx.Bytes()
|
||||
_, err := transaction.NewTransactionFromBytes(bs)
|
||||
|
@ -307,7 +308,7 @@ func (l *localClient) SendRawTransaction(tx *transaction.Transaction) (util.Uint
|
|||
return tx.Hash(), nil
|
||||
}
|
||||
|
||||
func (l *localClient) putTransactions() error {
|
||||
func (l *LocalClient) putTransactions() error {
|
||||
// 1. Prepare new block.
|
||||
lastBlock, err := l.bc.GetBlock(l.bc.CurrentBlockHash())
|
||||
if err != nil {
|
||||
|
@ -349,7 +350,7 @@ func (l *localClient) putTransactions() error {
|
|||
return l.bc.AddBlock(b)
|
||||
}
|
||||
|
||||
func invokeFunction(c Client, h util.Uint160, method string, parameters []any, signers []transaction.Signer) (*result.Invoke, error) {
|
||||
func InvokeFunction(c Client, h util.Uint160, method string, parameters []any, signers []transaction.Signer) (*result.Invoke, error) {
|
||||
w := io.NewBufBinWriter()
|
||||
emit.Array(w.BinWriter, parameters...)
|
||||
emit.AppCallNoArgs(w.BinWriter, h, method, callflag.All)
|
||||
|
@ -361,7 +362,7 @@ func invokeFunction(c Client, h util.Uint160, method string, parameters []any, s
|
|||
|
||||
var errGetDesignatedByRoleResponse = errors.New("`getDesignatedByRole`: invalid response")
|
||||
|
||||
func getDesignatedByRole(inv *invoker.Invoker, h util.Uint160, role noderoles.Role, u uint32) (keys.PublicKeys, error) {
|
||||
func GetDesignatedByRole(inv *invoker.Invoker, h util.Uint160, role noderoles.Role, u uint32) (keys.PublicKeys, error) {
|
||||
arr, err := unwrap.Array(inv.Call(h, "getDesignatedByRole", int64(role), int64(u)))
|
||||
if err != nil {
|
||||
return nil, errGetDesignatedByRoleResponse
|
||||
|
@ -382,7 +383,7 @@ func getDesignatedByRole(inv *invoker.Invoker, h util.Uint160, role noderoles.Ro
|
|||
return pubs, nil
|
||||
}
|
||||
|
||||
func (l *localClient) dump() (err error) {
|
||||
func (l *LocalClient) Dump() (err error) {
|
||||
defer l.bc.Close()
|
||||
|
||||
f, err := os.Create(l.dumpPath)
|
|
@ -1,4 +1,4 @@
|
|||
package morph
|
||||
package helper
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"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/keys"
|
||||
|
@ -34,19 +35,19 @@ type Client interface {
|
|||
CalculateNetworkFee(tx *transaction.Transaction) (int64, error)
|
||||
}
|
||||
|
||||
type hashVUBPair struct {
|
||||
hash util.Uint256
|
||||
vub uint32
|
||||
type HashVUBPair struct {
|
||||
Hash util.Uint256
|
||||
Vub uint32
|
||||
}
|
||||
|
||||
type clientContext struct {
|
||||
type ClientContext struct {
|
||||
Client Client // a raw neo-go client OR a local chain implementation
|
||||
CommitteeAct *actor.Actor // committee actor with the Global witness scope
|
||||
ReadOnlyInvoker *invoker.Invoker // R/O contract invoker, does not contain any signer
|
||||
SentTxs []hashVUBPair
|
||||
SentTxs []HashVUBPair
|
||||
}
|
||||
|
||||
func getN3Client(v *viper.Viper) (Client, error) {
|
||||
func GetN3Client(v *viper.Viper) (Client, error) {
|
||||
// number of opened connections
|
||||
// by neo-go client per one host
|
||||
const (
|
||||
|
@ -55,7 +56,7 @@ func getN3Client(v *viper.Viper) (Client, error) {
|
|||
)
|
||||
|
||||
ctx := context.Background()
|
||||
endpoint := v.GetString(endpointFlag)
|
||||
endpoint := v.GetString(commonflags.EndpointFlag)
|
||||
if endpoint == "" {
|
||||
return nil, errors.New("missing endpoint")
|
||||
}
|
||||
|
@ -72,20 +73,20 @@ func getN3Client(v *viper.Viper) (Client, error) {
|
|||
return c, nil
|
||||
}
|
||||
|
||||
func defaultClientContext(c Client, committeeAcc *wallet.Account) (*clientContext, error) {
|
||||
commAct, err := newActor(c, committeeAcc)
|
||||
func DefaultClientContext(c Client, committeeAcc *wallet.Account) (*ClientContext, error) {
|
||||
commAct, err := NewActor(c, committeeAcc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &clientContext{
|
||||
return &ClientContext{
|
||||
Client: c,
|
||||
CommitteeAct: commAct,
|
||||
ReadOnlyInvoker: invoker.New(c, nil),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *clientContext) sendTx(tx *transaction.Transaction, cmd *cobra.Command, await bool) error {
|
||||
func (c *ClientContext) SendTx(tx *transaction.Transaction, cmd *cobra.Command, await bool) error {
|
||||
h, err := c.Client.SendRawTransaction(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -95,10 +96,27 @@ func (c *clientContext) sendTx(tx *transaction.Transaction, cmd *cobra.Command,
|
|||
return fmt.Errorf("sent and actual tx hashes mismatch:\n\tsent: %v\n\tactual: %v", tx.Hash().StringLE(), h.StringLE())
|
||||
}
|
||||
|
||||
c.SentTxs = append(c.SentTxs, hashVUBPair{hash: h, vub: tx.ValidUntilBlock})
|
||||
c.SentTxs = append(c.SentTxs, HashVUBPair{Hash: h, Vub: tx.ValidUntilBlock})
|
||||
|
||||
if await {
|
||||
return c.awaitTx(cmd)
|
||||
return c.AwaitTx(cmd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClientContext) AwaitTx(cmd *cobra.Command) error {
|
||||
if len(c.SentTxs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if local, ok := c.Client.(*LocalClient); ok {
|
||||
if err := local.putTransactions(); err != nil {
|
||||
return fmt.Errorf("can't persist transactions: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err := AwaitTx(cmd, c.Client, c.SentTxs)
|
||||
c.SentTxs = c.SentTxs[:0]
|
||||
|
||||
return err
|
||||
}
|
121
cmd/frostfs-adm/internal/modules/morph/helper/netmap.go
Normal file
121
cmd/frostfs-adm/internal/modules/morph/helper/netmap.go
Normal file
|
@ -0,0 +1,121 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"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/stackitem"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var NetmapConfigKeys = []string{
|
||||
netmap.EpochDurationConfig,
|
||||
netmap.MaxObjectSizeConfig,
|
||||
netmap.ContainerFeeConfig,
|
||||
netmap.ContainerAliasFeeConfig,
|
||||
netmap.IrCandidateFeeConfig,
|
||||
netmap.WithdrawFeeConfig,
|
||||
netmap.HomomorphicHashingDisabledKey,
|
||||
netmap.MaintenanceModeAllowedConfig,
|
||||
}
|
||||
|
||||
func GetDefaultNetmapContractConfigMap() map[string]any {
|
||||
m := make(map[string]any)
|
||||
m[netmap.EpochDurationConfig] = viper.GetInt64(commonflags.EpochDurationInitFlag)
|
||||
m[netmap.MaxObjectSizeConfig] = viper.GetInt64(commonflags.MaxObjectSizeInitFlag)
|
||||
m[netmap.ContainerFeeConfig] = viper.GetInt64(commonflags.ContainerFeeInitFlag)
|
||||
m[netmap.ContainerAliasFeeConfig] = viper.GetInt64(commonflags.ContainerAliasFeeInitFlag)
|
||||
m[netmap.IrCandidateFeeConfig] = viper.GetInt64(commonflags.CandidateFeeInitFlag)
|
||||
m[netmap.WithdrawFeeConfig] = viper.GetInt64(commonflags.WithdrawFeeInitFlag)
|
||||
m[netmap.HomomorphicHashingDisabledKey] = viper.GetBool(commonflags.HomomorphicHashDisabledInitFlag)
|
||||
m[netmap.MaintenanceModeAllowedConfig] = viper.GetBool(commonflags.MaintenanceModeAllowedInitFlag)
|
||||
return m
|
||||
}
|
||||
|
||||
func ParseConfigFromNetmapContract(arr []stackitem.Item) (map[string][]byte, error) {
|
||||
m := make(map[string][]byte, len(arr))
|
||||
for _, param := range arr {
|
||||
tuple, ok := param.Value().([]stackitem.Item)
|
||||
if !ok || len(tuple) != 2 {
|
||||
return nil, errors.New("invalid ListConfig response from netmap contract")
|
||||
}
|
||||
|
||||
k, err := tuple[0].TryBytes()
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid config key from netmap contract")
|
||||
}
|
||||
|
||||
v, err := tuple[1].TryBytes()
|
||||
if err != nil {
|
||||
return nil, InvalidConfigValueErr(string(k))
|
||||
}
|
||||
m[string(k)] = v
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func InvalidConfigValueErr(key string) error {
|
||||
return fmt.Errorf("invalid %s config value from netmap contract", key)
|
||||
}
|
||||
|
||||
func EmitNewEpochCall(bw *io.BufBinWriter, wCtx *InitializeContext, nmHash util.Uint160) error {
|
||||
curr, err := unwrap.Int64(wCtx.ReadOnlyInvoker.Call(nmHash, "epoch"))
|
||||
if err != nil {
|
||||
return errors.New("can't fetch current epoch from the netmap contract")
|
||||
}
|
||||
|
||||
newEpoch := curr + 1
|
||||
wCtx.Command.Printf("Current epoch: %d, increase to %d.\n", curr, newEpoch)
|
||||
|
||||
// In NeoFS this is done via Notary contract. Here, however, we can form the
|
||||
// transaction locally.
|
||||
emit.AppCall(bw.BinWriter, nmHash, "newEpoch", callflag.All, newEpoch)
|
||||
return bw.Err
|
||||
}
|
||||
|
||||
func GetNetConfigFromNetmapContract(roInvoker *invoker.Invoker) ([]stackitem.Item, error) {
|
||||
r := management.NewReader(roInvoker)
|
||||
cs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get nns contract: %w", err)
|
||||
}
|
||||
nmHash, err := NNSResolveHash(roInvoker, cs.Hash, DomainOf(constants.NetmapContract))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||
}
|
||||
arr, err := unwrap.Array(roInvoker.Call(nmHash, "listConfig"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't fetch list of network config keys from the netmap contract")
|
||||
}
|
||||
return arr, err
|
||||
}
|
||||
|
||||
func MergeNetmapConfig(roInvoker *invoker.Invoker, md map[string]any) error {
|
||||
arr, err := GetNetConfigFromNetmapContract(roInvoker)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m, err := ParseConfigFromNetmapContract(arr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range m {
|
||||
for _, key := range NetmapConfigKeys {
|
||||
if k == key {
|
||||
md[k] = v
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
177
cmd/frostfs-adm/internal/modules/morph/helper/util.go
Normal file
177
cmd/frostfs-adm/internal/modules/morph/helper/util.go
Normal file
|
@ -0,0 +1,177 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||
"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/fixedn"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func GetAlphabetWallets(v *viper.Viper, walletDir string) ([]*wallet.Wallet, error) {
|
||||
wallets, err := openAlphabetWallets(v, walletDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(wallets) > constants.MaxAlphabetNodes {
|
||||
return nil, ErrTooManyAlphabetNodes
|
||||
}
|
||||
return wallets, nil
|
||||
}
|
||||
|
||||
func openAlphabetWallets(v *viper.Viper, walletDir string) ([]*wallet.Wallet, error) {
|
||||
walletFiles, err := os.ReadDir(walletDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read alphabet wallets dir: %w", err)
|
||||
}
|
||||
|
||||
var size int
|
||||
loop:
|
||||
for i := 0; i < len(walletFiles); i++ {
|
||||
name := innerring.GlagoliticLetter(i).String() + ".json"
|
||||
for j := range walletFiles {
|
||||
if walletFiles[j].Name() == name {
|
||||
size++
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
if size == 0 {
|
||||
return nil, errors.New("alphabet wallets dir is empty (run `generate-alphabet` command first)")
|
||||
}
|
||||
|
||||
wallets := make([]*wallet.Wallet, size)
|
||||
for i := 0; i < size; i++ {
|
||||
letter := innerring.GlagoliticLetter(i).String()
|
||||
p := filepath.Join(walletDir, letter+".json")
|
||||
w, err := wallet.NewWalletFromFile(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't open wallet: %w", err)
|
||||
}
|
||||
|
||||
password, err := config.GetPassword(v, letter)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't fetch password: %w", err)
|
||||
}
|
||||
|
||||
for i := range w.Accounts {
|
||||
if err := w.Accounts[i].Decrypt(password, keys.NEP2ScryptParams()); err != nil {
|
||||
return nil, fmt.Errorf("can't unlock wallet: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
wallets[i] = w
|
||||
}
|
||||
|
||||
return wallets, nil
|
||||
}
|
||||
|
||||
func NewActor(c actor.RPCActor, committeeAcc *wallet.Account) (*actor.Actor, error) {
|
||||
return actor.New(c, []actor.SignerAccount{{
|
||||
Signer: transaction.Signer{
|
||||
Account: committeeAcc.Contract.ScriptHash(),
|
||||
Scopes: transaction.Global,
|
||||
},
|
||||
Account: committeeAcc,
|
||||
}})
|
||||
}
|
||||
|
||||
func ReadContract(ctrPath, ctrName string) (*ContractState, error) {
|
||||
rawNef, err := os.ReadFile(filepath.Join(ctrPath, ctrName+"_contract.nef"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read NEF file for %s contract: %w", ctrName, err)
|
||||
}
|
||||
rawManif, err := os.ReadFile(filepath.Join(ctrPath, "config.json"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read manifest file for %s contract: %w", ctrName, err)
|
||||
}
|
||||
|
||||
cs := &ContractState{
|
||||
RawNEF: rawNef,
|
||||
RawManifest: rawManif,
|
||||
}
|
||||
|
||||
return cs, cs.Parse()
|
||||
}
|
||||
|
||||
func readContractsFromArchive(file io.Reader, names []string) (map[string]*ContractState, error) {
|
||||
m := make(map[string]*ContractState, len(names))
|
||||
for i := range names {
|
||||
m[names[i]] = new(ContractState)
|
||||
}
|
||||
|
||||
gr, err := gzip.NewReader(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("contracts file must be tar.gz archive: %w", err)
|
||||
}
|
||||
|
||||
r := tar.NewReader(gr)
|
||||
for h, err := r.Next(); ; h, err = r.Next() {
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
dir, _ := filepath.Split(h.Name)
|
||||
ctrName := filepath.Base(dir)
|
||||
|
||||
cs, ok := m[ctrName]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.HasSuffix(h.Name, filepath.Join(ctrName, ctrName+"_contract.nef")):
|
||||
cs.RawNEF, err = io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read NEF file for %s contract: %w", ctrName, err)
|
||||
}
|
||||
case strings.HasSuffix(h.Name, "config.json"):
|
||||
cs.RawManifest, err = io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read manifest file for %s contract: %w", ctrName, err)
|
||||
}
|
||||
}
|
||||
m[ctrName] = cs
|
||||
}
|
||||
|
||||
for ctrName, cs := range m {
|
||||
if cs.RawNEF == nil {
|
||||
return nil, fmt.Errorf("NEF for %s contract wasn't found", ctrName)
|
||||
}
|
||||
if cs.RawManifest == nil {
|
||||
return nil, fmt.Errorf("manifest for %s contract wasn't found", ctrName)
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func GetAlphabetNNSDomain(i int) string {
|
||||
return constants.AlphabetContract + strconv.FormatUint(uint64(i), 10) + ".frostfs"
|
||||
}
|
||||
|
||||
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
|
||||
}
|
76
cmd/frostfs-adm/internal/modules/morph/helper/wallet.go
Normal file
76
cmd/frostfs-adm/internal/modules/morph/helper/wallet.go
Normal file
|
@ -0,0 +1,76 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func InitializeContractWallet(v *viper.Viper, walletDir string) (*wallet.Wallet, error) {
|
||||
password, err := config.GetPassword(v, constants.ContractWalletPasswordKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w, err := wallet.NewWallet(filepath.Join(walletDir, constants.ContractWalletFilename))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
acc, err := wallet.NewAccount()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = acc.Encrypt(password, keys.NEP2ScryptParams())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w.AddAccount(acc)
|
||||
if err := w.SavePretty(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func openContractWallet(v *viper.Viper, cmd *cobra.Command, walletDir string) (*wallet.Wallet, error) {
|
||||
p := filepath.Join(walletDir, constants.ContractWalletFilename)
|
||||
w, err := wallet.NewWalletFromFile(p)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("can't open wallet: %w", err)
|
||||
}
|
||||
|
||||
cmd.Printf("Contract group wallet is missing, initialize at %s\n", p)
|
||||
return InitializeContractWallet(v, walletDir)
|
||||
}
|
||||
|
||||
password, err := config.GetPassword(v, constants.ContractWalletPasswordKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range w.Accounts {
|
||||
if err := w.Accounts[i].Decrypt(password, keys.NEP2ScryptParams()); err != nil {
|
||||
return nil, fmt.Errorf("can't unlock wallet: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return w, 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)
|
||||
}
|
|
@ -1,483 +0,0 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||
"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/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
// maxAlphabetNodes is the maximum number of candidates allowed, which is currently limited by the size
|
||||
// of the invocation script.
|
||||
// See: https://github.com/nspcc-dev/neo-go/blob/740488f7f35e367eaa99a71c0a609c315fe2b0fc/pkg/core/transaction/witness.go#L10
|
||||
maxAlphabetNodes = 22
|
||||
)
|
||||
|
||||
type cache struct {
|
||||
nnsCs *state.Contract
|
||||
groupKey *keys.PublicKey
|
||||
}
|
||||
|
||||
type initializeContext struct {
|
||||
clientContext
|
||||
cache
|
||||
// CommitteeAcc is used for retrieving the committee address and the verification script.
|
||||
CommitteeAcc *wallet.Account
|
||||
// ConsensusAcc is used for retrieving the committee address and the verification script.
|
||||
ConsensusAcc *wallet.Account
|
||||
Wallets []*wallet.Wallet
|
||||
// ContractWallet is a wallet for providing the contract group signature.
|
||||
ContractWallet *wallet.Wallet
|
||||
// Accounts contains simple signature accounts in the same order as in Wallets.
|
||||
Accounts []*wallet.Account
|
||||
Contracts map[string]*contractState
|
||||
Command *cobra.Command
|
||||
ContractPath string
|
||||
ContractURL string
|
||||
}
|
||||
|
||||
var ErrTooManyAlphabetNodes = fmt.Errorf("too many alphabet nodes (maximum allowed is %d)", maxAlphabetNodes)
|
||||
|
||||
func initializeSideChainCmd(cmd *cobra.Command, _ []string) error {
|
||||
initCtx, err := newInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("initialization error: %w", err)
|
||||
}
|
||||
defer initCtx.close()
|
||||
|
||||
// 1. Transfer funds to committee accounts.
|
||||
cmd.Println("Stage 1: transfer GAS to alphabet nodes.")
|
||||
if err := initCtx.transferFunds(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Println("Stage 2: set notary and alphabet nodes in designate contract.")
|
||||
if err := initCtx.setNotaryAndAlphabetNodes(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. Deploy NNS contract.
|
||||
cmd.Println("Stage 3: deploy NNS contract.")
|
||||
if err := initCtx.deployNNS(deployMethodName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 4. Deploy NeoFS contracts.
|
||||
cmd.Println("Stage 4: deploy NeoFS contracts.")
|
||||
if err := initCtx.deployContracts(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Println("Stage 4.1: Transfer GAS to proxy contract.")
|
||||
if err := initCtx.transferGASToProxy(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Println("Stage 5: register candidates.")
|
||||
if err := initCtx.registerCandidates(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Println("Stage 6: transfer NEO to alphabet contracts.")
|
||||
if err := initCtx.transferNEOToAlphabetContracts(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Println("Stage 7: set addresses in NNS.")
|
||||
return initCtx.setNNS()
|
||||
}
|
||||
|
||||
func (c *initializeContext) close() {
|
||||
if local, ok := c.Client.(*localClient); ok {
|
||||
err := local.dump()
|
||||
if err != nil {
|
||||
c.Command.PrintErrf("Can't write dump: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContext, error) {
|
||||
walletDir := config.ResolveHomePath(viper.GetString(alphabetWalletsFlag))
|
||||
wallets, err := getAlphabetWallets(v, walletDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
needContracts := cmd.Name() == "update-contracts" || cmd.Name() == "init"
|
||||
|
||||
var w *wallet.Wallet
|
||||
w, err = getWallet(cmd, v, needContracts, walletDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := createClient(cmd, v, wallets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
committeeAcc, err := getWalletAccount(wallets[0], committeeAccountName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't find committee account: %w", err)
|
||||
}
|
||||
|
||||
consensusAcc, err := getWalletAccount(wallets[0], consensusAccountName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't find consensus account: %w", err)
|
||||
}
|
||||
|
||||
if err := validateInit(cmd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctrPath, err := getContractsPath(cmd, needContracts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ctrURL string
|
||||
if needContracts {
|
||||
ctrURL, _ = cmd.Flags().GetString(contractsURLFlag)
|
||||
}
|
||||
|
||||
if err := checkNotaryEnabled(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accounts, err := createWalletAccounts(wallets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cliCtx, err := defaultClientContext(c, committeeAcc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("client context: %w", err)
|
||||
}
|
||||
|
||||
initCtx := &initializeContext{
|
||||
clientContext: *cliCtx,
|
||||
ConsensusAcc: consensusAcc,
|
||||
CommitteeAcc: committeeAcc,
|
||||
ContractWallet: w,
|
||||
Wallets: wallets,
|
||||
Accounts: accounts,
|
||||
Command: cmd,
|
||||
Contracts: make(map[string]*contractState),
|
||||
ContractPath: ctrPath,
|
||||
ContractURL: ctrURL,
|
||||
}
|
||||
|
||||
if needContracts {
|
||||
err := initCtx.readContracts(fullContractList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
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 ldf := cmd.Flags().Lookup(localDumpFlag); ldf != nil && ldf.Changed {
|
||||
if cmd.Flags().Changed(endpointFlag) {
|
||||
return nil, fmt.Errorf("`%s` and `%s` flags are mutually exclusive", endpointFlag, localDumpFlag)
|
||||
}
|
||||
c, err = newLocalClient(cmd, v, wallets, ldf.Value.String())
|
||||
} 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, 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 (c *initializeContext) awaitTx() error {
|
||||
return c.clientContext.awaitTx(c.Command)
|
||||
}
|
||||
|
||||
func (c *initializeContext) nnsContractState() (*state.Contract, error) {
|
||||
if c.nnsCs != nil {
|
||||
return c.nnsCs, nil
|
||||
}
|
||||
|
||||
r := management.NewReader(c.ReadOnlyInvoker)
|
||||
cs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.nnsCs = cs
|
||||
return cs, nil
|
||||
}
|
||||
|
||||
func (c *initializeContext) getSigner(tryGroup bool, acc *wallet.Account) transaction.Signer {
|
||||
if tryGroup && c.groupKey != nil {
|
||||
return transaction.Signer{
|
||||
Account: acc.Contract.ScriptHash(),
|
||||
Scopes: transaction.CustomGroups,
|
||||
AllowedGroups: keys.PublicKeys{c.groupKey},
|
||||
}
|
||||
}
|
||||
|
||||
signer := transaction.Signer{
|
||||
Account: acc.Contract.ScriptHash(),
|
||||
Scopes: transaction.Global, // Scope is important, as we have nested call to container contract.
|
||||
}
|
||||
|
||||
if !tryGroup {
|
||||
return signer
|
||||
}
|
||||
|
||||
nnsCs, err := c.nnsContractState()
|
||||
if err != nil {
|
||||
return signer
|
||||
}
|
||||
|
||||
groupKey, err := nnsResolveKey(c.ReadOnlyInvoker, nnsCs.Hash, morphClient.NNSGroupKeyName)
|
||||
if err == nil {
|
||||
c.groupKey = groupKey
|
||||
|
||||
signer.Scopes = transaction.CustomGroups
|
||||
signer.AllowedGroups = keys.PublicKeys{groupKey}
|
||||
}
|
||||
return signer
|
||||
}
|
||||
|
||||
func (c *clientContext) awaitTx(cmd *cobra.Command) error {
|
||||
if len(c.SentTxs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if local, ok := c.Client.(*localClient); ok {
|
||||
if err := local.putTransactions(); err != nil {
|
||||
return fmt.Errorf("can't persist transactions: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err := awaitTx(cmd, c.Client, c.SentTxs)
|
||||
c.SentTxs = c.SentTxs[:0]
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func awaitTx(cmd *cobra.Command, c Client, txs []hashVUBPair) error {
|
||||
cmd.Println("Waiting for transactions to persist...")
|
||||
|
||||
at := trigger.Application
|
||||
|
||||
var retErr error
|
||||
|
||||
loop:
|
||||
for i := range txs {
|
||||
var it int
|
||||
var pollInterval time.Duration
|
||||
var pollIntervalChanged bool
|
||||
for {
|
||||
// We must fetch current height before application log, to avoid race condition.
|
||||
currBlock, err := c.GetBlockCount()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch current block height: %w", err)
|
||||
}
|
||||
res, err := c.GetApplicationLog(txs[i].hash, &at)
|
||||
if err == nil {
|
||||
if retErr == nil && len(res.Executions) > 0 && res.Executions[0].VMState != vmstate.Halt {
|
||||
retErr = fmt.Errorf("tx %d persisted in %s state: %s",
|
||||
i, res.Executions[0].VMState, res.Executions[0].FaultException)
|
||||
}
|
||||
continue loop
|
||||
}
|
||||
if txs[i].vub < currBlock {
|
||||
return fmt.Errorf("tx was not persisted: vub=%d, height=%d", txs[i].vub, currBlock)
|
||||
}
|
||||
|
||||
pollInterval, pollIntervalChanged = nextPollInterval(it, pollInterval)
|
||||
if pollIntervalChanged && viper.GetBool(commonflags.Verbose) {
|
||||
cmd.Printf("Pool interval to check transaction persistence changed: %s\n", pollInterval.String())
|
||||
}
|
||||
|
||||
timer := time.NewTimer(pollInterval)
|
||||
select {
|
||||
case <-cmd.Context().Done():
|
||||
return cmd.Context().Err()
|
||||
case <-timer.C:
|
||||
}
|
||||
|
||||
it++
|
||||
}
|
||||
}
|
||||
|
||||
return retErr
|
||||
}
|
||||
|
||||
func nextPollInterval(it int, previous time.Duration) (time.Duration, bool) {
|
||||
const minPollInterval = 1 * time.Second
|
||||
const maxPollInterval = 16 * time.Second
|
||||
const changeAfter = 5
|
||||
if it == 0 {
|
||||
return minPollInterval, true
|
||||
}
|
||||
if it%changeAfter != 0 {
|
||||
return previous, false
|
||||
}
|
||||
nextInterval := previous * 2
|
||||
if nextInterval > maxPollInterval {
|
||||
return maxPollInterval, previous != maxPollInterval
|
||||
}
|
||||
return nextInterval, true
|
||||
}
|
||||
|
||||
// sendCommitteeTx creates transaction from script, signs it by committee nodes and sends it to RPC.
|
||||
// If tryGroup is false, global scope is used for the signer (useful when
|
||||
// working with native contracts).
|
||||
func (c *initializeContext) sendCommitteeTx(script []byte, tryGroup bool) error {
|
||||
return c.sendMultiTx(script, tryGroup, false)
|
||||
}
|
||||
|
||||
// sendConsensusTx creates transaction from script, signs it by alphabet nodes and sends it to RPC.
|
||||
// Not that because this is used only after the contracts were initialized and deployed,
|
||||
// we always try to have a group scope.
|
||||
func (c *initializeContext) sendConsensusTx(script []byte) error {
|
||||
return c.sendMultiTx(script, true, true)
|
||||
}
|
||||
|
||||
func (c *initializeContext) sendMultiTx(script []byte, tryGroup bool, withConsensus bool) error {
|
||||
var act *actor.Actor
|
||||
var err error
|
||||
|
||||
withConsensus = withConsensus && !c.ConsensusAcc.Contract.ScriptHash().Equals(c.CommitteeAcc.ScriptHash())
|
||||
if tryGroup {
|
||||
// Even for consensus signatures we need the committee to pay.
|
||||
signers := make([]actor.SignerAccount, 1, 2)
|
||||
signers[0] = actor.SignerAccount{
|
||||
Signer: c.getSigner(tryGroup, c.CommitteeAcc),
|
||||
Account: c.CommitteeAcc,
|
||||
}
|
||||
if withConsensus {
|
||||
signers = append(signers, actor.SignerAccount{
|
||||
Signer: c.getSigner(tryGroup, c.ConsensusAcc),
|
||||
Account: c.ConsensusAcc,
|
||||
})
|
||||
}
|
||||
act, err = actor.New(c.Client, signers)
|
||||
} else {
|
||||
if withConsensus {
|
||||
panic("BUG: should never happen")
|
||||
}
|
||||
act, err = c.CommitteeAct, nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create actor: %w", err)
|
||||
}
|
||||
|
||||
tx, err := act.MakeUnsignedRun(script, []transaction.Attribute{{Type: transaction.HighPriority}})
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not perform test invocation: %w", err)
|
||||
}
|
||||
|
||||
if err := c.multiSign(tx, committeeAccountName); err != nil {
|
||||
return err
|
||||
}
|
||||
if withConsensus {
|
||||
if err := c.multiSign(tx, consensusAccountName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return c.sendTx(tx, c.Command, false)
|
||||
}
|
||||
|
||||
func getWalletAccount(w *wallet.Wallet, typ string) (*wallet.Account, error) {
|
||||
for i := range w.Accounts {
|
||||
if w.Accounts[i].Label == typ {
|
||||
return w.Accounts[i], nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("account for '%s' not found", typ)
|
||||
}
|
||||
|
||||
func checkNotaryEnabled(c Client) error {
|
||||
ns, err := c.GetNativeContracts()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get native contract hashes: %w", err)
|
||||
}
|
||||
|
||||
notaryEnabled := false
|
||||
nativeHashes := make(map[string]util.Uint160, len(ns))
|
||||
for i := range ns {
|
||||
if ns[i].Manifest.Name == nativenames.Notary {
|
||||
notaryEnabled = true
|
||||
}
|
||||
nativeHashes[ns[i].Manifest.Name] = ns[i].Hash
|
||||
}
|
||||
if !notaryEnabled {
|
||||
return errors.New("notary contract must be enabled")
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package initialize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func initializeSideChainCmd(cmd *cobra.Command, _ []string) error {
|
||||
initCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("initialization error: %w", err)
|
||||
}
|
||||
defer initCtx.Close()
|
||||
|
||||
// 1. Transfer funds to committee accounts.
|
||||
cmd.Println("Stage 1: transfer GAS to alphabet nodes.")
|
||||
if err := transferFunds(initCtx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Println("Stage 2: set notary and alphabet nodes in designate contract.")
|
||||
if err := setNotaryAndAlphabetNodes(initCtx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. Deploy NNS contract.
|
||||
cmd.Println("Stage 3: deploy NNS contract.")
|
||||
if err := helper.DeployNNS(initCtx, constants.DeployMethodName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 4. Deploy NeoFS contracts.
|
||||
cmd.Println("Stage 4: deploy NeoFS contracts.")
|
||||
if err := deployContracts(initCtx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Println("Stage 4.1: Transfer GAS to proxy contract.")
|
||||
if err := transferGASToProxy(initCtx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Println("Stage 5: register candidates.")
|
||||
if err := registerCandidates(initCtx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Println("Stage 6: transfer NEO to alphabet contracts.")
|
||||
if err := transferNEOToAlphabetContracts(initCtx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Println("Stage 7: set addresses in NNS.")
|
||||
return setNNS(initCtx)
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
package initialize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
)
|
||||
|
||||
func deployContracts(c *helper.InitializeContext) error {
|
||||
alphaCs := c.GetContract(constants.AlphabetContract)
|
||||
|
||||
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 := state.CreateContractHash(acc.Contract.ScriptHash(), alphaCs.NEF.Checksum, alphaCs.Manifest.Name)
|
||||
if c.IsUpdated(ctrHash, alphaCs) {
|
||||
c.Command.Printf("Alphabet contract #%d is already deployed.\n", i)
|
||||
continue
|
||||
}
|
||||
|
||||
alphaCs.Manifest.Groups = baseGroups
|
||||
err := helper.AddManifestGroup(c.ContractWallet, ctrHash, alphaCs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't sign manifest group: %v", err)
|
||||
}
|
||||
|
||||
keysParam = append(keysParam, acc.PrivateKey().PublicKey().Bytes())
|
||||
params := helper.GetContractDeployParameters(alphaCs, c.GetAlphabetDeployItems(i, len(c.Wallets)))
|
||||
|
||||
act, err := actor.NewSimple(c.Client, acc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create actor: %w", err)
|
||||
}
|
||||
|
||||
txHash, vub, err := act.SendCall(management.Hash, constants.DeployMethodName, params...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't deploy alphabet #%d contract: %w", i, err)
|
||||
}
|
||||
|
||||
c.SentTxs = append(c.SentTxs, helper.HashVUBPair{Hash: txHash, Vub: vub})
|
||||
}
|
||||
|
||||
for _, ctrName := range constants.ContractList {
|
||||
cs := c.GetContract(ctrName)
|
||||
|
||||
ctrHash := cs.Hash
|
||||
if c.IsUpdated(ctrHash, cs) {
|
||||
c.Command.Printf("%s contract is already deployed.\n", ctrName)
|
||||
continue
|
||||
}
|
||||
|
||||
err := helper.AddManifestGroup(c.ContractWallet, ctrHash, cs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't sign manifest group: %v", err)
|
||||
}
|
||||
|
||||
args, err := helper.GetContractDeployData(c, ctrName, keysParam, constants.DeployMethodName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: getting deploy params: %v", ctrName, err)
|
||||
}
|
||||
params := helper.GetContractDeployParameters(cs, args)
|
||||
res, err := c.CommitteeAct.MakeCall(management.Hash, constants.DeployMethodName, params...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't deploy %s contract: %w", ctrName, err)
|
||||
}
|
||||
|
||||
if err := c.SendCommitteeTx(res.Script, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return c.AwaitTx()
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
package initialize
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"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/management"
|
||||
"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"
|
||||
)
|
||||
|
||||
func setNNS(c *helper.InitializeContext) error {
|
||||
r := management.NewReader(c.ReadOnlyInvoker)
|
||||
nnsCs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ok, err := c.NNSRootRegistered(nnsCs.Hash, "frostfs")
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
bw := io.NewBufBinWriter()
|
||||
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
|
||||
"frostfs", c.CommitteeAcc.Contract.ScriptHash(),
|
||||
constants.FrostfsOpsEmail, int64(3600), int64(600), int64(constants.DefaultExpirationTime), int64(3600))
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
if err := c.SendCommitteeTx(bw.Bytes(), true); err != nil {
|
||||
return fmt.Errorf("can't add domain root to NNS: %w", err)
|
||||
}
|
||||
if err := c.AwaitTx(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
alphaCs := c.GetContract(constants.AlphabetContract)
|
||||
for i, acc := range c.Accounts {
|
||||
alphaCs.Hash = state.CreateContractHash(acc.Contract.ScriptHash(), alphaCs.NEF.Checksum, alphaCs.Manifest.Name)
|
||||
|
||||
domain := helper.GetAlphabetNNSDomain(i)
|
||||
if err := nnsRegisterDomain(c, nnsCs.Hash, alphaCs.Hash, domain); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Command.Printf("NNS: Set %s -> %s\n", domain, alphaCs.Hash.StringLE())
|
||||
}
|
||||
|
||||
for _, ctrName := range constants.ContractList {
|
||||
cs := c.GetContract(ctrName)
|
||||
|
||||
domain := ctrName + ".frostfs"
|
||||
if err := nnsRegisterDomain(c, nnsCs.Hash, cs.Hash, domain); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Command.Printf("NNS: Set %s -> %s\n", domain, cs.Hash.StringLE())
|
||||
}
|
||||
|
||||
groupKey := c.ContractWallet.Accounts[0].PrivateKey().PublicKey()
|
||||
err = updateNNSGroup(c, nnsCs.Hash, groupKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Command.Printf("NNS: Set %s -> %s\n", morphClient.NNSGroupKeyName, hex.EncodeToString(groupKey.Bytes()))
|
||||
|
||||
return c.AwaitTx()
|
||||
}
|
||||
|
||||
func updateNNSGroup(c *helper.InitializeContext, nnsHash util.Uint160, pub *keys.PublicKey) error {
|
||||
bw := io.NewBufBinWriter()
|
||||
keyAlreadyAdded, domainRegCodeEmitted, err := c.EmitUpdateNNSGroupScript(bw, nnsHash, pub)
|
||||
if keyAlreadyAdded || err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
script := bw.Bytes()
|
||||
if domainRegCodeEmitted {
|
||||
w := io.NewBufBinWriter()
|
||||
emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1})
|
||||
wrapRegisterScriptWithPrice(w, nnsHash, script)
|
||||
script = w.Bytes()
|
||||
}
|
||||
|
||||
return c.SendCommitteeTx(script, true)
|
||||
}
|
||||
|
||||
// wrapRegisterScriptWithPrice wraps a given script with `getPrice`/`setPrice` calls for NNS.
|
||||
// It is intended to be used for a single transaction, and not as a part of other scripts.
|
||||
// It is assumed that script already contains static slot initialization code, the first one
|
||||
// (with index 0) is used to store the price.
|
||||
func wrapRegisterScriptWithPrice(w *io.BufBinWriter, nnsHash util.Uint160, s []byte) {
|
||||
if len(s) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
emit.AppCall(w.BinWriter, nnsHash, "getPrice", callflag.All)
|
||||
emit.Opcodes(w.BinWriter, opcode.STSFLD0)
|
||||
emit.AppCall(w.BinWriter, nnsHash, "setPrice", callflag.All, 1)
|
||||
|
||||
w.WriteBytes(s)
|
||||
|
||||
emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.PUSH1, opcode.PACK)
|
||||
emit.AppCallNoArgs(w.BinWriter, nnsHash, "setPrice", callflag.All)
|
||||
|
||||
if w.Err != nil {
|
||||
panic(fmt.Errorf("BUG: can't wrap register script: %w", w.Err))
|
||||
}
|
||||
}
|
||||
|
||||
func nnsRegisterDomain(c *helper.InitializeContext, nnsHash, expectedHash util.Uint160, domain string) error {
|
||||
script, ok, err := c.NNSRegisterDomainScript(nnsHash, expectedHash, domain)
|
||||
if ok || err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w := io.NewBufBinWriter()
|
||||
emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1})
|
||||
wrapRegisterScriptWithPrice(w, nnsHash, script)
|
||||
|
||||
emit.AppCall(w.BinWriter, nnsHash, "deleteRecords", callflag.All, domain, int64(nns.TXT))
|
||||
emit.AppCall(w.BinWriter, nnsHash, "addRecord", callflag.All,
|
||||
domain, int64(nns.TXT), expectedHash.StringLE())
|
||||
emit.AppCall(w.BinWriter, nnsHash, "addRecord", callflag.All,
|
||||
domain, int64(nns.TXT), address.Uint160ToString(expectedHash))
|
||||
return c.SendCommitteeTx(w.Bytes(), true)
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
package morph
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
|
@ -27,8 +29,8 @@ const (
|
|||
registerBatchSize = transaction.MaxAttributes - 1
|
||||
)
|
||||
|
||||
func (c *initializeContext) registerCandidateRange(start, end int) error {
|
||||
regPrice, err := c.getCandidateRegisterPrice()
|
||||
func registerCandidateRange(c *helper.InitializeContext, start, end int) error {
|
||||
regPrice, err := getCandidateRegisterPrice(c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch registration price: %w", err)
|
||||
}
|
||||
|
@ -45,7 +47,7 @@ func (c *initializeContext) registerCandidateRange(start, end int) error {
|
|||
}
|
||||
|
||||
signers := []actor.SignerAccount{{
|
||||
Signer: c.getSigner(false, c.CommitteeAcc),
|
||||
Signer: c.GetSigner(false, c.CommitteeAcc),
|
||||
Account: c.CommitteeAcc,
|
||||
}}
|
||||
for _, acc := range c.Accounts[start:end] {
|
||||
|
@ -67,7 +69,7 @@ func (c *initializeContext) registerCandidateRange(start, end int) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("can't create tx: %w", err)
|
||||
}
|
||||
if err := c.multiSign(tx, committeeAccountName); err != nil {
|
||||
if err := c.MultiSign(tx, constants.CommitteeAccountName); err != nil {
|
||||
return fmt.Errorf("can't sign a transaction: %w", err)
|
||||
}
|
||||
|
||||
|
@ -78,10 +80,10 @@ func (c *initializeContext) registerCandidateRange(start, end int) error {
|
|||
}
|
||||
}
|
||||
|
||||
return c.sendTx(tx, c.Command, true)
|
||||
return c.SendTx(tx, c.Command, true)
|
||||
}
|
||||
|
||||
func (c *initializeContext) registerCandidates() error {
|
||||
func registerCandidates(c *helper.InitializeContext) error {
|
||||
cc, err := unwrap.Array(c.ReadOnlyInvoker.Call(neo.Hash, "getCandidates"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("`getCandidates`: %w", err)
|
||||
|
@ -106,7 +108,7 @@ func (c *initializeContext) registerCandidates() error {
|
|||
if have >= end {
|
||||
continue
|
||||
}
|
||||
if err := c.registerCandidateRange(start, end); err != nil {
|
||||
if err := registerCandidateRange(c, start, end); err != nil {
|
||||
return fmt.Errorf("registering candidates %d..%d: %q", start, end-1, err)
|
||||
}
|
||||
}
|
||||
|
@ -114,15 +116,15 @@ func (c *initializeContext) registerCandidates() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *initializeContext) transferNEOToAlphabetContracts() error {
|
||||
func transferNEOToAlphabetContracts(c *helper.InitializeContext) error {
|
||||
neoHash := neo.Hash
|
||||
|
||||
ok, err := c.transferNEOFinished(neoHash)
|
||||
ok, err := transferNEOFinished(c, neoHash)
|
||||
if ok || err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cs := c.getContract(alphabetContract)
|
||||
cs := c.GetContract(constants.AlphabetContract)
|
||||
amount := initialAlphabetNEOAmount / len(c.Wallets)
|
||||
|
||||
bw := io.NewBufBinWriter()
|
||||
|
@ -133,14 +135,14 @@ func (c *initializeContext) transferNEOToAlphabetContracts() error {
|
|||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
}
|
||||
|
||||
if err := c.sendCommitteeTx(bw.Bytes(), false); err != nil {
|
||||
if err := c.SendCommitteeTx(bw.Bytes(), false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.awaitTx()
|
||||
return c.AwaitTx()
|
||||
}
|
||||
|
||||
func (c *initializeContext) transferNEOFinished(neoHash util.Uint160) (bool, error) {
|
||||
func transferNEOFinished(c *helper.InitializeContext, neoHash util.Uint160) (bool, error) {
|
||||
r := nep17.NewReader(c.ReadOnlyInvoker, neoHash)
|
||||
bal, err := r.BalanceOf(c.CommitteeAcc.Contract.ScriptHash())
|
||||
return bal.Cmp(big.NewInt(native.NEOTotalSupply)) == -1, err
|
||||
|
@ -148,7 +150,7 @@ func (c *initializeContext) transferNEOFinished(neoHash util.Uint160) (bool, err
|
|||
|
||||
var errGetPriceInvalid = errors.New("`getRegisterPrice`: invalid response")
|
||||
|
||||
func (c *initializeContext) getCandidateRegisterPrice() (int64, error) {
|
||||
func getCandidateRegisterPrice(c *helper.InitializeContext) (int64, error) {
|
||||
switch c.Client.(type) {
|
||||
case *rpcclient.Client:
|
||||
inv := invoker.New(c.Client, nil)
|
||||
|
@ -156,7 +158,7 @@ func (c *initializeContext) getCandidateRegisterPrice() (int64, error) {
|
|||
return reader.GetRegisterPrice()
|
||||
default:
|
||||
neoHash := neo.Hash
|
||||
res, err := invokeFunction(c.Client, neoHash, "getRegisterPrice", nil, nil)
|
||||
res, err := helper.InvokeFunction(c.Client, neoHash, "getRegisterPrice", nil, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package morph
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/rolemgmt"
|
||||
|
@ -8,8 +9,8 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
)
|
||||
|
||||
func (c *initializeContext) setNotaryAndAlphabetNodes() error {
|
||||
if ok, err := c.setRolesFinished(); ok || err != nil {
|
||||
func setNotaryAndAlphabetNodes(c *helper.InitializeContext) error {
|
||||
if ok, err := setRolesFinished(c); ok || err != nil {
|
||||
if err == nil {
|
||||
c.Command.Println("Stage 2: already performed.")
|
||||
}
|
||||
|
@ -27,19 +28,19 @@ func (c *initializeContext) setNotaryAndAlphabetNodes() error {
|
|||
emit.AppCall(w.BinWriter, rolemgmt.Hash, "designateAsRole",
|
||||
callflag.States|callflag.AllowNotify, int64(noderoles.NeoFSAlphabet), pubs)
|
||||
|
||||
if err := c.sendCommitteeTx(w.Bytes(), false); err != nil {
|
||||
if err := c.SendCommitteeTx(w.Bytes(), false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.awaitTx()
|
||||
return c.AwaitTx()
|
||||
}
|
||||
|
||||
func (c *initializeContext) setRolesFinished() (bool, error) {
|
||||
func setRolesFinished(c *helper.InitializeContext) (bool, error) {
|
||||
height, err := c.Client.GetBlockCount()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
pubs, err := getDesignatedByRole(c.ReadOnlyInvoker, rolemgmt.Hash, noderoles.NeoFSAlphabet, height)
|
||||
pubs, err := helper.GetDesignatedByRole(c.ReadOnlyInvoker, rolemgmt.Hash, noderoles.NeoFSAlphabet, height)
|
||||
return len(pubs) == len(c.Wallets), err
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package morph
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
|
@ -9,6 +9,14 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
cmdConfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/generate"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/netmap"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/node"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/policy"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
|
@ -42,10 +50,10 @@ func TestInitialize(t *testing.T) {
|
|||
testInitialize(t, 16)
|
||||
})
|
||||
fyrchik marked this conversation as resolved
Outdated
fyrchik
commented
Besides dev-env, we should run Besides dev-env, we should run `TestInitialize` (remove temporarily `t.Skip()`)
acid-ant
commented
Thanks, missed that. All tests were passed.
Thanks, missed that. All tests were passed.
```
...
=== RUN TestInitialize
--- PASS: TestInitialize (237.50s)
=== RUN TestInitialize/1_nodes
size: 1
alphabet-wallets: /tmp/TestInitialize1_nodes2496607394/001
wallet[0]: 0
Stage 1: transfer GAS to alphabet nodes.
Waiting for transactions to persist...
...
Current epoch: 0, increase to 1.
Waiting for transactions to persist...
--- PASS: TestInitialize/max_nodes/force-new-epoch (18.24s)
=== RUN TestInitialize/max_nodes/set-config
Waiting for transactions to persist...
--- PASS: TestInitialize/max_nodes/set-config (17.57s)
=== RUN TestInitialize/max_nodes/set-policy
Waiting for transactions to persist...
--- PASS: TestInitialize/max_nodes/set-policy (20.97s)
=== RUN TestInitialize/max_nodes/remove-node
Current epoch: 0, increase to 1.
Waiting for transactions to persist...
--- PASS: TestInitialize/max_nodes/remove-node (18.10s)
=== RUN TestInitialize/too_many_nodes
--- PASS: TestInitialize/too_many_nodes (0.00s)
PASS
Process finished with the exit code 0
```
fyrchik
commented
multinodes tests too? multinodes tests too?
acid-ant
commented
Yes, all tests in Yes, all tests in `TestInitialize` were passed.
|
||||
t.Run("max nodes", func(t *testing.T) {
|
||||
testInitialize(t, maxAlphabetNodes)
|
||||
testInitialize(t, constants.MaxAlphabetNodes)
|
||||
})
|
||||
t.Run("too many nodes", func(t *testing.T) {
|
||||
require.ErrorIs(t, generateTestData(t, t.TempDir(), maxAlphabetNodes+1), ErrTooManyAlphabetNodes)
|
||||
require.ErrorIs(t, generateTestData(t, t.TempDir(), constants.MaxAlphabetNodes+1), helper.ErrTooManyAlphabetNodes)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -54,53 +62,53 @@ func testInitialize(t *testing.T, committeeSize int) {
|
|||
v := viper.GetViper()
|
||||
|
||||
require.NoError(t, generateTestData(t, testdataDir, committeeSize))
|
||||
v.Set(protoConfigPath, filepath.Join(testdataDir, protoFileName))
|
||||
v.Set(constants.ProtoConfigPath, filepath.Join(testdataDir, protoFileName))
|
||||
|
||||
// Set to the path or remove the next statement to download from the network.
|
||||
require.NoError(t, initCmd.Flags().Set(contractsInitFlag, contractsPath))
|
||||
require.NoError(t, Cmd.Flags().Set(commonflags.ContractsInitFlag, contractsPath))
|
||||
|
||||
dumpPath := filepath.Join(testdataDir, "out")
|
||||
require.NoError(t, initCmd.Flags().Set(localDumpFlag, dumpPath))
|
||||
v.Set(alphabetWalletsFlag, testdataDir)
|
||||
v.Set(epochDurationInitFlag, 1)
|
||||
v.Set(maxObjectSizeInitFlag, 1024)
|
||||
require.NoError(t, Cmd.Flags().Set(commonflags.LocalDumpFlag, dumpPath))
|
||||
v.Set(commonflags.AlphabetWalletsFlag, testdataDir)
|
||||
v.Set(commonflags.EpochDurationInitFlag, 1)
|
||||
v.Set(commonflags.MaxObjectSizeInitFlag, 1024)
|
||||
|
||||
setTestCredentials(v, committeeSize)
|
||||
require.NoError(t, initializeSideChainCmd(initCmd, nil))
|
||||
require.NoError(t, initializeSideChainCmd(Cmd, nil))
|
||||
|
||||
t.Run("force-new-epoch", func(t *testing.T) {
|
||||
require.NoError(t, forceNewEpoch.Flags().Set(localDumpFlag, dumpPath))
|
||||
require.NoError(t, forceNewEpochCmd(forceNewEpoch, nil))
|
||||
require.NoError(t, netmap.ForceNewEpoch.Flags().Set(commonflags.LocalDumpFlag, dumpPath))
|
||||
require.NoError(t, netmap.ForceNewEpochCmd(netmap.ForceNewEpoch, nil))
|
||||
})
|
||||
t.Run("set-config", func(t *testing.T) {
|
||||
require.NoError(t, setConfig.Flags().Set(localDumpFlag, dumpPath))
|
||||
require.NoError(t, setConfigCmd(setConfig, []string{"MaintenanceModeAllowed=true"}))
|
||||
require.NoError(t, cmdConfig.SetCmd.Flags().Set(commonflags.LocalDumpFlag, dumpPath))
|
||||
require.NoError(t, cmdConfig.SetConfigCmd(cmdConfig.SetCmd, []string{"MaintenanceModeAllowed=true"}))
|
||||
})
|
||||
t.Run("set-policy", func(t *testing.T) {
|
||||
require.NoError(t, setPolicy.Flags().Set(localDumpFlag, dumpPath))
|
||||
require.NoError(t, setPolicyCmd(setPolicy, []string{"ExecFeeFactor=1"}))
|
||||
require.NoError(t, policy.Set.Flags().Set(commonflags.LocalDumpFlag, dumpPath))
|
||||
require.NoError(t, policy.SetPolicyCmd(policy.Set, []string{"ExecFeeFactor=1"}))
|
||||
})
|
||||
t.Run("remove-node", func(t *testing.T) {
|
||||
pk, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
pub := hex.EncodeToString(pk.PublicKey().Bytes())
|
||||
require.NoError(t, removeNodes.Flags().Set(localDumpFlag, dumpPath))
|
||||
require.NoError(t, removeNodesCmd(removeNodes, []string{pub}))
|
||||
require.NoError(t, node.RemoveCmd.Flags().Set(commonflags.LocalDumpFlag, dumpPath))
|
||||
require.NoError(t, node.RemoveNodesCmd(node.RemoveCmd, []string{pub}))
|
||||
})
|
||||
}
|
||||
|
||||
func generateTestData(t *testing.T, dir string, size int) error {
|
||||
v := viper.GetViper()
|
||||
v.Set(alphabetWalletsFlag, dir)
|
||||
v.Set(commonflags.AlphabetWalletsFlag, dir)
|
||||
|
||||
sizeStr := strconv.FormatUint(uint64(size), 10)
|
||||
if err := generateAlphabetCmd.Flags().Set(alphabetSizeFlag, sizeStr); err != nil {
|
||||
if err := generate.GenerateAlphabetCmd.Flags().Set(commonflags.AlphabetSizeFlag, sizeStr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
setTestCredentials(v, size)
|
||||
if err := generateAlphabetCreds(generateAlphabetCmd, nil); err != nil {
|
||||
if err := generate.AlphabetCreds(generate.GenerateAlphabetCmd, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -112,7 +120,7 @@ func generateTestData(t *testing.T, dir string, size int) error {
|
|||
return fmt.Errorf("wallet doesn't exist: %w", err)
|
||||
}
|
||||
for _, acc := range w.Accounts {
|
||||
if acc.Label == singleAccountName {
|
||||
if acc.Label == constants.SingleAccountName {
|
||||
pub, ok := vm.ParseSignatureContract(acc.Contract.Script)
|
||||
if !ok {
|
||||
return fmt.Errorf("could not parse signature script for %s", acc.Address)
|
||||
|
@ -143,40 +151,5 @@ func setTestCredentials(v *viper.Viper, size int) {
|
|||
for i := 0; i < size; i++ {
|
||||
v.Set("credentials."+innerring.GlagoliticLetter(i).String(), strconv.FormatUint(uint64(i), 10))
|
||||
}
|
||||
v.Set("credentials.contract", testContractPassword)
|
||||
}
|
||||
|
||||
func TestNextPollInterval(t *testing.T) {
|
||||
var pollInterval time.Duration
|
||||
var iteration int
|
||||
|
||||
pollInterval, hasChanged := nextPollInterval(iteration, pollInterval)
|
||||
require.True(t, hasChanged)
|
||||
require.Equal(t, time.Second, pollInterval)
|
||||
|
||||
iteration = 4
|
||||
pollInterval, hasChanged = nextPollInterval(iteration, pollInterval)
|
||||
require.False(t, hasChanged)
|
||||
require.Equal(t, time.Second, pollInterval)
|
||||
|
||||
iteration = 5
|
||||
pollInterval, hasChanged = nextPollInterval(iteration, pollInterval)
|
||||
require.True(t, hasChanged)
|
||||
require.Equal(t, 2*time.Second, pollInterval)
|
||||
|
||||
iteration = 10
|
||||
pollInterval, hasChanged = nextPollInterval(iteration, pollInterval)
|
||||
require.True(t, hasChanged)
|
||||
require.Equal(t, 4*time.Second, pollInterval)
|
||||
|
||||
iteration = 20
|
||||
pollInterval = 32 * time.Second
|
||||
pollInterval, hasChanged = nextPollInterval(iteration, pollInterval)
|
||||
require.True(t, hasChanged) // from 32s to 16s
|
||||
require.Equal(t, 16*time.Second, pollInterval)
|
||||
|
||||
pollInterval = 16 * time.Second
|
||||
pollInterval, hasChanged = nextPollInterval(iteration, pollInterval)
|
||||
require.False(t, hasChanged)
|
||||
require.Equal(t, 16*time.Second, pollInterval)
|
||||
v.Set("credentials.contract", constants.TestContractPassword)
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
package morph
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
|
@ -12,7 +14,6 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
scContext "github.com/nspcc-dev/neo-go/pkg/smartcontract/context"
|
||||
"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"
|
||||
|
@ -27,8 +28,8 @@ const (
|
|||
initialProxyGASAmount = 50_000 * native.GASFactor
|
||||
)
|
||||
|
||||
func (c *initializeContext) transferFunds() error {
|
||||
ok, err := c.transferFundsFinished()
|
||||
func transferFunds(c *helper.InitializeContext) error {
|
||||
ok, err := transferFundsFinished(c)
|
||||
if ok || err != nil {
|
||||
if err == nil {
|
||||
c.Command.Println("Stage 1: already performed.")
|
||||
|
@ -67,14 +68,14 @@ func (c *initializeContext) transferFunds() error {
|
|||
return fmt.Errorf("can't create transfer transaction: %w", err)
|
||||
}
|
||||
|
||||
if err := c.multiSignAndSend(tx, consensusAccountName); err != nil {
|
||||
if err := c.MultiSignAndSend(tx, constants.ConsensusAccountName); err != nil {
|
||||
return fmt.Errorf("can't send transfer transaction: %w", err)
|
||||
}
|
||||
|
||||
return c.awaitTx()
|
||||
return c.AwaitTx()
|
||||
}
|
||||
|
||||
func (c *initializeContext) transferFundsFinished() (bool, error) {
|
||||
func transferFundsFinished(c *helper.InitializeContext) (bool, error) {
|
||||
acc := c.Accounts[0]
|
||||
|
||||
r := nep17.NewReader(c.ReadOnlyInvoker, gas.Hash)
|
||||
|
@ -82,68 +83,8 @@ func (c *initializeContext) transferFundsFinished() (bool, error) {
|
|||
return res.Cmp(big.NewInt(initialAlphabetGASAmount/2)) == 1, err
|
||||
}
|
||||
|
||||
func (c *initializeContext) multiSignAndSend(tx *transaction.Transaction, accType string) error {
|
||||
if err := c.multiSign(tx, accType); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.sendTx(tx, c.Command, false)
|
||||
}
|
||||
|
||||
func (c *initializeContext) multiSign(tx *transaction.Transaction, accType string) error {
|
||||
version, err := c.Client.GetVersion()
|
||||
if err != nil {
|
||||
// error appears only if client
|
||||
// has not been initialized
|
||||
panic(err)
|
||||
}
|
||||
network := version.Protocol.Network
|
||||
|
||||
// Use parameter context to avoid dealing with signature order.
|
||||
pc := scContext.NewParameterContext("", network, tx)
|
||||
h := c.CommitteeAcc.Contract.ScriptHash()
|
||||
if accType == consensusAccountName {
|
||||
h = c.ConsensusAcc.Contract.ScriptHash()
|
||||
}
|
||||
for _, w := range c.Wallets {
|
||||
acc, err := getWalletAccount(w, accType)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't find %s wallet account: %w", accType, err)
|
||||
}
|
||||
|
||||
priv := acc.PrivateKey()
|
||||
sign := priv.SignHashable(uint32(network), tx)
|
||||
if err := pc.AddSignature(h, acc.Contract, priv.PublicKey(), sign); err != nil {
|
||||
return fmt.Errorf("can't add signature: %w", err)
|
||||
}
|
||||
if len(pc.Items[h].Signatures) == len(acc.Contract.Parameters) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
w, err := pc.GetWitness(h)
|
||||
if err != nil {
|
||||
return fmt.Errorf("incomplete signature: %w", err)
|
||||
}
|
||||
|
||||
for i := range tx.Signers {
|
||||
if tx.Signers[i].Account == h {
|
||||
if i < len(tx.Scripts) {
|
||||
tx.Scripts[i] = *w
|
||||
} else if i == len(tx.Scripts) {
|
||||
tx.Scripts = append(tx.Scripts, *w)
|
||||
} else {
|
||||
panic("BUG: invalid signing order")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("%s account was not found among transaction signers", accType)
|
||||
}
|
||||
|
||||
func (c *initializeContext) transferGASToProxy() error {
|
||||
proxyCs := c.getContract(proxyContract)
|
||||
func transferGASToProxy(c *helper.InitializeContext) error {
|
||||
proxyCs := c.GetContract(constants.ProxyContract)
|
||||
|
||||
r := nep17.NewReader(c.ReadOnlyInvoker, gas.Hash)
|
||||
bal, err := r.BalanceOf(proxyCs.Hash)
|
||||
|
@ -160,11 +101,11 @@ func (c *initializeContext) transferGASToProxy() error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := c.multiSignAndSend(tx, committeeAccountName); err != nil {
|
||||
if err := c.MultiSignAndSend(tx, constants.CommitteeAccountName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.awaitTx()
|
||||
return c.AwaitTx()
|
||||
}
|
||||
|
||||
type transferTarget struct {
|
||||
|
@ -174,7 +115,7 @@ type transferTarget struct {
|
|||
Data any
|
||||
}
|
||||
|
||||
func createNEP17MultiTransferTx(c Client, acc *wallet.Account, recipients []transferTarget) (*transaction.Transaction, error) {
|
||||
func createNEP17MultiTransferTx(c helper.Client, acc *wallet.Account, recipients []transferTarget) (*transaction.Transaction, error) {
|
||||
from := acc.Contract.ScriptHash()
|
||||
|
||||
w := io.NewBufBinWriter()
|
56
cmd/frostfs-adm/internal/modules/morph/initialize/root.go
Normal file
56
cmd/frostfs-adm/internal/modules/morph/initialize/root.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
package initialize
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
maxObjectSizeCLIFlag = "max-object-size"
|
||||
epochDurationCLIFlag = "epoch-duration"
|
||||
containerFeeCLIFlag = "container-fee"
|
||||
containerAliasFeeCLIFlag = "container-alias-fee"
|
||||
candidateFeeCLIFlag = "candidate-fee"
|
||||
homomorphicHashDisabledCLIFlag = "homomorphic-disabled"
|
||||
withdrawFeeCLIFlag = "withdraw-fee"
|
||||
)
|
||||
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Initialize side chain network with smart-contracts and network settings",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.EpochDurationInitFlag, cmd.Flags().Lookup(epochDurationCLIFlag))
|
||||
_ = viper.BindPFlag(commonflags.MaxObjectSizeInitFlag, cmd.Flags().Lookup(maxObjectSizeCLIFlag))
|
||||
_ = viper.BindPFlag(commonflags.HomomorphicHashDisabledInitFlag, cmd.Flags().Lookup(homomorphicHashDisabledCLIFlag))
|
||||
_ = viper.BindPFlag(commonflags.CandidateFeeInitFlag, cmd.Flags().Lookup(candidateFeeCLIFlag))
|
||||
_ = viper.BindPFlag(commonflags.ContainerFeeInitFlag, cmd.Flags().Lookup(containerFeeCLIFlag))
|
||||
_ = viper.BindPFlag(commonflags.ContainerAliasFeeInitFlag, cmd.Flags().Lookup(containerAliasFeeCLIFlag))
|
||||
_ = viper.BindPFlag(commonflags.WithdrawFeeInitFlag, cmd.Flags().Lookup(withdrawFeeCLIFlag))
|
||||
_ = viper.BindPFlag(constants.ProtoConfigPath, cmd.Flags().Lookup(constants.ProtoConfigPath))
|
||||
},
|
||||
RunE: initializeSideChainCmd,
|
||||
}
|
||||
|
||||
func initInitCmd() {
|
||||
Cmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
Cmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
Cmd.Flags().String(commonflags.ContractsInitFlag, "", commonflags.ContractsInitFlagDesc)
|
||||
Cmd.Flags().String(commonflags.ContractsURLFlag, "", commonflags.ContractsURLFlagDesc)
|
||||
Cmd.Flags().Uint(epochDurationCLIFlag, 240, "Amount of side chain blocks in one FrostFS epoch")
|
||||
Cmd.Flags().Uint(maxObjectSizeCLIFlag, 67108864, "Max single object size in bytes")
|
||||
Cmd.Flags().Bool(homomorphicHashDisabledCLIFlag, false, "Disable object homomorphic hashing")
|
||||
// Defaults are taken from neo-preodolenie.
|
||||
Cmd.Flags().Uint64(containerFeeCLIFlag, 1000, "Container registration fee")
|
||||
Cmd.Flags().Uint64(containerAliasFeeCLIFlag, 500, "Container alias fee")
|
||||
Cmd.Flags().String(constants.ProtoConfigPath, "", "Path to the consensus node configuration")
|
||||
Cmd.Flags().String(commonflags.LocalDumpFlag, "", "Path to the blocks dump file")
|
||||
Cmd.MarkFlagsMutuallyExclusive(commonflags.ContractsInitFlag, commonflags.ContractsURLFlag)
|
||||
}
|
||||
|
||||
func init() {
|
||||
initInitCmd()
|
||||
}
|
|
@ -1,679 +0,0 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||
morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
io2 "github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
||||
"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/vm/stackitem"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
nnsContract = "nns"
|
||||
frostfsContract = "frostfs" // not deployed in side-chain.
|
||||
processingContract = "processing" // not deployed in side-chain.
|
||||
alphabetContract = "alphabet"
|
||||
balanceContract = "balance"
|
||||
containerContract = "container"
|
||||
frostfsIDContract = "frostfsid"
|
||||
netmapContract = "netmap"
|
||||
policyContract = "policy"
|
||||
proxyContract = "proxy"
|
||||
)
|
||||
|
||||
const frostfsIDAdminConfigKey = "frostfsid.admin"
|
||||
|
||||
var (
|
||||
contractList = []string{
|
||||
balanceContract,
|
||||
containerContract,
|
||||
frostfsIDContract,
|
||||
netmapContract,
|
||||
policyContract,
|
||||
proxyContract,
|
||||
}
|
||||
|
||||
fullContractList = append([]string{
|
||||
frostfsContract,
|
||||
processingContract,
|
||||
nnsContract,
|
||||
alphabetContract,
|
||||
}, contractList...)
|
||||
|
||||
netmapConfigKeys = []string{
|
||||
netmap.EpochDurationConfig,
|
||||
netmap.MaxObjectSizeConfig,
|
||||
netmap.ContainerFeeConfig,
|
||||
netmap.ContainerAliasFeeConfig,
|
||||
netmap.IrCandidateFeeConfig,
|
||||
netmap.WithdrawFeeConfig,
|
||||
netmap.HomomorphicHashingDisabledKey,
|
||||
netmap.MaintenanceModeAllowedConfig,
|
||||
}
|
||||
)
|
||||
|
||||
type contractState struct {
|
||||
NEF *nef.File
|
||||
RawNEF []byte
|
||||
Manifest *manifest.Manifest
|
||||
RawManifest []byte
|
||||
Hash util.Uint160
|
||||
}
|
||||
|
||||
const (
|
||||
updateMethodName = "update"
|
||||
deployMethodName = "deploy"
|
||||
)
|
||||
|
||||
func domainOf(contract string) string {
|
||||
return contract + ".frostfs"
|
||||
}
|
||||
|
||||
func (c *initializeContext) deployNNS(method string) error {
|
||||
cs := c.getContract(nnsContract)
|
||||
h := cs.Hash
|
||||
|
||||
nnsCs, err := c.nnsContractState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if nnsCs != nil {
|
||||
if nnsCs.NEF.Checksum == cs.NEF.Checksum {
|
||||
if method == deployMethodName {
|
||||
c.Command.Println("NNS contract is already deployed.")
|
||||
} else {
|
||||
c.Command.Println("NNS contract is already updated.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
h = nnsCs.Hash
|
||||
}
|
||||
|
||||
err = c.addManifestGroup(h, cs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't sign manifest group: %v", err)
|
||||
}
|
||||
|
||||
params := getContractDeployParameters(cs, nil)
|
||||
|
||||
invokeHash := management.Hash
|
||||
if method == updateMethodName {
|
||||
invokeHash = nnsCs.Hash
|
||||
}
|
||||
|
||||
tx, err := c.CommitteeAct.MakeCall(invokeHash, method, params...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create deploy tx for %s: %w", nnsContract, err)
|
||||
}
|
||||
|
||||
if err := c.multiSignAndSend(tx, committeeAccountName); err != nil {
|
||||
return fmt.Errorf("can't send deploy transaction: %w", err)
|
||||
}
|
||||
|
||||
return c.awaitTx()
|
||||
}
|
||||
|
||||
func (c *initializeContext) updateContracts() error {
|
||||
alphaCs := c.getContract(alphabetContract)
|
||||
|
||||
nnsCs, err := c.nnsContractState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nnsHash := nnsCs.Hash
|
||||
|
||||
w := io2.NewBufBinWriter()
|
||||
|
||||
// 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.
|
||||
// 1. Initialize static slot for alphabet NEF.
|
||||
// 2. Store NEF into the static slot.
|
||||
// 3. Push parameters for each alphabet contract on stack.
|
||||
// 4. Add contract group to the manifest.
|
||||
// 5. For each alphabet contract, invoke `update` using parameters on stack and
|
||||
// NEF from step 2 and manifest from step 4.
|
||||
emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1})
|
||||
emit.Bytes(w.BinWriter, alphaCs.RawNEF)
|
||||
emit.Opcodes(w.BinWriter, opcode.STSFLD0)
|
||||
|
||||
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)
|
||||
emit.AppCall(w.BinWriter, nnsHash, "setPrice", callflag.All, 1)
|
||||
|
||||
for _, ctrName := range contractList {
|
||||
cs := c.getContract(ctrName)
|
||||
|
||||
method := updateMethodName
|
||||
ctrHash, err := nnsResolveHash(c.ReadOnlyInvoker, nnsHash, domainOf(ctrName))
|
||||
if err != nil {
|
||||
if errors.Is(err, errMissingNNSRecord) {
|
||||
// if contract not found we deploy it instead of update
|
||||
method = deployMethodName
|
||||
} else {
|
||||
return fmt.Errorf("can't resolve hash for contract update: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = c.addManifestGroup(ctrHash, cs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't sign manifest group: %v", err)
|
||||
}
|
||||
|
||||
invokeHash := management.Hash
|
||||
if method == updateMethodName {
|
||||
invokeHash = ctrHash
|
||||
}
|
||||
|
||||
args, err := c.getContractDeployData(ctrName, keysParam, updateMethodName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: getting update params: %v", ctrName, err)
|
||||
}
|
||||
params := getContractDeployParameters(cs, args)
|
||||
res, err := c.CommitteeAct.MakeCall(invokeHash, method, params...)
|
||||
if err != nil {
|
||||
if method != updateMethodName || !strings.Contains(err.Error(), common.ErrAlreadyUpdated) {
|
||||
return fmt.Errorf("deploy contract: %w", err)
|
||||
}
|
||||
c.Command.Printf("%s contract is already updated.\n", ctrName)
|
||||
continue
|
||||
}
|
||||
|
||||
w.WriteBytes(res.Script)
|
||||
|
||||
if method == deployMethodName {
|
||||
// same actions are done in initializeContext.setNNS, can be unified
|
||||
domain := ctrName + ".frostfs"
|
||||
script, ok, err := c.nnsRegisterDomainScript(nnsHash, cs.Hash, domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
w.WriteBytes(script)
|
||||
emit.AppCall(w.BinWriter, nnsHash, "deleteRecords", callflag.All, domain, int64(nns.TXT))
|
||||
emit.AppCall(w.BinWriter, nnsHash, "addRecord", callflag.All,
|
||||
domain, int64(nns.TXT), cs.Hash.StringLE())
|
||||
emit.AppCall(w.BinWriter, nnsHash, "addRecord", callflag.All,
|
||||
domain, int64(nns.TXT), address.Uint160ToString(cs.Hash))
|
||||
}
|
||||
c.Command.Printf("NNS: Set %s -> %s\n", domain, cs.Hash.StringLE())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
if err := c.sendCommitteeTx(w.Bytes(), false); err != nil {
|
||||
if !strings.Contains(err.Error(), common.ErrAlreadyUpdated) {
|
||||
return nil, err
|
||||
}
|
||||
c.Command.Println("Alphabet contracts are already updated.")
|
||||
}
|
||||
|
||||
return keysParam, nil
|
||||
}
|
||||
|
||||
func (c *initializeContext) deployContracts() error {
|
||||
alphaCs := c.getContract(alphabetContract)
|
||||
|
||||
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 := state.CreateContractHash(acc.Contract.ScriptHash(), alphaCs.NEF.Checksum, alphaCs.Manifest.Name)
|
||||
if c.isUpdated(ctrHash, alphaCs) {
|
||||
c.Command.Printf("Alphabet contract #%d is already deployed.\n", i)
|
||||
continue
|
||||
}
|
||||
|
||||
alphaCs.Manifest.Groups = baseGroups
|
||||
err := c.addManifestGroup(ctrHash, alphaCs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't sign manifest group: %v", err)
|
||||
}
|
||||
|
||||
keysParam = append(keysParam, acc.PrivateKey().PublicKey().Bytes())
|
||||
params := getContractDeployParameters(alphaCs, c.getAlphabetDeployItems(i, len(c.Wallets)))
|
||||
|
||||
act, err := actor.NewSimple(c.Client, acc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create actor: %w", err)
|
||||
}
|
||||
|
||||
txHash, vub, err := act.SendCall(management.Hash, deployMethodName, params...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't deploy alphabet #%d contract: %w", i, err)
|
||||
}
|
||||
|
||||
c.SentTxs = append(c.SentTxs, hashVUBPair{hash: txHash, vub: vub})
|
||||
}
|
||||
|
||||
for _, ctrName := range contractList {
|
||||
cs := c.getContract(ctrName)
|
||||
|
||||
ctrHash := cs.Hash
|
||||
if c.isUpdated(ctrHash, cs) {
|
||||
c.Command.Printf("%s contract is already deployed.\n", ctrName)
|
||||
continue
|
||||
}
|
||||
|
||||
err := c.addManifestGroup(ctrHash, cs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't sign manifest group: %v", err)
|
||||
}
|
||||
|
||||
args, err := c.getContractDeployData(ctrName, keysParam, deployMethodName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: getting deploy params: %v", ctrName, err)
|
||||
}
|
||||
params := getContractDeployParameters(cs, args)
|
||||
res, err := c.CommitteeAct.MakeCall(management.Hash, deployMethodName, params...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't deploy %s contract: %w", ctrName, err)
|
||||
}
|
||||
|
||||
if err := c.sendCommitteeTx(res.Script, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return c.awaitTx()
|
||||
}
|
||||
|
||||
func (c *initializeContext) isUpdated(ctrHash util.Uint160, cs *contractState) bool {
|
||||
r := management.NewReader(c.ReadOnlyInvoker)
|
||||
realCs, err := r.GetContract(ctrHash)
|
||||
return err == nil && realCs != nil && realCs.NEF.Checksum == cs.NEF.Checksum
|
||||
}
|
||||
|
||||
func (c *initializeContext) getContract(ctrName string) *contractState {
|
||||
return c.Contracts[ctrName]
|
||||
}
|
||||
|
||||
func (c *initializeContext) readContracts(names []string) error {
|
||||
var (
|
||||
fi os.FileInfo
|
||||
err error
|
||||
)
|
||||
if c.ContractPath != "" {
|
||||
fi, err = os.Stat(c.ContractPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid contracts path: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if c.ContractPath != "" && fi.IsDir() {
|
||||
for _, ctrName := range names {
|
||||
cs, err := readContract(filepath.Join(c.ContractPath, ctrName), ctrName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Contracts[ctrName] = cs
|
||||
}
|
||||
} else {
|
||||
var r io.ReadCloser
|
||||
if c.ContractPath != "" {
|
||||
r, err = os.Open(c.ContractPath)
|
||||
} else if c.ContractURL != "" {
|
||||
r, err = downloadContracts(c.Command, c.ContractURL)
|
||||
} else {
|
||||
r, err = downloadContractsFromRepository(c.Command)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't open contracts archive: %w", err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
m, err := readContractsFromArchive(r, names)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, name := range names {
|
||||
if err := m[name].parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Contracts[name] = m[name]
|
||||
}
|
||||
}
|
||||
|
||||
for _, ctrName := range names {
|
||||
if ctrName != alphabetContract {
|
||||
cs := c.Contracts[ctrName]
|
||||
cs.Hash = state.CreateContractHash(c.CommitteeAcc.Contract.ScriptHash(),
|
||||
cs.NEF.Checksum, cs.Manifest.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readContract(ctrPath, ctrName string) (*contractState, error) {
|
||||
rawNef, err := os.ReadFile(filepath.Join(ctrPath, ctrName+"_contract.nef"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read NEF file for %s contract: %w", ctrName, err)
|
||||
}
|
||||
rawManif, err := os.ReadFile(filepath.Join(ctrPath, "config.json"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read manifest file for %s contract: %w", ctrName, err)
|
||||
}
|
||||
|
||||
cs := &contractState{
|
||||
RawNEF: rawNef,
|
||||
RawManifest: rawManif,
|
||||
}
|
||||
|
||||
return cs, cs.parse()
|
||||
}
|
||||
|
||||
func (cs *contractState) parse() error {
|
||||
nf, err := nef.FileFromBytes(cs.RawNEF)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't parse NEF file: %w", err)
|
||||
}
|
||||
|
||||
m := new(manifest.Manifest)
|
||||
if err := json.Unmarshal(cs.RawManifest, m); err != nil {
|
||||
return fmt.Errorf("can't parse manifest file: %w", err)
|
||||
}
|
||||
|
||||
cs.NEF = &nf
|
||||
cs.Manifest = m
|
||||
return nil
|
||||
}
|
||||
|
||||
func readContractsFromArchive(file io.Reader, names []string) (map[string]*contractState, error) {
|
||||
m := make(map[string]*contractState, len(names))
|
||||
for i := range names {
|
||||
m[names[i]] = new(contractState)
|
||||
}
|
||||
|
||||
gr, err := gzip.NewReader(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("contracts file must be tar.gz archive: %w", err)
|
||||
}
|
||||
|
||||
r := tar.NewReader(gr)
|
||||
for h, err := r.Next(); ; h, err = r.Next() {
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
dir, _ := filepath.Split(h.Name)
|
||||
ctrName := filepath.Base(dir)
|
||||
|
||||
cs, ok := m[ctrName]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.HasSuffix(h.Name, filepath.Join(ctrName, ctrName+"_contract.nef")):
|
||||
cs.RawNEF, err = io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read NEF file for %s contract: %w", ctrName, err)
|
||||
}
|
||||
case strings.HasSuffix(h.Name, "config.json"):
|
||||
cs.RawManifest, err = io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read manifest file for %s contract: %w", ctrName, err)
|
||||
}
|
||||
}
|
||||
m[ctrName] = cs
|
||||
}
|
||||
|
||||
for ctrName, cs := range m {
|
||||
if cs.RawNEF == nil {
|
||||
return nil, fmt.Errorf("NEF for %s contract wasn't found", ctrName)
|
||||
}
|
||||
if cs.RawManifest == nil {
|
||||
return nil, fmt.Errorf("manifest for %s contract wasn't found", ctrName)
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func getContractDeployParameters(cs *contractState, deployData []any) []any {
|
||||
return []any{cs.RawNEF, cs.RawManifest, deployData}
|
||||
}
|
||||
|
||||
func (c *initializeContext) getContractDeployData(ctrName string, keysParam []any, method string) ([]any, error) {
|
||||
items := make([]any, 0, 6)
|
||||
|
||||
switch ctrName {
|
||||
case frostfsContract:
|
||||
items = append(items,
|
||||
c.Contracts[processingContract].Hash,
|
||||
keysParam,
|
||||
smartcontract.Parameter{})
|
||||
case processingContract:
|
||||
items = append(items, c.Contracts[frostfsContract].Hash)
|
||||
return items[1:], nil // no notary info
|
||||
case balanceContract:
|
||||
items = append(items,
|
||||
c.Contracts[netmapContract].Hash,
|
||||
c.Contracts[containerContract].Hash)
|
||||
case containerContract:
|
||||
// In case if NNS is updated multiple times, we can't calculate
|
||||
// it's actual hash based on local data, thus query chain.
|
||||
r := management.NewReader(c.ReadOnlyInvoker)
|
||||
nnsCs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get nns contract: %w", err)
|
||||
}
|
||||
items = append(items,
|
||||
c.Contracts[netmapContract].Hash,
|
||||
c.Contracts[balanceContract].Hash,
|
||||
c.Contracts[frostfsIDContract].Hash,
|
||||
nnsCs.Hash,
|
||||
"container")
|
||||
case frostfsIDContract:
|
||||
var (
|
||||
h util.Uint160
|
||||
found bool
|
||||
err error
|
||||
)
|
||||
if method == updateMethodName {
|
||||
h, found, err = c.getFrostfsIDAdminFromContract()
|
||||
}
|
||||
if method != updateMethodName || err == nil && !found {
|
||||
h, found, err = getFrostfsIDAdmin(viper.GetViper())
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if found {
|
||||
items = append(items, h)
|
||||
} else {
|
||||
items = append(items, c.Contracts[proxyContract].Hash)
|
||||
}
|
||||
case netmapContract:
|
||||
md := getDefaultNetmapContractConfigMap()
|
||||
if method == updateMethodName {
|
||||
if err := c.mergeNetmapConfig(md); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var configParam []any
|
||||
for k, v := range md {
|
||||
configParam = append(configParam, k, v)
|
||||
}
|
||||
|
||||
items = append(items,
|
||||
c.Contracts[balanceContract].Hash,
|
||||
c.Contracts[containerContract].Hash,
|
||||
keysParam,
|
||||
configParam)
|
||||
case proxyContract:
|
||||
items = nil
|
||||
case policyContract:
|
||||
items = append(items, c.Contracts[proxyContract].Hash)
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid contract name: %s", ctrName))
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (c *initializeContext) getFrostfsIDAdminFromContract() (util.Uint160, bool, error) {
|
||||
r := management.NewReader(c.ReadOnlyInvoker)
|
||||
cs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return util.Uint160{}, false, fmt.Errorf("get nns contract: %w", err)
|
||||
}
|
||||
fidHash, err := nnsResolveHash(c.ReadOnlyInvoker, cs.Hash, domainOf(frostfsIDContract))
|
||||
if err != nil {
|
||||
return util.Uint160{}, false, fmt.Errorf("resolve frostfsid contract hash: %w", err)
|
||||
}
|
||||
item, err := unwrap.Item(c.ReadOnlyInvoker.Call(fidHash, "getAdmin"))
|
||||
if err != nil {
|
||||
return util.Uint160{}, false, fmt.Errorf("getAdmin: %w", err)
|
||||
}
|
||||
if _, ok := item.(stackitem.Null); ok {
|
||||
return util.Uint160{}, false, nil
|
||||
}
|
||||
|
||||
bs, err := item.TryBytes()
|
||||
if err != nil {
|
||||
return util.Uint160{}, true, fmt.Errorf("getAdmin: decode result: %w", err)
|
||||
}
|
||||
h, err := util.Uint160DecodeBytesBE(bs)
|
||||
if err != nil {
|
||||
return util.Uint160{}, true, fmt.Errorf("getAdmin: decode result: %w", err)
|
||||
}
|
||||
return h, true, nil
|
||||
}
|
||||
|
||||
func (c *initializeContext) getNetConfigFromNetmapContract() ([]stackitem.Item, error) {
|
||||
r := management.NewReader(c.ReadOnlyInvoker)
|
||||
cs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get nns contract: %w", err)
|
||||
}
|
||||
nmHash, err := nnsResolveHash(c.ReadOnlyInvoker, cs.Hash, domainOf(netmapContract))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||
}
|
||||
arr, err := unwrap.Array(c.ReadOnlyInvoker.Call(nmHash, "listConfig"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't fetch list of network config keys from the netmap contract")
|
||||
}
|
||||
return arr, err
|
||||
}
|
||||
|
||||
func (c *initializeContext) mergeNetmapConfig(md map[string]any) error {
|
||||
arr, err := c.getNetConfigFromNetmapContract()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m, err := parseConfigFromNetmapContract(arr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range m {
|
||||
for _, key := range netmapConfigKeys {
|
||||
if k == key {
|
||||
md[k] = v
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *initializeContext) getAlphabetDeployItems(i, n int) []any {
|
||||
items := make([]any, 5)
|
||||
items[0] = c.Contracts[netmapContract].Hash
|
||||
items[1] = c.Contracts[proxyContract].Hash
|
||||
items[2] = innerring.GlagoliticLetter(i).String()
|
||||
items[3] = int64(i)
|
||||
items[4] = int64(n)
|
||||
return items
|
||||
}
|
|
@ -1,305 +0,0 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||
morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"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"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
nnsClient "github.com/nspcc-dev/neo-go/pkg/rpcclient/nns"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"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/vm/stackitem"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||
)
|
||||
|
||||
const defaultExpirationTime = 10 * 365 * 24 * time.Hour / time.Second
|
||||
|
||||
const frostfsOpsEmail = "ops@frostfs.info"
|
||||
|
||||
func (c *initializeContext) setNNS() error {
|
||||
r := management.NewReader(c.ReadOnlyInvoker)
|
||||
nnsCs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ok, err := c.nnsRootRegistered(nnsCs.Hash, "frostfs")
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
bw := io.NewBufBinWriter()
|
||||
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
|
||||
"frostfs", c.CommitteeAcc.Contract.ScriptHash(),
|
||||
frostfsOpsEmail, int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
if err := c.sendCommitteeTx(bw.Bytes(), true); err != nil {
|
||||
return fmt.Errorf("can't add domain root to NNS: %w", err)
|
||||
}
|
||||
if err := c.awaitTx(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
alphaCs := c.getContract(alphabetContract)
|
||||
for i, acc := range c.Accounts {
|
||||
alphaCs.Hash = state.CreateContractHash(acc.Contract.ScriptHash(), alphaCs.NEF.Checksum, alphaCs.Manifest.Name)
|
||||
|
||||
domain := getAlphabetNNSDomain(i)
|
||||
if err := c.nnsRegisterDomain(nnsCs.Hash, alphaCs.Hash, domain); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Command.Printf("NNS: Set %s -> %s\n", domain, alphaCs.Hash.StringLE())
|
||||
}
|
||||
|
||||
for _, ctrName := range contractList {
|
||||
cs := c.getContract(ctrName)
|
||||
|
||||
domain := ctrName + ".frostfs"
|
||||
if err := c.nnsRegisterDomain(nnsCs.Hash, cs.Hash, domain); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Command.Printf("NNS: Set %s -> %s\n", domain, cs.Hash.StringLE())
|
||||
}
|
||||
|
||||
groupKey := c.ContractWallet.Accounts[0].PrivateKey().PublicKey()
|
||||
err = c.updateNNSGroup(nnsCs.Hash, groupKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Command.Printf("NNS: Set %s -> %s\n", morphClient.NNSGroupKeyName, hex.EncodeToString(groupKey.Bytes()))
|
||||
|
||||
return c.awaitTx()
|
||||
}
|
||||
|
||||
func (c *initializeContext) updateNNSGroup(nnsHash util.Uint160, pub *keys.PublicKey) error {
|
||||
bw := io.NewBufBinWriter()
|
||||
keyAlreadyAdded, domainRegCodeEmitted, err := c.emitUpdateNNSGroupScript(bw, nnsHash, pub)
|
||||
if keyAlreadyAdded || err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
script := bw.Bytes()
|
||||
if domainRegCodeEmitted {
|
||||
w := io.NewBufBinWriter()
|
||||
emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1})
|
||||
wrapRegisterScriptWithPrice(w, nnsHash, script)
|
||||
script = w.Bytes()
|
||||
}
|
||||
|
||||
return c.sendCommitteeTx(script, true)
|
||||
}
|
||||
|
||||
// emitUpdateNNSGroupScript emits script for updating group key stored in NNS.
|
||||
// First return value is true iff the key is already there and nothing should be done.
|
||||
// Second return value is true iff a domain registration code was emitted.
|
||||
func (c *initializeContext) emitUpdateNNSGroupScript(bw *io.BufBinWriter, nnsHash util.Uint160, pub *keys.PublicKey) (bool, bool, error) {
|
||||
isAvail, err := nnsIsAvailable(c.Client, nnsHash, morphClient.NNSGroupKeyName)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
if !isAvail {
|
||||
currentPub, err := nnsResolveKey(c.ReadOnlyInvoker, nnsHash, morphClient.NNSGroupKeyName)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
if pub.Equal(currentPub) {
|
||||
return true, false, nil
|
||||
}
|
||||
}
|
||||
|
||||
if isAvail {
|
||||
emit.AppCall(bw.BinWriter, nnsHash, "register", callflag.All,
|
||||
morphClient.NNSGroupKeyName, c.CommitteeAcc.Contract.ScriptHash(),
|
||||
frostfsOpsEmail, int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
}
|
||||
|
||||
emit.AppCall(bw.BinWriter, nnsHash, "deleteRecords", callflag.All, "group.frostfs", int64(nns.TXT))
|
||||
emit.AppCall(bw.BinWriter, nnsHash, "addRecord", callflag.All,
|
||||
"group.frostfs", int64(nns.TXT), hex.EncodeToString(pub.Bytes()))
|
||||
|
||||
return false, isAvail, nil
|
||||
}
|
||||
|
||||
func getAlphabetNNSDomain(i int) string {
|
||||
return alphabetContract + strconv.FormatUint(uint64(i), 10) + ".frostfs"
|
||||
}
|
||||
|
||||
// wrapRegisterScriptWithPrice wraps a given script with `getPrice`/`setPrice` calls for NNS.
|
||||
// It is intended to be used for a single transaction, and not as a part of other scripts.
|
||||
// It is assumed that script already contains static slot initialization code, the first one
|
||||
// (with index 0) is used to store the price.
|
||||
func wrapRegisterScriptWithPrice(w *io.BufBinWriter, nnsHash util.Uint160, s []byte) {
|
||||
if len(s) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
emit.AppCall(w.BinWriter, nnsHash, "getPrice", callflag.All)
|
||||
emit.Opcodes(w.BinWriter, opcode.STSFLD0)
|
||||
emit.AppCall(w.BinWriter, nnsHash, "setPrice", callflag.All, 1)
|
||||
|
||||
w.WriteBytes(s)
|
||||
|
||||
emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.PUSH1, opcode.PACK)
|
||||
emit.AppCallNoArgs(w.BinWriter, nnsHash, "setPrice", callflag.All)
|
||||
|
||||
if w.Err != nil {
|
||||
panic(fmt.Errorf("BUG: can't wrap register script: %w", w.Err))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *initializeContext) nnsRegisterDomainScript(nnsHash, expectedHash util.Uint160, domain string) ([]byte, bool, error) {
|
||||
ok, err := nnsIsAvailable(c.Client, nnsHash, domain)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if ok {
|
||||
bw := io.NewBufBinWriter()
|
||||
emit.AppCall(bw.BinWriter, nnsHash, "register", callflag.All,
|
||||
domain, c.CommitteeAcc.Contract.ScriptHash(),
|
||||
frostfsOpsEmail, int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
|
||||
if bw.Err != nil {
|
||||
panic(bw.Err)
|
||||
}
|
||||
return bw.Bytes(), false, nil
|
||||
}
|
||||
|
||||
s, err := nnsResolveHash(c.ReadOnlyInvoker, nnsHash, domain)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return nil, s == expectedHash, nil
|
||||
}
|
||||
|
||||
func (c *initializeContext) nnsRegisterDomain(nnsHash, expectedHash util.Uint160, domain string) error {
|
||||
script, ok, err := c.nnsRegisterDomainScript(nnsHash, expectedHash, domain)
|
||||
if ok || err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w := io.NewBufBinWriter()
|
||||
emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1})
|
||||
wrapRegisterScriptWithPrice(w, nnsHash, script)
|
||||
|
||||
emit.AppCall(w.BinWriter, nnsHash, "deleteRecords", callflag.All, domain, int64(nns.TXT))
|
||||
emit.AppCall(w.BinWriter, nnsHash, "addRecord", callflag.All,
|
||||
domain, int64(nns.TXT), expectedHash.StringLE())
|
||||
emit.AppCall(w.BinWriter, nnsHash, "addRecord", callflag.All,
|
||||
domain, int64(nns.TXT), address.Uint160ToString(expectedHash))
|
||||
return c.sendCommitteeTx(w.Bytes(), true)
|
||||
}
|
||||
|
||||
func (c *initializeContext) nnsRootRegistered(nnsHash util.Uint160, zone string) (bool, error) {
|
||||
res, err := c.CommitteeAct.Call(nnsHash, "isAvailable", "name."+zone)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return res.State == vmstate.Halt.String(), nil
|
||||
}
|
||||
|
||||
var errMissingNNSRecord = errors.New("missing NNS record")
|
||||
|
||||
// Returns errMissingNNSRecord if invocation fault exception contains "token not found".
|
||||
func nnsResolveHash(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (util.Uint160, error) {
|
||||
item, err := nnsResolve(inv, nnsHash, domain)
|
||||
if err != nil {
|
||||
return util.Uint160{}, err
|
||||
}
|
||||
return parseNNSResolveResult(item)
|
||||
}
|
||||
|
||||
func nnsResolve(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (stackitem.Item, error) {
|
||||
return unwrap.Item(inv.Call(nnsHash, "resolve", domain, int64(nns.TXT)))
|
||||
}
|
||||
|
||||
func nnsResolveKey(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (*keys.PublicKey, error) {
|
||||
res, err := nnsResolve(inv, nnsHash, domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := res.Value().(stackitem.Null); ok {
|
||||
return nil, errors.New("NNS record is missing")
|
||||
}
|
||||
arr, ok := res.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
return nil, errors.New("API of the NNS contract method `resolve` has changed")
|
||||
}
|
||||
for i := range arr {
|
||||
var bs []byte
|
||||
bs, err = arr[i].TryBytes()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
return keys.NewPublicKeyFromString(string(bs))
|
||||
}
|
||||
return nil, errors.New("no valid keys are found")
|
||||
}
|
||||
|
||||
// parseNNSResolveResult parses the result of resolving NNS record.
|
||||
// It works with multiple formats (corresponding to multiple NNS versions).
|
||||
// If array of hashes is provided, it returns only the first one.
|
||||
func parseNNSResolveResult(res stackitem.Item) (util.Uint160, error) {
|
||||
arr, ok := res.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
arr = []stackitem.Item{res}
|
||||
}
|
||||
if _, ok := res.Value().(stackitem.Null); ok || len(arr) == 0 {
|
||||
return util.Uint160{}, errors.New("NNS record is missing")
|
||||
}
|
||||
for i := range arr {
|
||||
bs, err := arr[i].TryBytes()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// We support several formats for hash encoding, this logic should be maintained in sync
|
||||
// with nnsResolve from pkg/morph/client/nns.go
|
||||
h, err := util.Uint160DecodeStringLE(string(bs))
|
||||
if err == nil {
|
||||
return h, nil
|
||||
}
|
||||
|
||||
h, err = address.StringToUint160(string(bs))
|
||||
if err == nil {
|
||||
return h, nil
|
||||
}
|
||||
}
|
||||
return util.Uint160{}, errors.New("no valid hashes are found")
|
||||
}
|
||||
|
||||
func nnsIsAvailable(c Client, nnsHash util.Uint160, name string) (bool, error) {
|
||||
switch c.(type) {
|
||||
case *rpcclient.Client:
|
||||
inv := invoker.New(c, nil)
|
||||
reader := nnsClient.NewReader(inv, nnsHash)
|
||||
return reader.IsAvailable(name)
|
||||
default:
|
||||
b, err := unwrap.Bool(invokeFunction(c, nnsHash, "isAvailable", []any{name}, nil))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("`isAvailable`: invalid response: %w", err)
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
}
|
45
cmd/frostfs-adm/internal/modules/morph/netmap/epoch.go
Normal file
45
cmd/frostfs-adm/internal/modules/morph/netmap/epoch.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package netmap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
fyrchik marked this conversation as resolved
Outdated
fyrchik
commented
Why use alias? Why use alias?
acid-ant
commented
Thanks, removed. Thanks, removed.
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func ForceNewEpochCmd(cmd *cobra.Command, _ []string) error {
|
||||
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't to initialize context: %w", err)
|
||||
}
|
||||
|
||||
r := management.NewReader(wCtx.ReadOnlyInvoker)
|
||||
cs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||
}
|
||||
|
||||
nmHash, err := helper.NNSResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, helper.DomainOf(constants.NetmapContract))
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||
}
|
||||
|
||||
bw := io.NewBufBinWriter()
|
||||
if err := helper.EmitNewEpochCall(bw, wCtx, nmHash); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = wCtx.SendConsensusTx(bw.Bytes()); err == nil {
|
||||
err = wCtx.AwaitTx()
|
||||
}
|
||||
if err != nil && strings.Contains(err.Error(), "invalid epoch") {
|
||||
cmd.Println("Epoch has already ticked.")
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
package morph
|
||||
package netmap
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
|
@ -11,7 +13,7 @@ import (
|
|||
)
|
||||
|
||||
func listNetmapCandidatesNodes(cmd *cobra.Command, _ []string) {
|
||||
c, err := getN3Client(viper.GetViper())
|
||||
c, err := helper.GetN3Client(viper.GetViper())
|
||||
commonCmd.ExitOnErr(cmd, "can't create N3 client: %w", err)
|
||||
|
||||
inv := invoker.New(c, nil)
|
||||
|
@ -20,7 +22,7 @@ func listNetmapCandidatesNodes(cmd *cobra.Command, _ []string) {
|
|||
cs, err := r.GetContractByID(1)
|
||||
commonCmd.ExitOnErr(cmd, "can't get NNS contract info: %w", err)
|
||||
|
||||
nmHash, err := nnsResolveHash(inv, cs.Hash, domainOf(netmapContract))
|
||||
nmHash, err := helper.NNSResolveHash(inv, cs.Hash, helper.DomainOf(constants.NetmapContract))
|
||||
commonCmd.ExitOnErr(cmd, "can't get netmap contract hash: %w", err)
|
||||
|
||||
res, err := inv.Call(nmHash, "netmapCandidates")
|
43
cmd/frostfs-adm/internal/modules/morph/netmap/root.go
Normal file
43
cmd/frostfs-adm/internal/modules/morph/netmap/root.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package netmap
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
CandidatesCmd = &cobra.Command{
|
||||
Use: "netmap-candidates",
|
||||
Short: "List netmap candidates nodes",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
fyrchik marked this conversation as resolved
Outdated
fyrchik
commented
Again, can we move flag definitions to sth like Again, can we move flag definitions to sth like `commonflags` package like we have for CLI?
acid-ant
commented
Moved flags to package Moved flags to package `adm\internal\commonflags`, `const` to package `constants`. Also renamed `util` to `helper` to avoid conflicts with other imports.
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
},
|
||||
Run: listNetmapCandidatesNodes,
|
||||
}
|
||||
ForceNewEpoch = &cobra.Command{
|
||||
Use: "force-new-epoch",
|
||||
Short: "Create new FrostFS epoch event in the side chain",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: ForceNewEpochCmd,
|
||||
}
|
||||
)
|
||||
|
||||
func initNetmapCandidatesCmd() {
|
||||
CandidatesCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
}
|
||||
|
||||
func initForceNewEpochCmd() {
|
||||
ForceNewEpoch.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
ForceNewEpoch.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
ForceNewEpoch.Flags().String(commonflags.LocalDumpFlag, "", "Path to the blocks dump file")
|
||||
}
|
||||
|
||||
func init() {
|
||||
initNetmapCandidatesCmd()
|
||||
initForceNewEpochCmd()
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func getDefaultNetmapContractConfigMap() map[string]any {
|
||||
m := make(map[string]any)
|
||||
m[netmap.EpochDurationConfig] = viper.GetInt64(epochDurationInitFlag)
|
||||
m[netmap.MaxObjectSizeConfig] = viper.GetInt64(maxObjectSizeInitFlag)
|
||||
m[netmap.ContainerFeeConfig] = viper.GetInt64(containerFeeInitFlag)
|
||||
m[netmap.ContainerAliasFeeConfig] = viper.GetInt64(containerAliasFeeInitFlag)
|
||||
m[netmap.IrCandidateFeeConfig] = viper.GetInt64(candidateFeeInitFlag)
|
||||
m[netmap.WithdrawFeeConfig] = viper.GetInt64(withdrawFeeInitFlag)
|
||||
m[netmap.HomomorphicHashingDisabledKey] = viper.GetBool(homomorphicHashDisabledInitFlag)
|
||||
m[netmap.MaintenanceModeAllowedConfig] = viper.GetBool(maintenanceModeAllowedInitFlag)
|
||||
return m
|
||||
}
|
||||
|
||||
func parseConfigFromNetmapContract(arr []stackitem.Item) (map[string][]byte, error) {
|
||||
m := make(map[string][]byte, len(arr))
|
||||
for _, param := range arr {
|
||||
tuple, ok := param.Value().([]stackitem.Item)
|
||||
if !ok || len(tuple) != 2 {
|
||||
return nil, errors.New("invalid ListConfig response from netmap contract")
|
||||
}
|
||||
|
||||
k, err := tuple[0].TryBytes()
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid config key from netmap contract")
|
||||
}
|
||||
|
||||
v, err := tuple[1].TryBytes()
|
||||
if err != nil {
|
||||
return nil, invalidConfigValueErr(string(k))
|
||||
}
|
||||
m[string(k)] = v
|
||||
}
|
||||
return m, nil
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
package morph
|
||||
package node
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
netmapcontract "git.frostfs.info/TrueCloudLab/frostfs-contract/netmap"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
|
@ -14,7 +16,7 @@ import (
|
|||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func removeNodesCmd(cmd *cobra.Command, args []string) error {
|
||||
func RemoveNodesCmd(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("at least one node key must be provided")
|
||||
}
|
||||
|
@ -28,11 +30,11 @@ func removeNodesCmd(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
}
|
||||
|
||||
wCtx, err := newInitializeContext(cmd, viper.GetViper())
|
||||
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't initialize context: %w", err)
|
||||
}
|
||||
defer wCtx.close()
|
||||
defer wCtx.Close()
|
||||
|
||||
r := management.NewReader(wCtx.ReadOnlyInvoker)
|
||||
cs, err := r.GetContractByID(1)
|
||||
|
@ -40,7 +42,7 @@ func removeNodesCmd(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||
}
|
||||
|
||||
nmHash, err := nnsResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, domainOf(netmapContract))
|
||||
nmHash, err := helper.NNSResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, helper.DomainOf(constants.NetmapContract))
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||
}
|
||||
|
@ -51,13 +53,13 @@ func removeNodesCmd(cmd *cobra.Command, args []string) error {
|
|||
int64(netmapcontract.NodeStateOffline), nodeKeys[i].Bytes())
|
||||
}
|
||||
|
||||
if err := emitNewEpochCall(bw, wCtx, nmHash); err != nil {
|
||||
if err := helper.EmitNewEpochCall(bw, wCtx, nmHash); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := wCtx.sendConsensusTx(bw.Bytes()); err != nil {
|
||||
if err := wCtx.SendConsensusTx(bw.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return wCtx.awaitTx()
|
||||
return wCtx.AwaitTx()
|
||||
}
|
28
cmd/frostfs-adm/internal/modules/morph/node/root.go
Normal file
28
cmd/frostfs-adm/internal/modules/morph/node/root.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package node
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var RemoveCmd = &cobra.Command{
|
||||
Use: "remove-nodes key1 [key2 [...]]",
|
||||
Short: "Remove storage nodes from the netmap",
|
||||
Long: `Move nodes to the Offline state in the candidates list and tick an epoch to update the netmap`,
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: RemoveNodesCmd,
|
||||
}
|
||||
|
||||
func initRemoveNodesCmd() {
|
||||
RemoveCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
RemoveCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
RemoveCmd.Flags().String(commonflags.LocalDumpFlag, "", "Path to the blocks dump file")
|
||||
}
|
||||
|
||||
func init() {
|
||||
initRemoveNodesCmd()
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
package morph
|
||||
package notary
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"github.com/nspcc-dev/neo-go/cli/input"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
|
@ -20,9 +22,14 @@ import (
|
|||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// defaultNotaryDepositLifetime is an amount of blocks notary deposit stays valid.
|
||||
// https://github.com/nspcc-dev/neo-go/blob/master/pkg/core/native/notary.go#L48
|
||||
const defaultNotaryDepositLifetime = 5760
|
||||
const (
|
||||
// defaultNotaryDepositLifetime is an amount of blocks notary deposit stays valid.
|
||||
// https://github.com/nspcc-dev/neo-go/blob/master/pkg/core/native/notary.go#L48
|
||||
defaultNotaryDepositLifetime = 5760
|
||||
|
||||
walletAccountFlag = "account"
|
||||
notaryDepositTillFlag = "till"
|
||||
)
|
||||
|
||||
func depositNotary(cmd *cobra.Command, _ []string) error {
|
||||
w, err := openWallet(cmd)
|
||||
|
@ -54,11 +61,11 @@ func depositNotary(cmd *cobra.Command, _ []string) error {
|
|||
return fmt.Errorf("can't unlock account: %v", err)
|
||||
}
|
||||
|
||||
gasStr, err := cmd.Flags().GetString(refillGasAmountFlag)
|
||||
gasStr, err := cmd.Flags().GetString(commonflags.RefillGasAmountFlag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gasAmount, err := parseGASAmount(gasStr)
|
||||
gasAmount, err := helper.ParseGASAmount(gasStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -79,12 +86,12 @@ func depositNotary(cmd *cobra.Command, _ []string) error {
|
|||
}
|
||||
|
||||
func transferGas(cmd *cobra.Command, acc *wallet.Account, accHash util.Uint160, gasAmount fixedn.Fixed8, till int64) error {
|
||||
c, err := getN3Client(viper.GetViper())
|
||||
c, err := helper.GetN3Client(viper.GetViper())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkNotaryEnabled(c); err != nil {
|
||||
if err := helper.CheckNotaryEnabled(c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -116,15 +123,15 @@ func transferGas(cmd *cobra.Command, acc *wallet.Account, accHash util.Uint160,
|
|||
return fmt.Errorf("could not send tx: %w", err)
|
||||
}
|
||||
|
||||
return awaitTx(cmd, c, []hashVUBPair{{hash: txHash, vub: vub}})
|
||||
return helper.AwaitTx(cmd, c, []helper.HashVUBPair{{Hash: txHash, Vub: vub}})
|
||||
}
|
||||
|
||||
func openWallet(cmd *cobra.Command) (*wallet.Wallet, error) {
|
||||
p, err := cmd.Flags().GetString(storageWalletFlag)
|
||||
p, err := cmd.Flags().GetString(commonflags.StorageWalletFlag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if p == "" {
|
||||
return nil, fmt.Errorf("missing wallet path (use '--%s <out.json>')", storageWalletFlag)
|
||||
return nil, fmt.Errorf("missing wallet path (use '--%s <out.json>')", commonflags.StorageWalletFlag)
|
||||
}
|
||||
|
||||
w, err := wallet.NewWalletFromFile(p)
|
28
cmd/frostfs-adm/internal/modules/morph/notary/root.go
Normal file
28
cmd/frostfs-adm/internal/modules/morph/notary/root.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package notary
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var DepositCmd = &cobra.Command{
|
||||
Use: "deposit-notary",
|
||||
Short: "Deposit GAS for notary service",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: depositNotary,
|
||||
}
|
||||
|
||||
func initDepositoryNotaryCmd() {
|
||||
DepositCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
DepositCmd.Flags().String(commonflags.StorageWalletFlag, "", "Path to storage node wallet")
|
||||
DepositCmd.Flags().String(walletAccountFlag, "", "Wallet account address")
|
||||
DepositCmd.Flags().String(commonflags.RefillGasAmountFlag, "", "Amount of GAS to deposit")
|
||||
DepositCmd.Flags().String(notaryDepositTillFlag, "", "Notary deposit duration in blocks")
|
||||
}
|
||||
|
||||
func init() {
|
||||
initDepositoryNotaryCmd()
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package morph
|
||||
package policy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -7,6 +7,7 @@ import (
|
|||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
|
@ -23,8 +24,8 @@ const (
|
|||
setFeeParam = "FeePerByte"
|
||||
)
|
||||
|
||||
func setPolicyCmd(cmd *cobra.Command, args []string) error {
|
||||
wCtx, err := newInitializeContext(cmd, viper.GetViper())
|
||||
func SetPolicyCmd(cmd *cobra.Command, args []string) error {
|
||||
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't to initialize context: %w", err)
|
||||
}
|
||||
|
@ -50,15 +51,15 @@ func setPolicyCmd(cmd *cobra.Command, args []string) error {
|
|||
emit.AppCall(bw.BinWriter, policy.Hash, "set"+k, callflag.All, int64(value))
|
||||
}
|
||||
|
||||
if err := wCtx.sendCommitteeTx(bw.Bytes(), false); err != nil {
|
||||
if err := wCtx.SendCommitteeTx(bw.Bytes(), false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return wCtx.awaitTx()
|
||||
return wCtx.AwaitTx()
|
||||
}
|
||||
|
||||
func dumpPolicyCmd(cmd *cobra.Command, _ []string) error {
|
||||
c, err := getN3Client(viper.GetViper())
|
||||
c, err := helper.GetN3Client(viper.GetViper())
|
||||
commonCmd.ExitOnErr(cmd, "can't create N3 client:", err)
|
||||
|
||||
inv := invoker.New(c, nil)
|
47
cmd/frostfs-adm/internal/modules/morph/policy/root.go
Normal file
47
cmd/frostfs-adm/internal/modules/morph/policy/root.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package policy
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
Set = &cobra.Command{
|
||||
Use: "set-policy [ExecFeeFactor=<n1>] [StoragePrice=<n2>] [FeePerByte=<n3>]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: "Set global policy values",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: SetPolicyCmd,
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return []string{"ExecFeeFactor=", "StoragePrice=", "FeePerByte="}, cobra.ShellCompDirectiveNoSpace
|
||||
},
|
||||
}
|
||||
|
||||
Dump = &cobra.Command{
|
||||
Use: "dump-policy",
|
||||
Short: "Dump FrostFS policy",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: dumpPolicyCmd,
|
||||
}
|
||||
)
|
||||
|
||||
func initSetPolicyCmd() {
|
||||
Set.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
Set.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
Set.Flags().String(commonflags.LocalDumpFlag, "", "Path to the blocks dump file")
|
||||
}
|
||||
|
||||
func initDumpPolicyCmd() {
|
||||
Dump.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
}
|
||||
|
||||
func init() {
|
||||
initSetPolicyCmd()
|
||||
initDumpPolicyCmd()
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
package morph
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
|
@ -35,7 +37,7 @@ func removeProxyAccount(cmd *cobra.Command, _ []string) {
|
|||
}
|
||||
|
||||
func processAccount(cmd *cobra.Command, addr util.Uint160, method string) error {
|
||||
wCtx, err := newInitializeContext(cmd, viper.GetViper())
|
||||
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't to initialize context: %w", err)
|
||||
}
|
||||
|
@ -46,7 +48,7 @@ func processAccount(cmd *cobra.Command, addr util.Uint160, method string) error
|
|||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||
}
|
||||
|
||||
proxyHash, err := nnsResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, domainOf(proxyContract))
|
||||
proxyHash, err := helper.NNSResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, helper.DomainOf(constants.ProxyContract))
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get proxy contract hash: %w", err)
|
||||
}
|
||||
|
@ -54,11 +56,11 @@ func processAccount(cmd *cobra.Command, addr util.Uint160, method string) error
|
|||
bw := io.NewBufBinWriter()
|
||||
emit.AppCall(bw.BinWriter, proxyHash, method, callflag.All, addr)
|
||||
|
||||
if err := wCtx.sendConsensusTx(bw.Bytes()); err != nil {
|
||||
if err := wCtx.SendConsensusTx(bw.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = wCtx.awaitTx(); err != nil {
|
||||
if err = wCtx.AwaitTx(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
43
cmd/frostfs-adm/internal/modules/morph/proxy/root.go
Normal file
43
cmd/frostfs-adm/internal/modules/morph/proxy/root.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package proxy
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
AddAccountCmd = &cobra.Command{
|
||||
Use: "proxy-add-account",
|
||||
Short: "Adds account to proxy contract",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: addProxyAccount,
|
||||
}
|
||||
RemoveAccountCmd = &cobra.Command{
|
||||
Use: "proxy-remove-account",
|
||||
Short: "Remove from proxy contract",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: removeProxyAccount,
|
||||
}
|
||||
)
|
||||
|
||||
func initProxyAddAccount() {
|
||||
AddAccountCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
AddAccountCmd.Flags().String(accountAddressFlag, "", "Wallet address string")
|
||||
}
|
||||
|
||||
func initProxyRemoveAccount() {
|
||||
RemoveAccountCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
RemoveAccountCmd.Flags().String(accountAddressFlag, "", "Wallet address string")
|
||||
}
|
||||
|
||||
func init() {
|
||||
initProxyAddAccount()
|
||||
initProxyRemoveAccount()
|
||||
}
|
|
@ -1,469 +1,52 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/ape"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/balance"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/container"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/contract"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/frostfsid"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/generate"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/initialize"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/netmap"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/node"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/notary"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/policy"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/proxy"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
alphabetWalletsFlag = "alphabet-wallets"
|
||||
alphabetWalletsFlagDesc = "Path to alphabet wallets dir"
|
||||
alphabetSizeFlag = "size"
|
||||
endpointFlag = "rpc-endpoint"
|
||||
endpointFlagDesc = "N3 RPC node endpoint"
|
||||
endpointFlagShort = "r"
|
||||
storageWalletFlag = "storage-wallet"
|
||||
storageWalletLabelFlag = "label"
|
||||
storageGasCLIFlag = "initial-gas"
|
||||
storageGasConfigFlag = "storage.initial_gas"
|
||||
contractsInitFlag = "contracts"
|
||||
contractsInitFlagDesc = "Path to archive with compiled FrostFS contracts (the default is to fetch the latest release from the official repository)"
|
||||
contractsURLFlag = "contracts-url"
|
||||
contractsURLFlagDesc = "URL to archive with compiled FrostFS contracts"
|
||||
maxObjectSizeInitFlag = "network.max_object_size"
|
||||
maxObjectSizeCLIFlag = "max-object-size"
|
||||
epochDurationInitFlag = "network.epoch_duration"
|
||||
epochDurationCLIFlag = "epoch-duration"
|
||||
containerFeeInitFlag = "network.fee.container"
|
||||
containerAliasFeeInitFlag = "network.fee.container_alias"
|
||||
containerFeeCLIFlag = "container-fee"
|
||||
containerAliasFeeCLIFlag = "container-alias-fee"
|
||||
candidateFeeInitFlag = "network.fee.candidate"
|
||||
candidateFeeCLIFlag = "candidate-fee"
|
||||
homomorphicHashDisabledInitFlag = "network.homomorphic_hash_disabled"
|
||||
maintenanceModeAllowedInitFlag = "network.maintenance_mode_allowed"
|
||||
homomorphicHashDisabledCLIFlag = "homomorphic-disabled"
|
||||
withdrawFeeInitFlag = "network.fee.withdraw"
|
||||
withdrawFeeCLIFlag = "withdraw-fee"
|
||||
containerDumpFlag = "dump"
|
||||
containerContractFlag = "container-contract"
|
||||
containerIDsFlag = "cid"
|
||||
refillGasAmountFlag = "gas"
|
||||
walletAccountFlag = "account"
|
||||
notaryDepositTillFlag = "till"
|
||||
localDumpFlag = "local-dump"
|
||||
protoConfigPath = "protocol"
|
||||
walletAddressFlag = "wallet-address"
|
||||
)
|
||||
|
||||
var (
|
||||
// RootCmd is a root command of config section.
|
||||
RootCmd = &cobra.Command{
|
||||
Use: "morph",
|
||||
Short: "Section for morph network configuration commands",
|
||||
}
|
||||
|
||||
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(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
},
|
||||
RunE: generateAlphabetCreds,
|
||||
}
|
||||
|
||||
initCmd = &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Initialize side chain network with smart-contracts and network settings",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
_ = viper.BindPFlag(epochDurationInitFlag, cmd.Flags().Lookup(epochDurationCLIFlag))
|
||||
_ = viper.BindPFlag(maxObjectSizeInitFlag, cmd.Flags().Lookup(maxObjectSizeCLIFlag))
|
||||
_ = viper.BindPFlag(homomorphicHashDisabledInitFlag, cmd.Flags().Lookup(homomorphicHashDisabledCLIFlag))
|
||||
_ = viper.BindPFlag(candidateFeeInitFlag, cmd.Flags().Lookup(candidateFeeCLIFlag))
|
||||
_ = viper.BindPFlag(containerFeeInitFlag, cmd.Flags().Lookup(containerFeeCLIFlag))
|
||||
_ = viper.BindPFlag(containerAliasFeeInitFlag, cmd.Flags().Lookup(containerAliasFeeCLIFlag))
|
||||
_ = viper.BindPFlag(withdrawFeeInitFlag, cmd.Flags().Lookup(withdrawFeeCLIFlag))
|
||||
_ = viper.BindPFlag(protoConfigPath, cmd.Flags().Lookup(protoConfigPath))
|
||||
},
|
||||
RunE: initializeSideChainCmd,
|
||||
}
|
||||
|
||||
generateStorageCmd = &cobra.Command{
|
||||
Use: "generate-storage-wallet",
|
||||
Short: "Generate storage node wallet for the morph network",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(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(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
_ = viper.BindPFlag(refillGasAmountFlag, cmd.Flags().Lookup(refillGasAmountFlag))
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return refillGas(cmd, refillGasAmountFlag, false)
|
||||
},
|
||||
}
|
||||
|
||||
forceNewEpoch = &cobra.Command{
|
||||
Use: "force-new-epoch",
|
||||
Short: "Create new FrostFS epoch event in the side chain",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
},
|
||||
RunE: forceNewEpochCmd,
|
||||
}
|
||||
|
||||
removeNodes = &cobra.Command{
|
||||
Use: "remove-nodes key1 [key2 [...]]",
|
||||
Short: "Remove storage nodes from the netmap",
|
||||
Long: `Move nodes to the Offline state in the candidates list and tick an epoch to update the netmap`,
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
},
|
||||
RunE: removeNodesCmd,
|
||||
}
|
||||
|
||||
setConfig = &cobra.Command{
|
||||
Use: "set-config key1=val1 [key2=val2 ...]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: "Add/update global config value in the FrostFS network",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
},
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: setConfigCmd,
|
||||
}
|
||||
|
||||
setPolicy = &cobra.Command{
|
||||
Use: "set-policy [ExecFeeFactor=<n1>] [StoragePrice=<n2>] [FeePerByte=<n3>]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: "Set global policy values",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
},
|
||||
RunE: setPolicyCmd,
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return []string{"ExecFeeFactor=", "StoragePrice=", "FeePerByte="}, cobra.ShellCompDirectiveNoSpace
|
||||
},
|
||||
}
|
||||
|
||||
dumpPolicy = &cobra.Command{
|
||||
Use: "dump-policy",
|
||||
Short: "Dump FrostFS policy",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
},
|
||||
RunE: dumpPolicyCmd,
|
||||
}
|
||||
|
||||
dumpContractHashesCmd = &cobra.Command{
|
||||
Use: "dump-hashes",
|
||||
Short: "Dump deployed contract hashes",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
},
|
||||
RunE: dumpContractHashes,
|
||||
}
|
||||
|
||||
dumpNetworkConfigCmd = &cobra.Command{
|
||||
Use: "dump-config",
|
||||
Short: "Dump FrostFS network config",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
},
|
||||
RunE: dumpNetworkConfig,
|
||||
}
|
||||
|
||||
dumpBalancesCmd = &cobra.Command{
|
||||
Use: "dump-balances",
|
||||
Short: "Dump GAS balances",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
},
|
||||
RunE: dumpBalances,
|
||||
}
|
||||
|
||||
updateContractsCmd = &cobra.Command{
|
||||
Use: "update-contracts",
|
||||
Short: "Update FrostFS contracts",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
},
|
||||
RunE: updateContracts,
|
||||
}
|
||||
|
||||
dumpContainersCmd = &cobra.Command{
|
||||
Use: "dump-containers",
|
||||
Short: "Dump FrostFS containers to file",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
},
|
||||
RunE: dumpContainers,
|
||||
}
|
||||
|
||||
restoreContainersCmd = &cobra.Command{
|
||||
Use: "restore-containers",
|
||||
Short: "Restore FrostFS containers from file",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
},
|
||||
RunE: restoreContainers,
|
||||
}
|
||||
|
||||
listContainersCmd = &cobra.Command{
|
||||
Use: "list-containers",
|
||||
Short: "List FrostFS containers",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
},
|
||||
RunE: listContainers,
|
||||
}
|
||||
|
||||
depositNotaryCmd = &cobra.Command{
|
||||
Use: "deposit-notary",
|
||||
Short: "Deposit GAS for notary service",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
},
|
||||
RunE: depositNotary,
|
||||
}
|
||||
|
||||
netmapCandidatesCmd = &cobra.Command{
|
||||
Use: "netmap-candidates",
|
||||
Short: "List netmap candidates nodes",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
},
|
||||
Run: listNetmapCandidatesNodes,
|
||||
}
|
||||
|
||||
proxyAddAccountCmd = &cobra.Command{
|
||||
Use: "proxy-add-account",
|
||||
Short: "Adds account to proxy contract",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
},
|
||||
Run: addProxyAccount,
|
||||
}
|
||||
|
||||
proxyRemoveAccountCmd = &cobra.Command{
|
||||
Use: "proxy-remove-account",
|
||||
Short: "Remove from proxy contract",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
},
|
||||
Run: removeProxyAccount,
|
||||
}
|
||||
)
|
||||
// RootCmd is a root command of config section.
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "morph",
|
||||
Short: "Section for morph network configuration commands",
|
||||
}
|
||||
|
||||
func init() {
|
||||
initGenerateAlphabetCmd()
|
||||
initInitCmd()
|
||||
initDeployCmd()
|
||||
initGenerateStorageCmd()
|
||||
initForceNewEpochCmd()
|
||||
initRemoveNodesCmd()
|
||||
initSetPolicyCmd()
|
||||
initDumpPolicyCmd()
|
||||
initDumpContractHashesCmd()
|
||||
initDumpNetworkConfigCmd()
|
||||
initSetConfigCmd()
|
||||
initDumpBalancesCmd()
|
||||
initUpdateContractsCmd()
|
||||
initDumpContainersCmd()
|
||||
initRestoreContainersCmd()
|
||||
initListContainersCmd()
|
||||
initRefillGasCmd()
|
||||
initDepositoryNotaryCmd()
|
||||
initNetmapCandidatesCmd()
|
||||
RootCmd.AddCommand(generate.RefillGasCmd)
|
||||
RootCmd.AddCommand(initialize.Cmd)
|
||||
RootCmd.AddCommand(contract.DeployCmd)
|
||||
RootCmd.AddCommand(generate.GenerateStorageCmd)
|
||||
RootCmd.AddCommand(netmap.ForceNewEpoch)
|
||||
RootCmd.AddCommand(node.RemoveCmd)
|
||||
RootCmd.AddCommand(policy.Set)
|
||||
RootCmd.AddCommand(policy.Dump)
|
||||
RootCmd.AddCommand(contract.DumpHashesCmd)
|
||||
RootCmd.AddCommand(config.SetCmd)
|
||||
RootCmd.AddCommand(config.DumpCmd)
|
||||
RootCmd.AddCommand(balance.DumpCmd)
|
||||
RootCmd.AddCommand(contract.UpdateCmd)
|
||||
RootCmd.AddCommand(container.ListCmd)
|
||||
RootCmd.AddCommand(container.RestoreCmd)
|
||||
RootCmd.AddCommand(container.DumpCmd)
|
||||
RootCmd.AddCommand(generate.GenerateAlphabetCmd)
|
||||
RootCmd.AddCommand(notary.DepositCmd)
|
||||
RootCmd.AddCommand(netmap.CandidatesCmd)
|
||||
|
||||
RootCmd.AddCommand(apeCmd)
|
||||
initAddRuleChainCmd()
|
||||
initRemoveRuleChainCmd()
|
||||
initListRuleChainsCmd()
|
||||
initSetAdminCmd()
|
||||
initGetAdminCmd()
|
||||
RootCmd.AddCommand(ape.Cmd)
|
||||
RootCmd.AddCommand(proxy.AddAccountCmd)
|
||||
RootCmd.AddCommand(proxy.RemoveAccountCmd)
|
||||
|
||||
initProxyAddAccount()
|
||||
initProxyRemoveAccount()
|
||||
|
||||
RootCmd.AddCommand(frostfsidCmd)
|
||||
initFrostfsIDCreateNamespaceCmd()
|
||||
initFrostfsIDListNamespacesCmd()
|
||||
initFrostfsIDCreateSubjectCmd()
|
||||
initFrostfsIDDeleteSubjectCmd()
|
||||
initFrostfsIDListSubjectsCmd()
|
||||
initFrostfsIDCreateGroupCmd()
|
||||
initFrostfsIDDeleteGroupCmd()
|
||||
initFrostfsIDListGroupsCmd()
|
||||
initFrostfsIDAddSubjectToGroupCmd()
|
||||
initFrostfsIDRemoveSubjectFromGroupCmd()
|
||||
initFrostfsIDListGroupSubjectsCmd()
|
||||
}
|
||||
|
||||
func initProxyAddAccount() {
|
||||
RootCmd.AddCommand(proxyAddAccountCmd)
|
||||
proxyAddAccountCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
proxyAddAccountCmd.Flags().String(accountAddressFlag, "", "Wallet address string")
|
||||
}
|
||||
|
||||
func initProxyRemoveAccount() {
|
||||
RootCmd.AddCommand(proxyRemoveAccountCmd)
|
||||
proxyRemoveAccountCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
proxyRemoveAccountCmd.Flags().String(accountAddressFlag, "", "Wallet address string")
|
||||
}
|
||||
|
||||
func initNetmapCandidatesCmd() {
|
||||
RootCmd.AddCommand(netmapCandidatesCmd)
|
||||
netmapCandidatesCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
}
|
||||
|
||||
func initDepositoryNotaryCmd() {
|
||||
RootCmd.AddCommand(depositNotaryCmd)
|
||||
depositNotaryCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
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 initRefillGasCmd() {
|
||||
RootCmd.AddCommand(refillGasCmd)
|
||||
refillGasCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
refillGasCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
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, endpointFlagShort, "", endpointFlagDesc)
|
||||
listContainersCmd.Flags().String(containerContractFlag, "", "Container contract hash (for networks without NNS)")
|
||||
}
|
||||
|
||||
func initRestoreContainersCmd() {
|
||||
RootCmd.AddCommand(restoreContainersCmd)
|
||||
restoreContainersCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
restoreContainersCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
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, endpointFlagShort, "", endpointFlagDesc)
|
||||
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, "", alphabetWalletsFlagDesc)
|
||||
updateContractsCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
updateContractsCmd.Flags().String(contractsInitFlag, "", contractsInitFlagDesc)
|
||||
updateContractsCmd.Flags().String(contractsURLFlag, "", contractsURLFlagDesc)
|
||||
updateContractsCmd.MarkFlagsMutuallyExclusive(contractsInitFlag, contractsURLFlag)
|
||||
}
|
||||
|
||||
func initDumpBalancesCmd() {
|
||||
RootCmd.AddCommand(dumpBalancesCmd)
|
||||
dumpBalancesCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
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, "", alphabetWalletsFlagDesc)
|
||||
setConfig.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
setConfig.Flags().Bool(forceConfigSet, false, "Force setting not well-known configuration key")
|
||||
setConfig.Flags().String(localDumpFlag, "", "Path to the blocks dump file")
|
||||
}
|
||||
|
||||
func initDumpNetworkConfigCmd() {
|
||||
RootCmd.AddCommand(dumpNetworkConfigCmd)
|
||||
dumpNetworkConfigCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
}
|
||||
|
||||
func initDumpContractHashesCmd() {
|
||||
RootCmd.AddCommand(dumpContractHashesCmd)
|
||||
dumpContractHashesCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
dumpContractHashesCmd.Flags().String(customZoneFlag, "", "Custom zone to search.")
|
||||
}
|
||||
|
||||
func initSetPolicyCmd() {
|
||||
RootCmd.AddCommand(setPolicy)
|
||||
setPolicy.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
setPolicy.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
setPolicy.Flags().String(localDumpFlag, "", "Path to the blocks dump file")
|
||||
}
|
||||
|
||||
func initDumpPolicyCmd() {
|
||||
RootCmd.AddCommand(dumpPolicy)
|
||||
dumpPolicy.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
}
|
||||
|
||||
func initRemoveNodesCmd() {
|
||||
RootCmd.AddCommand(removeNodes)
|
||||
removeNodes.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
removeNodes.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
removeNodes.Flags().String(localDumpFlag, "", "Path to the blocks dump file")
|
||||
}
|
||||
|
||||
func initForceNewEpochCmd() {
|
||||
RootCmd.AddCommand(forceNewEpoch)
|
||||
forceNewEpoch.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
forceNewEpoch.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
forceNewEpoch.Flags().String(localDumpFlag, "", "Path to the blocks dump file")
|
||||
}
|
||||
|
||||
func initGenerateStorageCmd() {
|
||||
RootCmd.AddCommand(generateStorageCmd)
|
||||
generateStorageCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
generateStorageCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
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, "", alphabetWalletsFlagDesc)
|
||||
initCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
||||
initCmd.Flags().String(contractsInitFlag, "", contractsInitFlagDesc)
|
||||
initCmd.Flags().String(contractsURLFlag, "", contractsURLFlagDesc)
|
||||
initCmd.Flags().Uint(epochDurationCLIFlag, 240, "Amount of side chain blocks in one FrostFS epoch")
|
||||
initCmd.Flags().Uint(maxObjectSizeCLIFlag, 67108864, "Max single object size in bytes")
|
||||
initCmd.Flags().Bool(homomorphicHashDisabledCLIFlag, false, "Disable object homomorphic hashing")
|
||||
// Defaults are taken from neo-preodolenie.
|
||||
initCmd.Flags().Uint64(containerFeeCLIFlag, 1000, "Container registration fee")
|
||||
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")
|
||||
initCmd.MarkFlagsMutuallyExclusive(contractsInitFlag, contractsURLFlag)
|
||||
}
|
||||
|
||||
func initGenerateAlphabetCmd() {
|
||||
RootCmd.AddCommand(generateAlphabetCmd)
|
||||
generateAlphabetCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
||||
generateAlphabetCmd.Flags().Uint(alphabetSizeFlag, 7, "Amount of alphabet wallets to generate")
|
||||
}
|
||||
|
||||
func initDeployCmd() {
|
||||
RootCmd.AddCommand(deployCmd)
|
||||
RootCmd.AddCommand(frostfsid.Cmd)
|
||||
}
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func updateContracts(cmd *cobra.Command, _ []string) error {
|
||||
wCtx, err := newInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("initialization error: %w", err)
|
||||
}
|
||||
|
||||
if err := wCtx.deployNNS(updateMethodName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return wCtx.updateContracts()
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||
"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/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func getAlphabetWallets(v *viper.Viper, walletDir string) ([]*wallet.Wallet, error) {
|
||||
wallets, err := openAlphabetWallets(v, walletDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(wallets) > maxAlphabetNodes {
|
||||
return nil, ErrTooManyAlphabetNodes
|
||||
}
|
||||
return wallets, nil
|
||||
}
|
||||
|
||||
func openAlphabetWallets(v *viper.Viper, walletDir string) ([]*wallet.Wallet, error) {
|
||||
walletFiles, err := os.ReadDir(walletDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read alphabet wallets dir: %w", err)
|
||||
}
|
||||
|
||||
var size int
|
||||
loop:
|
||||
for i := 0; i < len(walletFiles); i++ {
|
||||
name := innerring.GlagoliticLetter(i).String() + ".json"
|
||||
for j := range walletFiles {
|
||||
if walletFiles[j].Name() == name {
|
||||
size++
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
if size == 0 {
|
||||
return nil, errors.New("alphabet wallets dir is empty (run `generate-alphabet` command first)")
|
||||
}
|
||||
|
||||
wallets := make([]*wallet.Wallet, size)
|
||||
for i := 0; i < size; i++ {
|
||||
letter := innerring.GlagoliticLetter(i).String()
|
||||
p := filepath.Join(walletDir, letter+".json")
|
||||
w, err := wallet.NewWalletFromFile(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't open wallet: %w", err)
|
||||
}
|
||||
|
||||
password, err := config.GetPassword(v, letter)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't fetch password: %w", err)
|
||||
}
|
||||
|
||||
for i := range w.Accounts {
|
||||
if err := w.Accounts[i].Decrypt(password, keys.NEP2ScryptParams()); err != nil {
|
||||
return nil, fmt.Errorf("can't unlock wallet: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
wallets[i] = w
|
||||
}
|
||||
|
||||
return wallets, nil
|
||||
}
|
||||
|
||||
func newActor(c actor.RPCActor, committeeAcc *wallet.Account) (*actor.Actor, error) {
|
||||
return actor.New(c, []actor.SignerAccount{{
|
||||
Signer: transaction.Signer{
|
||||
Account: committeeAcc.Contract.ScriptHash(),
|
||||
Scopes: transaction.Global,
|
||||
},
|
||||
Account: committeeAcc,
|
||||
}})
|
||||
}
|
Loading…
Add table
Reference in a new issue
Can we avoid having this generic package?
There are client helpers, which could be put separately, there are NNS-related routines, which could be moved too.
I'm planning to add these changes in the next iteration, don't want to change current implementation. Add comment here.