adm: Refactor morph
command #950
62 changed files with 3005 additions and 2735 deletions
|
@ -11,4 +11,29 @@ const (
|
||||||
Verbose = "verbose"
|
Verbose = "verbose"
|
||||||
VerboseShorthand = "v"
|
VerboseShorthand = "v"
|
||||||
VerboseUsage = "Verbose output"
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"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"
|
parseutil "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
|
||||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||||
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||||
|
@ -32,17 +33,12 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
apeCmd = &cobra.Command{
|
|
||||||
Use: "ape",
|
|
||||||
Short: "Section for APE configuration commands",
|
|
||||||
}
|
|
||||||
|
|
||||||
addRuleChainCmd = &cobra.Command{
|
addRuleChainCmd = &cobra.Command{
|
||||||
Use: "add-rule-chain",
|
Use: "add-rule-chain",
|
||||||
Short: "Add rule chain",
|
Short: "Add rule chain",
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||||
},
|
},
|
||||||
Run: addRuleChain,
|
Run: addRuleChain,
|
||||||
}
|
}
|
||||||
|
@ -51,8 +47,8 @@ var (
|
||||||
Use: "rm-rule-chain",
|
Use: "rm-rule-chain",
|
||||||
Short: "Remove rule chain",
|
Short: "Remove rule chain",
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||||
},
|
},
|
||||||
Run: removeRuleChain,
|
Run: removeRuleChain,
|
||||||
}
|
}
|
||||||
|
@ -61,8 +57,8 @@ var (
|
||||||
Use: "list-rule-chains",
|
Use: "list-rule-chains",
|
||||||
Short: "List rule chains",
|
Short: "List rule chains",
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||||
},
|
},
|
||||||
Run: listRuleChains,
|
Run: listRuleChains,
|
||||||
}
|
}
|
||||||
|
@ -71,8 +67,8 @@ var (
|
||||||
Use: "set-admin",
|
Use: "set-admin",
|
||||||
Short: "Set admin",
|
Short: "Set admin",
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||||
},
|
},
|
||||||
Run: setAdmin,
|
Run: setAdmin,
|
||||||
}
|
}
|
||||||
|
@ -81,18 +77,18 @@ var (
|
||||||
Use: "get-admin",
|
Use: "get-admin",
|
||||||
Short: "Get admin",
|
Short: "Get admin",
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||||
},
|
},
|
||||||
Run: getAdmin,
|
Run: getAdmin,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func initAddRuleChainCmd() {
|
func initAddRuleChainCmd() {
|
||||||
apeCmd.AddCommand(addRuleChainCmd)
|
Cmd.AddCommand(addRuleChainCmd)
|
||||||
|
|
||||||
addRuleChainCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
addRuleChainCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
addRuleChainCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
addRuleChainCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||||
|
|
||||||
addRuleChainCmd.Flags().String(targetTypeFlag, "", targetTypeDesc)
|
addRuleChainCmd.Flags().String(targetTypeFlag, "", targetTypeDesc)
|
||||||
_ = addRuleChainCmd.MarkFlagRequired(targetTypeFlag)
|
_ = addRuleChainCmd.MarkFlagRequired(targetTypeFlag)
|
||||||
|
@ -108,10 +104,10 @@ func initAddRuleChainCmd() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func initRemoveRuleChainCmd() {
|
func initRemoveRuleChainCmd() {
|
||||||
apeCmd.AddCommand(removeRuleChainCmd)
|
Cmd.AddCommand(removeRuleChainCmd)
|
||||||
|
|
||||||
removeRuleChainCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
removeRuleChainCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
removeRuleChainCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
removeRuleChainCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||||
|
|
||||||
removeRuleChainCmd.Flags().String(targetTypeFlag, "", targetTypeDesc)
|
removeRuleChainCmd.Flags().String(targetTypeFlag, "", targetTypeDesc)
|
||||||
_ = removeRuleChainCmd.MarkFlagRequired(targetTypeFlag)
|
_ = removeRuleChainCmd.MarkFlagRequired(targetTypeFlag)
|
||||||
|
@ -122,10 +118,10 @@ func initRemoveRuleChainCmd() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func initListRuleChainsCmd() {
|
func initListRuleChainsCmd() {
|
||||||
apeCmd.AddCommand(listRuleChainsCmd)
|
Cmd.AddCommand(listRuleChainsCmd)
|
||||||
|
|
||||||
listRuleChainsCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
listRuleChainsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
listRuleChainsCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
listRuleChainsCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||||
listRuleChainsCmd.Flags().StringP(targetTypeFlag, "t", "", targetTypeDesc)
|
listRuleChainsCmd.Flags().StringP(targetTypeFlag, "t", "", targetTypeDesc)
|
||||||
_ = listRuleChainsCmd.MarkFlagRequired(targetTypeFlag)
|
_ = listRuleChainsCmd.MarkFlagRequired(targetTypeFlag)
|
||||||
listRuleChainsCmd.Flags().String(targetNameFlag, "", targetNameDesc)
|
listRuleChainsCmd.Flags().String(targetNameFlag, "", targetNameDesc)
|
||||||
|
@ -134,19 +130,19 @@ func initListRuleChainsCmd() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func initSetAdminCmd() {
|
func initSetAdminCmd() {
|
||||||
apeCmd.AddCommand(setAdminCmd)
|
Cmd.AddCommand(setAdminCmd)
|
||||||
|
|
||||||
setAdminCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
setAdminCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
setAdminCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
setAdminCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||||
setAdminCmd.Flags().String(addrAdminFlag, "", addrAdminDesc)
|
setAdminCmd.Flags().String(addrAdminFlag, "", addrAdminDesc)
|
||||||
_ = setAdminCmd.MarkFlagRequired(addrAdminFlag)
|
_ = setAdminCmd.MarkFlagRequired(addrAdminFlag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initGetAdminCmd() {
|
func initGetAdminCmd() {
|
||||||
apeCmd.AddCommand(getAdminCmd)
|
Cmd.AddCommand(getAdminCmd)
|
||||||
|
|
||||||
getAdminCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
getAdminCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
getAdminCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
getAdminCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addRuleChain(cmd *cobra.Command, _ []string) {
|
func addRuleChain(cmd *cobra.Command, _ []string) {
|
|
@ -1,11 +1,14 @@
|
||||||
package morph
|
package ape
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"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/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"
|
parseutil "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
|
||||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||||
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
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) {
|
func newPolicyContractInterface(cmd *cobra.Command) (*morph.ContractStorage, *actor.Actor) {
|
||||||
v := viper.GetViper()
|
v := viper.GetViper()
|
||||||
c, err := getN3Client(v)
|
c, err := helper.GetN3Client(v)
|
||||||
commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err)
|
commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err)
|
||||||
|
|
||||||
walletDir := config.ResolveHomePath(viper.GetString(alphabetWalletsFlag))
|
walletDir := config.ResolveHomePath(viper.GetString(commonflags.AlphabetWalletsFlag))
|
||||||
wallets, err := getAlphabetWallets(v, walletDir)
|
wallets, err := helper.GetAlphabetWallets(v, walletDir)
|
||||||
commonCmd.ExitOnErr(cmd, "unable to get alphabet wallets: %w", err)
|
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)
|
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)
|
commonCmd.ExitOnErr(cmd, "can't create actor: %w", err)
|
||||||
|
|
||||||
inv := &ac.Invoker
|
inv := &ac.Invoker
|
||||||
|
@ -96,7 +99,7 @@ func newPolicyContractInterface(cmd *cobra.Command) (*morph.ContractStorage, *ac
|
||||||
nnsCs, err := r.GetContractByID(1)
|
nnsCs, err := r.GetContractByID(1)
|
||||||
commonCmd.ExitOnErr(cmd, "can't get NNS contract state: %w", err)
|
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)
|
commonCmd.ExitOnErr(cmd, "unable to resolve policy contract hash: %w", err)
|
||||||
|
|
||||||
return morph.NewContractStorage(ac, ch), ac
|
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 (
|
import (
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
|
@ -7,6 +7,8 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
"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"
|
"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/native/noderoles"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
|
@ -49,7 +51,7 @@ func dumpBalances(cmd *cobra.Command, _ []string) error {
|
||||||
nmHash util.Uint160
|
nmHash util.Uint160
|
||||||
)
|
)
|
||||||
|
|
||||||
c, err := getN3Client(viper.GetViper())
|
c, err := helper.GetN3Client(viper.GetViper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -63,7 +65,7 @@ func dumpBalances(cmd *cobra.Command, _ []string) error {
|
||||||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
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 {
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("can't get hash of the proxy contract: %w", err)
|
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
|
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)
|
alphaList := make([]accBalancePair, count)
|
||||||
|
|
||||||
w := io.NewBufBinWriter()
|
w := io.NewBufBinWriter()
|
||||||
for i := range alphaList {
|
for i := range alphaList {
|
||||||
emit.AppCall(w.BinWriter, nnsHash, "resolve", callflag.ReadOnly,
|
emit.AppCall(w.BinWriter, nnsHash, "resolve", callflag.ReadOnly,
|
||||||
getAlphabetNNSDomain(i),
|
helper.GetAlphabetNNSDomain(i),
|
||||||
int64(nns.TXT))
|
int64(nns.TXT))
|
||||||
}
|
}
|
||||||
if w.Err != nil {
|
if w.Err != nil {
|
||||||
|
@ -169,7 +171,7 @@ func printAlphabetContractBalances(cmd *cobra.Command, c Client, inv *invoker.In
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range alphaList {
|
for i := range alphaList {
|
||||||
h, err := parseNNSResolveResult(alphaRes.Stack[i])
|
h, err := helper.ParseNNSResolveResult(alphaRes.Stack[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't fetch the alphabet contract #%d hash: %w", i, err)
|
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
|
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)
|
inv := invoker.New(c, nil)
|
||||||
|
|
||||||
height, err := c.GetBlockCount()
|
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)
|
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 {
|
if err != nil {
|
||||||
return nil, errors.New("can't fetch list of IR nodes from the netmap contract")
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -10,6 +10,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
|
"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/morph/client/netmap"
|
"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/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||||
|
@ -24,7 +26,7 @@ import (
|
||||||
const forceConfigSet = "force"
|
const forceConfigSet = "force"
|
||||||
|
|
||||||
func dumpNetworkConfig(cmd *cobra.Command, _ []string) error {
|
func dumpNetworkConfig(cmd *cobra.Command, _ []string) error {
|
||||||
c, err := getN3Client(viper.GetViper())
|
c, err := helper.GetN3Client(viper.GetViper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't create N3 client: %w", err)
|
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)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
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)
|
buf := bytes.NewBuffer(nil)
|
||||||
tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
|
tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
|
||||||
|
|
||||||
m, err := parseConfigFromNetmapContract(arr)
|
m, err := helper.ParseConfigFromNetmapContract(arr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)))
|
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%d (int)\n", k, n)))
|
||||||
case netmap.HomomorphicHashingDisabledKey, netmap.MaintenanceModeAllowedConfig:
|
case netmap.HomomorphicHashingDisabledKey, netmap.MaintenanceModeAllowedConfig:
|
||||||
if len(v) == 0 || len(v) > 1 {
|
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)))
|
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%t (bool)\n", k, v[0] == 1)))
|
||||||
default:
|
default:
|
||||||
|
@ -79,12 +81,12 @@ func dumpNetworkConfig(cmd *cobra.Command, _ []string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setConfigCmd(cmd *cobra.Command, args []string) error {
|
func SetConfigCmd(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return errors.New("empty config pairs")
|
return errors.New("empty config pairs")
|
||||||
}
|
}
|
||||||
|
|
||||||
wCtx, err := newInitializeContext(cmd, viper.GetViper())
|
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't initialize context: %w", err)
|
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)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return wCtx.awaitTx()
|
return wCtx.AwaitTx()
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseConfigPair(kvStr string, force bool) (key string, val any, err error) {
|
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
|
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
|
||||||
|
|
||||||
|
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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -7,6 +7,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"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"
|
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/crypto/hash"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"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 {
|
if err != nil {
|
||||||
return util.Uint160{}, fmt.Errorf("can't get NNS contract state: %w", err)
|
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 {
|
if err != nil {
|
||||||
return util.Uint160{}, err
|
return util.Uint160{}, err
|
||||||
}
|
}
|
||||||
|
@ -73,7 +75,7 @@ func dumpContainers(cmd *cobra.Command, _ []string) error {
|
||||||
return fmt.Errorf("invalid filename: %w", err)
|
return fmt.Errorf("invalid filename: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := getN3Client(viper.GetViper())
|
c, err := helper.GetN3Client(viper.GetViper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't create N3 client: %w", err)
|
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 {
|
func listContainers(cmd *cobra.Command, _ []string) error {
|
||||||
c, err := getN3Client(viper.GetViper())
|
c, err := helper.GetN3Client(viper.GetViper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't create N3 client: %w", err)
|
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)
|
return fmt.Errorf("invalid filename: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wCtx, err := newInitializeContext(cmd, viper.GetViper())
|
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer wCtx.close()
|
defer wCtx.Close()
|
||||||
|
|
||||||
containers, err := parseContainers(filename)
|
containers, err := parseContainers(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -218,10 +220,10 @@ func restoreContainers(cmd *cobra.Command, _ []string) error {
|
||||||
return err
|
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()
|
bw := io.NewBufBinWriter()
|
||||||
for _, cnt := range containers {
|
for _, cnt := range containers {
|
||||||
hv := hash.Sha256(cnt.Value)
|
hv := hash.Sha256(cnt.Value)
|
||||||
|
@ -245,7 +247,7 @@ func restoreOrPutContainers(containers []Container, isOK func([]byte) bool, cmd
|
||||||
panic(bw.Err)
|
panic(bw.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := wCtx.sendConsensusTx(bw.Bytes()); err != nil {
|
if err := wCtx.SendConsensusTx(bw.Bytes()); err != nil {
|
||||||
return err
|
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())
|
emit.AppCall(bw.BinWriter, containerHash, "get", callflag.All, hashValue.BytesBE())
|
||||||
res, err := wCtx.Client.InvokeScript(bw.Bytes(), nil)
|
res, err := wCtx.Client.InvokeScript(bw.Bytes(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -299,14 +301,14 @@ func parseContainers(filename string) ([]Container, error) {
|
||||||
return containers, nil
|
return containers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchContainerContractHash(wCtx *initializeContext) (util.Uint160, error) {
|
func fetchContainerContractHash(wCtx *helper.InitializeContext) (util.Uint160, error) {
|
||||||
r := management.NewReader(wCtx.ReadOnlyInvoker)
|
r := management.NewReader(wCtx.ReadOnlyInvoker)
|
||||||
nnsCs, err := r.GetContractByID(1)
|
nnsCs, err := r.GetContractByID(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.Uint160{}, fmt.Errorf("can't get NNS contract state: %w", err)
|
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 {
|
if err != nil {
|
||||||
return util.Uint160{}, fmt.Errorf("can't fetch container contract hash: %w", err)
|
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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -7,6 +7,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
"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/cli/cmdargs"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
|
@ -23,10 +26,9 @@ import (
|
||||||
const (
|
const (
|
||||||
contractPathFlag = "contract"
|
contractPathFlag = "contract"
|
||||||
updateFlag = "update"
|
updateFlag = "update"
|
||||||
customZoneFlag = "domain"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var deployCmd = &cobra.Command{
|
var DeployCmd = &cobra.Command{
|
||||||
Use: "deploy",
|
Use: "deploy",
|
||||||
Short: "Deploy additional smart-contracts",
|
Short: "Deploy additional smart-contracts",
|
||||||
Long: `Deploy additional smart-contract which are not related to core.
|
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'.
|
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).`,
|
NNS name is taken by stripping '_contract.nef' from the NEF file (similar to frostfs contracts).`,
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||||
},
|
},
|
||||||
RunE: deployContractCmd,
|
RunE: deployContractCmd,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
ff := deployCmd.Flags()
|
ff := DeployCmd.Flags()
|
||||||
|
|
||||||
ff.String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
ff.String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||||
_ = deployCmd.MarkFlagFilename(alphabetWalletsFlag)
|
_ = DeployCmd.MarkFlagFilename(commonflags.AlphabetWalletsFlag)
|
||||||
|
|
||||||
ff.StringP(endpointFlag, "r", "", endpointFlagDesc)
|
ff.StringP(commonflags.EndpointFlag, "r", "", commonflags.EndpointFlagDesc)
|
||||||
ff.String(contractPathFlag, "", "Path to the contract directory")
|
ff.String(contractPathFlag, "", "Path to the contract directory")
|
||||||
_ = deployCmd.MarkFlagFilename(contractPathFlag)
|
_ = DeployCmd.MarkFlagFilename(contractPathFlag)
|
||||||
|
|
||||||
ff.Bool(updateFlag, false, "Update an existing contract")
|
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 {
|
func deployContractCmd(cmd *cobra.Command, args []string) error {
|
||||||
v := viper.GetViper()
|
v := viper.GetViper()
|
||||||
c, err := newInitializeContext(cmd, v)
|
c, err := helper.NewInitializeContext(cmd, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("initialization error: %w", err)
|
return fmt.Errorf("initialization error: %w", err)
|
||||||
}
|
}
|
||||||
defer c.close()
|
defer c.Close()
|
||||||
|
|
||||||
ctrPath, _ := cmd.Flags().GetString(contractPathFlag)
|
ctrPath, _ := cmd.Flags().GetString(contractPathFlag)
|
||||||
ctrName, err := probeContractName(ctrPath)
|
ctrName, err := probeContractName(ctrPath)
|
||||||
|
@ -71,7 +73,7 @@ func deployContractCmd(cmd *cobra.Command, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cs, err := readContract(ctrPath, ctrName)
|
cs, err := helper.ReadContract(ctrPath, ctrName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -83,17 +85,17 @@ func deployContractCmd(cmd *cobra.Command, args []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
callHash := management.Hash
|
callHash := management.Hash
|
||||||
method := deployMethodName
|
method := constants.DeployMethodName
|
||||||
zone, _ := cmd.Flags().GetString(customZoneFlag)
|
zone, _ := cmd.Flags().GetString(commonflags.CustomZoneFlag)
|
||||||
domain := ctrName + "." + zone
|
domain := ctrName + "." + zone
|
||||||
isUpdate, _ := cmd.Flags().GetBool(updateFlag)
|
isUpdate, _ := cmd.Flags().GetBool(updateFlag)
|
||||||
if isUpdate {
|
if isUpdate {
|
||||||
cs.Hash, err = nnsResolveHash(c.ReadOnlyInvoker, nnsCs.Hash, domain)
|
cs.Hash, err = helper.NNSResolveHash(c.ReadOnlyInvoker, nnsCs.Hash, domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't fetch contract hash from NNS: %w", err)
|
return fmt.Errorf("can't fetch contract hash from NNS: %w", err)
|
||||||
}
|
}
|
||||||
callHash = cs.Hash
|
callHash = cs.Hash
|
||||||
method = updateMethodName
|
method = constants.UpdateMethodName
|
||||||
} else {
|
} else {
|
||||||
cs.Hash = state.CreateContractHash(
|
cs.Hash = state.CreateContractHash(
|
||||||
c.CommitteeAcc.Contract.ScriptHash(),
|
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))
|
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 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()
|
bw := io.NewBufBinWriter()
|
||||||
emit.Instruction(bw.BinWriter, opcode.INITSSLOT, []byte{1})
|
emit.Instruction(bw.BinWriter, opcode.INITSSLOT, []byte{1})
|
||||||
emit.AppCall(bw.BinWriter, nnsCs.Hash, "getPrice", callflag.All)
|
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()
|
start := bw.Len()
|
||||||
needRecord := false
|
needRecord := false
|
||||||
|
|
||||||
ok, err := c.nnsRootRegistered(nnsCs.Hash, zone)
|
ok, err := c.NNSRootRegistered(nnsCs.Hash, zone)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if !ok {
|
} 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,
|
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
|
||||||
zone, c.CommitteeAcc.Contract.ScriptHash(),
|
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.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||||
|
|
||||||
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
|
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
|
||||||
domain, c.CommitteeAcc.Contract.ScriptHash(),
|
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)
|
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||||
} else {
|
} else {
|
||||||
s, ok, err := c.nnsRegisterDomainScript(nnsCs.Hash, cs.Hash, domain)
|
s, ok, err := c.NNSRegisterDomainScript(nnsCs.Hash, cs.Hash, domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package morph
|
package contract
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -8,6 +8,9 @@ import (
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
"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"
|
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/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||||
|
@ -33,7 +36,7 @@ type contractDumpInfo struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpContractHashes(cmd *cobra.Command, _ []string) error {
|
func dumpContractHashes(cmd *cobra.Command, _ []string) error {
|
||||||
c, err := getN3Client(viper.GetViper())
|
c, err := helper.GetN3Client(viper.GetViper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't create N3 client: %w", err)
|
return fmt.Errorf("can't create N3 client: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -44,16 +47,16 @@ func dumpContractHashes(cmd *cobra.Command, _ []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
zone, _ := cmd.Flags().GetString(customZoneFlag)
|
zone, _ := cmd.Flags().GetString(commonflags.CustomZoneFlag)
|
||||||
if zone != "" {
|
if zone != "" {
|
||||||
return dumpCustomZoneHashes(cmd, cs.Hash, zone, c)
|
return dumpCustomZoneHashes(cmd, cs.Hash, zone, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
infos := []contractDumpInfo{{name: nnsContract, hash: cs.Hash}}
|
infos := []contractDumpInfo{{name: constants.NNSContract, hash: cs.Hash}}
|
||||||
|
|
||||||
irSize := 0
|
irSize := 0
|
||||||
for ; irSize < lastGlagoliticLetter; irSize++ {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if ok {
|
} else if ok {
|
||||||
|
@ -67,7 +70,7 @@ func dumpContractHashes(cmd *cobra.Command, _ []string) error {
|
||||||
bw.Reset()
|
bw.Reset()
|
||||||
for i := 0; i < irSize; i++ {
|
for i := 0; i < irSize; i++ {
|
||||||
emit.AppCall(bw.BinWriter, cs.Hash, "resolve", callflag.ReadOnly,
|
emit.AppCall(bw.BinWriter, cs.Hash, "resolve", callflag.ReadOnly,
|
||||||
getAlphabetNNSDomain(i),
|
helper.GetAlphabetNNSDomain(i),
|
||||||
int64(nns.TXT))
|
int64(nns.TXT))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,17 +81,17 @@ func dumpContractHashes(cmd *cobra.Command, _ []string) error {
|
||||||
|
|
||||||
for i := 0; i < irSize; i++ {
|
for i := 0; i < irSize; i++ {
|
||||||
info := contractDumpInfo{name: fmt.Sprintf("alphabet %d", 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
|
info.hash = h
|
||||||
}
|
}
|
||||||
infos = append(infos, info)
|
infos = append(infos, info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ctrName := range contractList {
|
for _, ctrName := range constants.ContractList {
|
||||||
bw.Reset()
|
bw.Reset()
|
||||||
emit.AppCall(bw.BinWriter, cs.Hash, "resolve", callflag.ReadOnly,
|
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)
|
res, err := c.InvokeScript(bw.Bytes(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -97,7 +100,7 @@ func dumpContractHashes(cmd *cobra.Command, _ []string) error {
|
||||||
|
|
||||||
info := contractDumpInfo{name: ctrName}
|
info := contractDumpInfo{name: ctrName}
|
||||||
if len(res.Stack) != 0 {
|
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
|
info.hash = h
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +113,7 @@ func dumpContractHashes(cmd *cobra.Command, _ []string) error {
|
||||||
return nil
|
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
|
const nnsMaxTokens = 100
|
||||||
|
|
||||||
inv := invoker.New(c, nil)
|
inv := invoker.New(c, nil)
|
||||||
|
@ -132,7 +135,7 @@ func dumpCustomZoneHashes(cmd *cobra.Command, nnsHash util.Uint160, zone string,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h, err := nnsResolveHash(inv, nnsHash, string(bs))
|
h, err := helper.NNSResolveHash(inv, nnsHash, string(bs))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmd.PrintErrf("Could not resolve name %s: %v\n", string(bs), err)
|
cmd.PrintErrf("Could not resolve name %s: %v\n", string(bs), err)
|
||||||
return
|
return
|
||||||
|
@ -224,7 +227,7 @@ func printContractInfo(cmd *cobra.Command, infos []contractDumpInfo) {
|
||||||
cmd.Print(buf.String())
|
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()
|
bw := io.NewBufBinWriter()
|
||||||
sub := io.NewBufBinWriter()
|
sub := io.NewBufBinWriter()
|
||||||
for i := range infos {
|
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 (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -6,6 +6,9 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
frostfsidclient "git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
|
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"
|
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/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
|
@ -26,12 +29,12 @@ const (
|
||||||
includeNamesFlag = "include-names"
|
includeNamesFlag = "include-names"
|
||||||
groupNameFlag = "group-name"
|
groupNameFlag = "group-name"
|
||||||
groupIDFlag = "group-id"
|
groupIDFlag = "group-id"
|
||||||
|
|
||||||
|
rootNamespacePlaceholder = "<root>"
|
||||||
)
|
)
|
||||||
|
|
||||||
const rootNamespacePlaceholder = "<root>"
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
frostfsidCmd = &cobra.Command{
|
Cmd = &cobra.Command{
|
||||||
Use: "frostfsid",
|
Use: "frostfsid",
|
||||||
Short: "Section for frostfsid interactions commands",
|
Short: "Section for frostfsid interactions commands",
|
||||||
}
|
}
|
||||||
|
@ -40,8 +43,8 @@ var (
|
||||||
Use: "create-namespace",
|
Use: "create-namespace",
|
||||||
Short: "Create new namespace in frostfsid contract",
|
Short: "Create new namespace in frostfsid contract",
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||||
},
|
},
|
||||||
Run: frostfsidCreateNamespace,
|
Run: frostfsidCreateNamespace,
|
||||||
}
|
}
|
||||||
|
@ -50,8 +53,8 @@ var (
|
||||||
Use: "list-namespaces",
|
Use: "list-namespaces",
|
||||||
Short: "List all namespaces in frostfsid",
|
Short: "List all namespaces in frostfsid",
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||||
},
|
},
|
||||||
Run: frostfsidListNamespaces,
|
Run: frostfsidListNamespaces,
|
||||||
}
|
}
|
||||||
|
@ -60,8 +63,8 @@ var (
|
||||||
Use: "create-subject",
|
Use: "create-subject",
|
||||||
Short: "Create subject in frostfsid contract",
|
Short: "Create subject in frostfsid contract",
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||||
},
|
},
|
||||||
Run: frostfsidCreateSubject,
|
Run: frostfsidCreateSubject,
|
||||||
}
|
}
|
||||||
|
@ -70,8 +73,8 @@ var (
|
||||||
Use: "delete-subject",
|
Use: "delete-subject",
|
||||||
Short: "Delete subject from frostfsid contract",
|
Short: "Delete subject from frostfsid contract",
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||||
},
|
},
|
||||||
Run: frostfsidDeleteSubject,
|
Run: frostfsidDeleteSubject,
|
||||||
}
|
}
|
||||||
|
@ -80,8 +83,8 @@ var (
|
||||||
Use: "list-subjects",
|
Use: "list-subjects",
|
||||||
Short: "List subjects in namespace",
|
Short: "List subjects in namespace",
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||||
},
|
},
|
||||||
Run: frostfsidListSubjects,
|
Run: frostfsidListSubjects,
|
||||||
}
|
}
|
||||||
|
@ -90,8 +93,8 @@ var (
|
||||||
Use: "create-group",
|
Use: "create-group",
|
||||||
Short: "Create group in frostfsid contract",
|
Short: "Create group in frostfsid contract",
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||||
},
|
},
|
||||||
Run: frostfsidCreateGroup,
|
Run: frostfsidCreateGroup,
|
||||||
}
|
}
|
||||||
|
@ -100,8 +103,8 @@ var (
|
||||||
Use: "delete-group",
|
Use: "delete-group",
|
||||||
Short: "Delete group from frostfsid contract",
|
Short: "Delete group from frostfsid contract",
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||||
},
|
},
|
||||||
Run: frostfsidDeleteGroup,
|
Run: frostfsidDeleteGroup,
|
||||||
}
|
}
|
||||||
|
@ -110,8 +113,8 @@ var (
|
||||||
Use: "list-groups",
|
Use: "list-groups",
|
||||||
Short: "List groups in namespace",
|
Short: "List groups in namespace",
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||||
},
|
},
|
||||||
Run: frostfsidListGroups,
|
Run: frostfsidListGroups,
|
||||||
}
|
}
|
||||||
|
@ -120,8 +123,8 @@ var (
|
||||||
Use: "add-subject-to-group",
|
Use: "add-subject-to-group",
|
||||||
Short: "Add subject to group",
|
Short: "Add subject to group",
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||||
},
|
},
|
||||||
Run: frostfsidAddSubjectToGroup,
|
Run: frostfsidAddSubjectToGroup,
|
||||||
}
|
}
|
||||||
|
@ -130,8 +133,8 @@ var (
|
||||||
Use: "remove-subject-from-group",
|
Use: "remove-subject-from-group",
|
||||||
Short: "Remove subject from group",
|
Short: "Remove subject from group",
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||||
},
|
},
|
||||||
Run: frostfsidRemoveSubjectFromGroup,
|
Run: frostfsidRemoveSubjectFromGroup,
|
||||||
}
|
}
|
||||||
|
@ -140,96 +143,96 @@ var (
|
||||||
Use: "list-group-subjects",
|
Use: "list-group-subjects",
|
||||||
Short: "List subjects in group",
|
Short: "List subjects in group",
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||||
},
|
},
|
||||||
Run: frostfsidListGroupSubjects,
|
Run: frostfsidListGroupSubjects,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func initFrostfsIDCreateNamespaceCmd() {
|
func initFrostfsIDCreateNamespaceCmd() {
|
||||||
frostfsidCmd.AddCommand(frostfsidCreateNamespaceCmd)
|
Cmd.AddCommand(frostfsidCreateNamespaceCmd)
|
||||||
frostfsidCreateNamespaceCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
frostfsidCreateNamespaceCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
frostfsidCreateNamespaceCmd.Flags().String(namespaceFlag, "", "Namespace name to create")
|
frostfsidCreateNamespaceCmd.Flags().String(namespaceFlag, "", "Namespace name to create")
|
||||||
frostfsidCreateNamespaceCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
frostfsidCreateNamespaceCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initFrostfsIDListNamespacesCmd() {
|
func initFrostfsIDListNamespacesCmd() {
|
||||||
frostfsidCmd.AddCommand(frostfsidListNamespacesCmd)
|
Cmd.AddCommand(frostfsidListNamespacesCmd)
|
||||||
frostfsidListNamespacesCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
frostfsidListNamespacesCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
frostfsidListNamespacesCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
frostfsidListNamespacesCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initFrostfsIDCreateSubjectCmd() {
|
func initFrostfsIDCreateSubjectCmd() {
|
||||||
frostfsidCmd.AddCommand(frostfsidCreateSubjectCmd)
|
Cmd.AddCommand(frostfsidCreateSubjectCmd)
|
||||||
frostfsidCreateSubjectCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
frostfsidCreateSubjectCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
frostfsidCreateSubjectCmd.Flags().String(namespaceFlag, "", "Namespace where create subject")
|
frostfsidCreateSubjectCmd.Flags().String(namespaceFlag, "", "Namespace where create subject")
|
||||||
frostfsidCreateSubjectCmd.Flags().String(subjectNameFlag, "", "Subject name, must be unique in namespace")
|
frostfsidCreateSubjectCmd.Flags().String(subjectNameFlag, "", "Subject name, must be unique in namespace")
|
||||||
frostfsidCreateSubjectCmd.Flags().String(subjectKeyFlag, "", "Subject hex-encoded public key")
|
frostfsidCreateSubjectCmd.Flags().String(subjectKeyFlag, "", "Subject hex-encoded public key")
|
||||||
frostfsidCreateSubjectCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
frostfsidCreateSubjectCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initFrostfsIDDeleteSubjectCmd() {
|
func initFrostfsIDDeleteSubjectCmd() {
|
||||||
frostfsidCmd.AddCommand(frostfsidDeleteSubjectCmd)
|
Cmd.AddCommand(frostfsidDeleteSubjectCmd)
|
||||||
frostfsidDeleteSubjectCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
frostfsidDeleteSubjectCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
frostfsidDeleteSubjectCmd.Flags().String(subjectAddressFlag, "", "Subject address")
|
frostfsidDeleteSubjectCmd.Flags().String(subjectAddressFlag, "", "Subject address")
|
||||||
frostfsidDeleteSubjectCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
frostfsidDeleteSubjectCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initFrostfsIDListSubjectsCmd() {
|
func initFrostfsIDListSubjectsCmd() {
|
||||||
frostfsidCmd.AddCommand(frostfsidListSubjectsCmd)
|
Cmd.AddCommand(frostfsidListSubjectsCmd)
|
||||||
frostfsidListSubjectsCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
frostfsidListSubjectsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
frostfsidListSubjectsCmd.Flags().String(namespaceFlag, "", "Namespace to list subjects")
|
frostfsidListSubjectsCmd.Flags().String(namespaceFlag, "", "Namespace to list subjects")
|
||||||
frostfsidListSubjectsCmd.Flags().Bool(includeNamesFlag, false, "Whether include subject name (require additional requests)")
|
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() {
|
func initFrostfsIDCreateGroupCmd() {
|
||||||
frostfsidCmd.AddCommand(frostfsidCreateGroupCmd)
|
Cmd.AddCommand(frostfsidCreateGroupCmd)
|
||||||
frostfsidCreateGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
frostfsidCreateGroupCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
frostfsidCreateGroupCmd.Flags().String(namespaceFlag, "", "Namespace where create group")
|
frostfsidCreateGroupCmd.Flags().String(namespaceFlag, "", "Namespace where create group")
|
||||||
frostfsidCreateGroupCmd.Flags().String(groupNameFlag, "", "Group name, must be unique in namespace")
|
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() {
|
func initFrostfsIDDeleteGroupCmd() {
|
||||||
frostfsidCmd.AddCommand(frostfsidDeleteGroupCmd)
|
Cmd.AddCommand(frostfsidDeleteGroupCmd)
|
||||||
frostfsidDeleteGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
frostfsidDeleteGroupCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
frostfsidDeleteGroupCmd.Flags().String(namespaceFlag, "", "Namespace to delete group")
|
frostfsidDeleteGroupCmd.Flags().String(namespaceFlag, "", "Namespace to delete group")
|
||||||
frostfsidDeleteGroupCmd.Flags().Int64(groupIDFlag, 0, "Group id")
|
frostfsidDeleteGroupCmd.Flags().Int64(groupIDFlag, 0, "Group id")
|
||||||
frostfsidDeleteGroupCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
frostfsidDeleteGroupCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initFrostfsIDListGroupsCmd() {
|
func initFrostfsIDListGroupsCmd() {
|
||||||
frostfsidCmd.AddCommand(frostfsidListGroupsCmd)
|
Cmd.AddCommand(frostfsidListGroupsCmd)
|
||||||
frostfsidListGroupsCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
frostfsidListGroupsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
frostfsidListGroupsCmd.Flags().String(namespaceFlag, "", "Namespace to list groups")
|
frostfsidListGroupsCmd.Flags().String(namespaceFlag, "", "Namespace to list groups")
|
||||||
frostfsidListGroupsCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
frostfsidListGroupsCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initFrostfsIDAddSubjectToGroupCmd() {
|
func initFrostfsIDAddSubjectToGroupCmd() {
|
||||||
frostfsidCmd.AddCommand(frostfsidAddSubjectToGroupCmd)
|
Cmd.AddCommand(frostfsidAddSubjectToGroupCmd)
|
||||||
frostfsidAddSubjectToGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
frostfsidAddSubjectToGroupCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
frostfsidAddSubjectToGroupCmd.Flags().String(subjectAddressFlag, "", "Subject address")
|
frostfsidAddSubjectToGroupCmd.Flags().String(subjectAddressFlag, "", "Subject address")
|
||||||
frostfsidAddSubjectToGroupCmd.Flags().Int64(groupIDFlag, 0, "Group id")
|
frostfsidAddSubjectToGroupCmd.Flags().Int64(groupIDFlag, 0, "Group id")
|
||||||
frostfsidAddSubjectToGroupCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
frostfsidAddSubjectToGroupCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initFrostfsIDRemoveSubjectFromGroupCmd() {
|
func initFrostfsIDRemoveSubjectFromGroupCmd() {
|
||||||
frostfsidCmd.AddCommand(frostfsidRemoveSubjectFromGroupCmd)
|
Cmd.AddCommand(frostfsidRemoveSubjectFromGroupCmd)
|
||||||
frostfsidRemoveSubjectFromGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
frostfsidRemoveSubjectFromGroupCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
frostfsidRemoveSubjectFromGroupCmd.Flags().String(subjectAddressFlag, "", "Subject address")
|
frostfsidRemoveSubjectFromGroupCmd.Flags().String(subjectAddressFlag, "", "Subject address")
|
||||||
frostfsidRemoveSubjectFromGroupCmd.Flags().Int64(groupIDFlag, 0, "Group id")
|
frostfsidRemoveSubjectFromGroupCmd.Flags().Int64(groupIDFlag, 0, "Group id")
|
||||||
frostfsidRemoveSubjectFromGroupCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
|
frostfsidRemoveSubjectFromGroupCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initFrostfsIDListGroupSubjectsCmd() {
|
func initFrostfsIDListGroupSubjectsCmd() {
|
||||||
frostfsidCmd.AddCommand(frostfsidListGroupSubjectsCmd)
|
Cmd.AddCommand(frostfsidListGroupSubjectsCmd)
|
||||||
frostfsidListGroupSubjectsCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
|
frostfsidListGroupSubjectsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
frostfsidListGroupSubjectsCmd.Flags().String(namespaceFlag, "", "Namespace name")
|
frostfsidListGroupSubjectsCmd.Flags().String(namespaceFlag, "", "Namespace name")
|
||||||
frostfsidListGroupSubjectsCmd.Flags().Int64(groupIDFlag, 0, "Group id")
|
frostfsidListGroupSubjectsCmd.Flags().Int64(groupIDFlag, 0, "Group id")
|
||||||
frostfsidListGroupSubjectsCmd.Flags().Bool(includeNamesFlag, false, "Whether include subject name (require additional requests)")
|
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) {
|
func frostfsidCreateNamespace(cmd *cobra.Command, _ []string) {
|
||||||
|
@ -415,11 +418,11 @@ type frostfsidClient struct {
|
||||||
bw *io.BufBinWriter
|
bw *io.BufBinWriter
|
||||||
contractHash util.Uint160
|
contractHash util.Uint160
|
||||||
roCli *frostfsidclient.Client // client can be used only for waiting tx, parsing and forming method params
|
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) {
|
func newFrostfsIDClient(cmd *cobra.Command) (*frostfsidClient, error) {
|
||||||
wCtx, err := newInitializeContext(cmd, viper.GetViper())
|
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't to initialize context: %w", err)
|
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)
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't get proxy contract hash: %w", err)
|
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 {
|
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
|
return err
|
||||||
}
|
}
|
||||||
f.bw.Reset()
|
f.bw.Reset()
|
||||||
|
|
||||||
return f.wCtx.awaitTx()
|
return f.wCtx.AwaitTx()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *frostfsidClient) sendWaitRes() (*state.AppExecResult, error) {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
f.bw.Reset()
|
f.bw.Reset()
|
||||||
|
@ -467,5 +470,5 @@ func (f *frostfsidClient) sendWaitRes() (*state.AppExecResult, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
f.wCtx.Command.Println("Waiting for transactions to persist...")
|
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 (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -10,32 +10,8 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/spf13/cobra"
|
"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 {
|
func getFrostfsIDSubjectKey(cmd *cobra.Command) *keys.PublicKey {
|
||||||
subjKeyHex, _ := cmd.Flags().GetString(subjectKeyFlag)
|
subjKeyHex, _ := cmd.Flags().GetString(subjectKeyFlag)
|
||||||
subjKey, err := keys.NewPublicKeyFromString(subjKeyHex)
|
subjKey, err := keys.NewPublicKeyFromString(subjKeyHex)
|
|
@ -1,9 +1,10 @@
|
||||||
package morph
|
package frostfsid
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/ape"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/ape"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"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/address"
|
||||||
|
@ -30,7 +31,7 @@ func TestFrostfsIDConfig(t *testing.T) {
|
||||||
v := viper.New()
|
v := viper.New()
|
||||||
v.Set("frostfsid.admin", fmts[i])
|
v.Set("frostfsid.admin", fmts[i])
|
||||||
|
|
||||||
actual, found, err := getFrostfsIDAdmin(v)
|
actual, found, err := helper.GetFrostfsIDAdmin(v)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.True(t, found)
|
require.True(t, found)
|
||||||
require.Equal(t, pks[i].GetScriptHash(), actual)
|
require.Equal(t, pks[i].GetScriptHash(), actual)
|
||||||
|
@ -40,14 +41,14 @@ func TestFrostfsIDConfig(t *testing.T) {
|
||||||
v := viper.New()
|
v := viper.New()
|
||||||
v.Set("frostfsid.admin", "abc")
|
v.Set("frostfsid.admin", "abc")
|
||||||
|
|
||||||
_, found, err := getFrostfsIDAdmin(v)
|
_, found, err := helper.GetFrostfsIDAdmin(v)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.True(t, found)
|
require.True(t, found)
|
||||||
})
|
})
|
||||||
t.Run("missing key", func(t *testing.T) {
|
t.Run("missing key", func(t *testing.T) {
|
||||||
v := viper.New()
|
v := viper.New()
|
||||||
|
|
||||||
_, found, err := getFrostfsIDAdmin(v)
|
_, found, err := helper.GetFrostfsIDAdmin(v)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.False(t, found)
|
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 (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -6,11 +6,13 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"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/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"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"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/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/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
@ -24,33 +26,27 @@ import (
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
func AlphabetCreds(cmd *cobra.Command, _ []string) error {
|
||||||
singleAccountName = "single"
|
|
||||||
committeeAccountName = "committee"
|
|
||||||
consensusAccountName = "consensus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func generateAlphabetCreds(cmd *cobra.Command, _ []string) error {
|
|
||||||
// alphabet size is not part of the config
|
// alphabet size is not part of the config
|
||||||
size, err := cmd.Flags().GetUint(alphabetSizeFlag)
|
size, err := cmd.Flags().GetUint(commonflags.AlphabetSizeFlag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
return errors.New("size must be > 0")
|
return errors.New("size must be > 0")
|
||||||
}
|
}
|
||||||
if size > maxAlphabetNodes {
|
if size > constants.MaxAlphabetNodes {
|
||||||
return ErrTooManyAlphabetNodes
|
return helper.ErrTooManyAlphabetNodes
|
||||||
}
|
}
|
||||||
|
|
||||||
v := viper.GetViper()
|
v := viper.GetViper()
|
||||||
walletDir := config.ResolveHomePath(viper.GetString(alphabetWalletsFlag))
|
walletDir := config.ResolveHomePath(viper.GetString(commonflags.AlphabetWalletsFlag))
|
||||||
pwds, err := initializeWallets(v, walletDir, int(size))
|
pwds, err := initializeWallets(v, walletDir, int(size))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = initializeContractWallet(v, walletDir)
|
_, err = helper.InitializeContractWallet(v, walletDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -87,7 +83,7 @@ func initializeWallets(v *viper.Viper, walletDir string, size int) ([]string, er
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't create wallet: %w", err)
|
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)
|
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
|
i := i
|
||||||
ps := pubs.Copy()
|
ps := pubs.Copy()
|
||||||
errG.Go(func() error {
|
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)
|
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)
|
return fmt.Errorf("can't create consentus account: %w", err)
|
||||||
}
|
}
|
||||||
if err := wallets[i].SavePretty(); err != nil {
|
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) {
|
func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) (err error) {
|
||||||
// storage wallet path is not part of the config
|
// 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
|
// wallet address is not part of the config
|
||||||
walletAddress, _ := cmd.Flags().GetString(walletAddressFlag)
|
walletAddress, _ := cmd.Flags().GetString(walletAddressFlag)
|
||||||
|
|
||||||
|
@ -157,7 +153,7 @@ func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) (err error
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if storageWalletPath == "" {
|
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
|
var w *wallet.Wallet
|
||||||
|
@ -182,7 +178,7 @@ func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) (err error
|
||||||
}
|
}
|
||||||
|
|
||||||
if label == "" {
|
if label == "" {
|
||||||
label = singleAccountName
|
label = constants.SingleAccountName
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := w.CreateAccount(label, password); err != nil {
|
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)
|
gasStr := viper.GetString(gasFlag)
|
||||||
|
|
||||||
gasAmount, err := parseGASAmount(gasStr)
|
gasAmount, err := helper.ParseGASAmount(gasStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
wCtx, err := newInitializeContext(cmd, viper.GetViper())
|
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)
|
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 err
|
||||||
}
|
}
|
||||||
|
|
||||||
return wCtx.awaitTx()
|
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
|
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package morph
|
package generate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -10,6 +10,8 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"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"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||||
"github.com/nspcc-dev/neo-go/cli/input"
|
"github.com/nspcc-dev/neo-go/cli/input"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
@ -20,57 +22,55 @@ import (
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
const testContractPassword = "grouppass"
|
|
||||||
|
|
||||||
func TestGenerateAlphabet(t *testing.T) {
|
func TestGenerateAlphabet(t *testing.T) {
|
||||||
const size = 4
|
const size = 4
|
||||||
|
|
||||||
walletDir := t.TempDir()
|
walletDir := t.TempDir()
|
||||||
buf := setupTestTerminal(t)
|
buf := setupTestTerminal(t)
|
||||||
|
|
||||||
cmd := generateAlphabetCmd
|
cmd := GenerateAlphabetCmd
|
||||||
v := viper.GetViper()
|
v := viper.GetViper()
|
||||||
|
|
||||||
t.Run("zero size", func(t *testing.T) {
|
t.Run("zero size", func(t *testing.T) {
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
v.Set(alphabetWalletsFlag, walletDir)
|
v.Set(commonflags.AlphabetWalletsFlag, walletDir)
|
||||||
require.NoError(t, cmd.Flags().Set(alphabetSizeFlag, "0"))
|
require.NoError(t, cmd.Flags().Set(commonflags.AlphabetSizeFlag, "0"))
|
||||||
buf.WriteString("pass\r")
|
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) {
|
t.Run("no password provided", func(t *testing.T) {
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
v.Set(alphabetWalletsFlag, walletDir)
|
v.Set(commonflags.AlphabetWalletsFlag, walletDir)
|
||||||
require.NoError(t, cmd.Flags().Set(alphabetSizeFlag, "1"))
|
require.NoError(t, cmd.Flags().Set(commonflags.AlphabetSizeFlag, "1"))
|
||||||
require.Error(t, generateAlphabetCreds(cmd, nil))
|
require.Error(t, AlphabetCreds(cmd, nil))
|
||||||
})
|
})
|
||||||
t.Run("missing directory", func(t *testing.T) {
|
t.Run("missing directory", func(t *testing.T) {
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
dir := filepath.Join(os.TempDir(), "notexist."+strconv.FormatUint(rand.Uint64(), 10))
|
dir := filepath.Join(os.TempDir(), "notexist."+strconv.FormatUint(rand.Uint64(), 10))
|
||||||
v.Set(alphabetWalletsFlag, dir)
|
v.Set(commonflags.AlphabetWalletsFlag, dir)
|
||||||
require.NoError(t, cmd.Flags().Set(alphabetSizeFlag, "1"))
|
require.NoError(t, cmd.Flags().Set(commonflags.AlphabetSizeFlag, "1"))
|
||||||
buf.WriteString("pass\r")
|
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) {
|
t.Run("no password for contract group wallet", func(t *testing.T) {
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
v.Set(alphabetWalletsFlag, walletDir)
|
v.Set(commonflags.AlphabetWalletsFlag, walletDir)
|
||||||
require.NoError(t, cmd.Flags().Set(alphabetSizeFlag, strconv.FormatUint(size, 10)))
|
require.NoError(t, cmd.Flags().Set(commonflags.AlphabetSizeFlag, strconv.FormatUint(size, 10)))
|
||||||
for i := uint64(0); i < size; i++ {
|
for i := uint64(0); i < size; i++ {
|
||||||
buf.WriteString(strconv.FormatUint(i, 10) + "\r")
|
buf.WriteString(strconv.FormatUint(i, 10) + "\r")
|
||||||
}
|
}
|
||||||
require.Error(t, generateAlphabetCreds(cmd, nil))
|
require.Error(t, AlphabetCreds(cmd, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
v.Set(alphabetWalletsFlag, walletDir)
|
v.Set(commonflags.AlphabetWalletsFlag, walletDir)
|
||||||
require.NoError(t, generateAlphabetCmd.Flags().Set(alphabetSizeFlag, strconv.FormatUint(size, 10)))
|
require.NoError(t, GenerateAlphabetCmd.Flags().Set(commonflags.AlphabetSizeFlag, strconv.FormatUint(size, 10)))
|
||||||
for i := uint64(0); i < size; i++ {
|
for i := uint64(0); i < size; i++ {
|
||||||
buf.WriteString(strconv.FormatUint(i, 10) + "\r")
|
buf.WriteString(strconv.FormatUint(i, 10) + "\r")
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.WriteString(testContractPassword + "\r")
|
buf.WriteString(constants.TestContractPassword + "\r")
|
||||||
require.NoError(t, generateAlphabetCreds(generateAlphabetCmd, nil))
|
require.NoError(t, AlphabetCreds(GenerateAlphabetCmd, nil))
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
for i := uint64(0); i < size; i++ {
|
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())
|
err := a.Decrypt(strconv.FormatUint(i, 10), keys.NEP2ScryptParams())
|
||||||
require.NoError(t, err, "can't decrypt account")
|
require.NoError(t, err, "can't decrypt account")
|
||||||
switch a.Label {
|
switch a.Label {
|
||||||
case consensusAccountName:
|
case constants.ConsensusAccountName:
|
||||||
require.Equal(t, smartcontract.GetDefaultHonestNodeCount(size), len(a.Contract.Parameters))
|
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))
|
require.Equal(t, smartcontract.GetMajorityHonestNodeCount(size), len(a.Contract.Parameters))
|
||||||
default:
|
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()
|
wg.Wait()
|
||||||
|
|
||||||
t.Run("check contract group wallet", func(t *testing.T) {
|
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)
|
w, err := wallet.NewWalletFromFile(p)
|
||||||
require.NoError(t, err, "contract wallet doesn't exist")
|
require.NoError(t, err, "contract wallet doesn't exist")
|
||||||
require.Equal(t, 1, len(w.Accounts), "contract wallet must have 1 accout")
|
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 (
|
import (
|
||||||
"context"
|
"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
|
|||||||
|
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 (
|
import (
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
|
@ -8,6 +8,7 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core"
|
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||||
|
@ -38,7 +39,7 @@ import (
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type localClient struct {
|
type LocalClient struct {
|
||||||
bc *core.Blockchain
|
bc *core.Blockchain
|
||||||
transactions []*transaction.Transaction
|
transactions []*transaction.Transaction
|
||||||
dumpPath string
|
dumpPath string
|
||||||
|
@ -46,8 +47,8 @@ type localClient struct {
|
||||||
maxGasInvoke int64
|
maxGasInvoke int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func newLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet, dumpPath string) (*localClient, error) {
|
func NewLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet, dumpPath string) (*LocalClient, error) {
|
||||||
cfg, err := config.LoadFile(v.GetString(protoConfigPath))
|
cfg, err := config.LoadFile(v.GetString(constants.ProtoConfigPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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))
|
m := smartcontract.GetDefaultHonestNodeCount(int(cfg.ProtocolConfiguration.ValidatorsCount))
|
||||||
accounts := make([]*wallet.Account, len(wallets))
|
accounts := make([]*wallet.Account, len(wallets))
|
||||||
for i := range accounts {
|
for i := range accounts {
|
||||||
accounts[i], err = getWalletAccount(wallets[i], consensusAccountName)
|
accounts[i], err = GetWalletAccount(wallets[i], constants.ConsensusAccountName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -102,7 +103,7 @@ func newLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &localClient{
|
return &LocalClient{
|
||||||
bc: bc,
|
bc: bc,
|
||||||
dumpPath: dumpPath,
|
dumpPath: dumpPath,
|
||||||
accounts: accounts[:m],
|
accounts: accounts[:m],
|
||||||
|
@ -110,15 +111,15 @@ func newLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *localClient) GetBlockCount() (uint32, error) {
|
func (l *LocalClient) GetBlockCount() (uint32, error) {
|
||||||
return l.bc.BlockHeight(), nil
|
return l.bc.BlockHeight(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *localClient) GetNativeContracts() ([]state.NativeContract, error) {
|
func (l *LocalClient) GetNativeContracts() ([]state.NativeContract, error) {
|
||||||
return l.bc.GetNatives(), nil
|
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)
|
aer, err := l.bc.GetAppExecResults(h, *t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -128,13 +129,13 @@ func (l *localClient) GetApplicationLog(h util.Uint256, t *trigger.Type) (*resul
|
||||||
return &a, nil
|
return &a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *localClient) GetCommittee() (keys.PublicKeys, error) {
|
func (l *LocalClient) GetCommittee() (keys.PublicKeys, error) {
|
||||||
// not used by `morph init` command
|
// not used by `morph init` command
|
||||||
panic("unexpected call")
|
panic("unexpected call")
|
||||||
}
|
}
|
||||||
|
|
||||||
// InvokeFunction is implemented via `InvokeScript`.
|
// 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
|
var err error
|
||||||
|
|
||||||
pp := make([]any, len(sPrm))
|
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
|
// not used by `morph init` command
|
||||||
panic("unexpected call")
|
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
|
// not used by `morph init` command
|
||||||
panic("unexpected call")
|
panic("unexpected call")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVersion return default version.
|
// GetVersion return default version.
|
||||||
func (l *localClient) GetVersion() (*result.Version, error) {
|
func (l *LocalClient) GetVersion() (*result.Version, error) {
|
||||||
c := l.bc.GetConfig()
|
c := l.bc.GetConfig()
|
||||||
return &result.Version{
|
return &result.Version{
|
||||||
Protocol: result.Protocol{
|
Protocol: result.Protocol{
|
||||||
|
@ -180,7 +181,7 @@ func (l *localClient) GetVersion() (*result.Version, error) {
|
||||||
}, nil
|
}, 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
|
// not used by `morph init` command
|
||||||
panic("unexpected call")
|
panic("unexpected call")
|
||||||
}
|
}
|
||||||
|
@ -188,7 +189,7 @@ func (l *localClient) InvokeContractVerify(util.Uint160, []smartcontract.Paramet
|
||||||
// CalculateNetworkFee calculates network fee for the given transaction.
|
// CalculateNetworkFee calculates network fee for the given transaction.
|
||||||
// Copied from neo-go with minor corrections (no need to support non-notary mode):
|
// 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
|
// 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.
|
// Avoid setting hash for this tx: server code doesn't touch client transaction.
|
||||||
data := tx.Bytes()
|
data := tx.Bytes()
|
||||||
tx, err := transaction.NewTransactionFromBytes(data)
|
tx, err := transaction.NewTransactionFromBytes(data)
|
||||||
|
@ -259,7 +260,7 @@ func (l *localClient) CalculateNetworkFee(tx *transaction.Transaction) (int64, e
|
||||||
return netFee, nil
|
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())
|
lastBlock, err := l.bc.GetBlock(l.bc.CurrentBlockHash())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -295,7 +296,7 @@ func (l *localClient) InvokeScript(script []byte, signers []transaction.Signer)
|
||||||
}, nil
|
}, 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.
|
// We need to test that transaction was formed correctly to catch as many errors as we can.
|
||||||
bs := tx.Bytes()
|
bs := tx.Bytes()
|
||||||
_, err := transaction.NewTransactionFromBytes(bs)
|
_, err := transaction.NewTransactionFromBytes(bs)
|
||||||
|
@ -307,7 +308,7 @@ func (l *localClient) SendRawTransaction(tx *transaction.Transaction) (util.Uint
|
||||||
return tx.Hash(), nil
|
return tx.Hash(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *localClient) putTransactions() error {
|
func (l *LocalClient) putTransactions() error {
|
||||||
// 1. Prepare new block.
|
// 1. Prepare new block.
|
||||||
lastBlock, err := l.bc.GetBlock(l.bc.CurrentBlockHash())
|
lastBlock, err := l.bc.GetBlock(l.bc.CurrentBlockHash())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -349,7 +350,7 @@ func (l *localClient) putTransactions() error {
|
||||||
return l.bc.AddBlock(b)
|
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()
|
w := io.NewBufBinWriter()
|
||||||
emit.Array(w.BinWriter, parameters...)
|
emit.Array(w.BinWriter, parameters...)
|
||||||
emit.AppCallNoArgs(w.BinWriter, h, method, callflag.All)
|
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")
|
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)))
|
arr, err := unwrap.Array(inv.Call(h, "getDesignatedByRole", int64(role), int64(u)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errGetDesignatedByRoleResponse
|
return nil, errGetDesignatedByRoleResponse
|
||||||
|
@ -382,7 +383,7 @@ func getDesignatedByRole(inv *invoker.Invoker, h util.Uint160, role noderoles.Ro
|
||||||
return pubs, nil
|
return pubs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *localClient) dump() (err error) {
|
func (l *LocalClient) Dump() (err error) {
|
||||||
defer l.bc.Close()
|
defer l.bc.Close()
|
||||||
|
|
||||||
f, err := os.Create(l.dumpPath)
|
f, err := os.Create(l.dumpPath)
|
|
@ -1,4 +1,4 @@
|
||||||
package morph
|
package helper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"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/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"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/crypto/keys"
|
||||||
|
@ -34,19 +35,19 @@ type Client interface {
|
||||||
CalculateNetworkFee(tx *transaction.Transaction) (int64, error)
|
CalculateNetworkFee(tx *transaction.Transaction) (int64, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type hashVUBPair struct {
|
type HashVUBPair struct {
|
||||||
hash util.Uint256
|
Hash util.Uint256
|
||||||
vub uint32
|
Vub uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
type clientContext struct {
|
type ClientContext struct {
|
||||||
Client Client // a raw neo-go client OR a local chain implementation
|
Client Client // a raw neo-go client OR a local chain implementation
|
||||||
CommitteeAct *actor.Actor // committee actor with the Global witness scope
|
CommitteeAct *actor.Actor // committee actor with the Global witness scope
|
||||||
ReadOnlyInvoker *invoker.Invoker // R/O contract invoker, does not contain any signer
|
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
|
// number of opened connections
|
||||||
// by neo-go client per one host
|
// by neo-go client per one host
|
||||||
const (
|
const (
|
||||||
|
@ -55,7 +56,7 @@ func getN3Client(v *viper.Viper) (Client, error) {
|
||||||
)
|
)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
endpoint := v.GetString(endpointFlag)
|
endpoint := v.GetString(commonflags.EndpointFlag)
|
||||||
if endpoint == "" {
|
if endpoint == "" {
|
||||||
return nil, errors.New("missing endpoint")
|
return nil, errors.New("missing endpoint")
|
||||||
}
|
}
|
||||||
|
@ -72,20 +73,20 @@ func getN3Client(v *viper.Viper) (Client, error) {
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultClientContext(c Client, committeeAcc *wallet.Account) (*clientContext, error) {
|
func DefaultClientContext(c Client, committeeAcc *wallet.Account) (*ClientContext, error) {
|
||||||
commAct, err := newActor(c, committeeAcc)
|
commAct, err := NewActor(c, committeeAcc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &clientContext{
|
return &ClientContext{
|
||||||
Client: c,
|
Client: c,
|
||||||
CommitteeAct: commAct,
|
CommitteeAct: commAct,
|
||||||
ReadOnlyInvoker: invoker.New(c, nil),
|
ReadOnlyInvoker: invoker.New(c, nil),
|
||||||
}, 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)
|
h, err := c.Client.SendRawTransaction(tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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())
|
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 {
|
if await {
|
||||||
return c.awaitTx(cmd)
|
return c.AwaitTx(cmd)
|
||||||
}
|
}
|
||||||
return nil
|
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 (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"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/native"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"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/core/transaction"
|
||||||
|
@ -27,8 +29,8 @@ const (
|
||||||
registerBatchSize = transaction.MaxAttributes - 1
|
registerBatchSize = transaction.MaxAttributes - 1
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *initializeContext) registerCandidateRange(start, end int) error {
|
func registerCandidateRange(c *helper.InitializeContext, start, end int) error {
|
||||||
regPrice, err := c.getCandidateRegisterPrice()
|
regPrice, err := getCandidateRegisterPrice(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't fetch registration price: %w", err)
|
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{{
|
signers := []actor.SignerAccount{{
|
||||||
Signer: c.getSigner(false, c.CommitteeAcc),
|
Signer: c.GetSigner(false, c.CommitteeAcc),
|
||||||
Account: c.CommitteeAcc,
|
Account: c.CommitteeAcc,
|
||||||
}}
|
}}
|
||||||
for _, acc := range c.Accounts[start:end] {
|
for _, acc := range c.Accounts[start:end] {
|
||||||
|
@ -67,7 +69,7 @@ func (c *initializeContext) registerCandidateRange(start, end int) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't create tx: %w", err)
|
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)
|
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"))
|
cc, err := unwrap.Array(c.ReadOnlyInvoker.Call(neo.Hash, "getCandidates"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("`getCandidates`: %w", err)
|
return fmt.Errorf("`getCandidates`: %w", err)
|
||||||
|
@ -106,7 +108,7 @@ func (c *initializeContext) registerCandidates() error {
|
||||||
if have >= end {
|
if have >= end {
|
||||||
continue
|
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)
|
return fmt.Errorf("registering candidates %d..%d: %q", start, end-1, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,15 +116,15 @@ func (c *initializeContext) registerCandidates() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *initializeContext) transferNEOToAlphabetContracts() error {
|
func transferNEOToAlphabetContracts(c *helper.InitializeContext) error {
|
||||||
neoHash := neo.Hash
|
neoHash := neo.Hash
|
||||||
|
|
||||||
ok, err := c.transferNEOFinished(neoHash)
|
ok, err := transferNEOFinished(c, neoHash)
|
||||||
if ok || err != nil {
|
if ok || err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cs := c.getContract(alphabetContract)
|
cs := c.GetContract(constants.AlphabetContract)
|
||||||
amount := initialAlphabetNEOAmount / len(c.Wallets)
|
amount := initialAlphabetNEOAmount / len(c.Wallets)
|
||||||
|
|
||||||
bw := io.NewBufBinWriter()
|
bw := io.NewBufBinWriter()
|
||||||
|
@ -133,14 +135,14 @@ func (c *initializeContext) transferNEOToAlphabetContracts() error {
|
||||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
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 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)
|
r := nep17.NewReader(c.ReadOnlyInvoker, neoHash)
|
||||||
bal, err := r.BalanceOf(c.CommitteeAcc.Contract.ScriptHash())
|
bal, err := r.BalanceOf(c.CommitteeAcc.Contract.ScriptHash())
|
||||||
return bal.Cmp(big.NewInt(native.NEOTotalSupply)) == -1, err
|
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")
|
var errGetPriceInvalid = errors.New("`getRegisterPrice`: invalid response")
|
||||||
|
|
||||||
func (c *initializeContext) getCandidateRegisterPrice() (int64, error) {
|
func getCandidateRegisterPrice(c *helper.InitializeContext) (int64, error) {
|
||||||
switch c.Client.(type) {
|
switch c.Client.(type) {
|
||||||
case *rpcclient.Client:
|
case *rpcclient.Client:
|
||||||
inv := invoker.New(c.Client, nil)
|
inv := invoker.New(c.Client, nil)
|
||||||
|
@ -156,7 +158,7 @@ func (c *initializeContext) getCandidateRegisterPrice() (int64, error) {
|
||||||
return reader.GetRegisterPrice()
|
return reader.GetRegisterPrice()
|
||||||
default:
|
default:
|
||||||
neoHash := neo.Hash
|
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 {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package morph
|
package initialize
|
||||||
|
|
||||||
import (
|
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/core/native/noderoles"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/rolemgmt"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/rolemgmt"
|
||||||
|
@ -8,8 +9,8 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *initializeContext) setNotaryAndAlphabetNodes() error {
|
func setNotaryAndAlphabetNodes(c *helper.InitializeContext) error {
|
||||||
if ok, err := c.setRolesFinished(); ok || err != nil {
|
if ok, err := setRolesFinished(c); ok || err != nil {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.Command.Println("Stage 2: already performed.")
|
c.Command.Println("Stage 2: already performed.")
|
||||||
}
|
}
|
||||||
|
@ -27,19 +28,19 @@ func (c *initializeContext) setNotaryAndAlphabetNodes() error {
|
||||||
emit.AppCall(w.BinWriter, rolemgmt.Hash, "designateAsRole",
|
emit.AppCall(w.BinWriter, rolemgmt.Hash, "designateAsRole",
|
||||||
callflag.States|callflag.AllowNotify, int64(noderoles.NeoFSAlphabet), pubs)
|
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 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()
|
height, err := c.Client.GetBlockCount()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
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
|
return len(pubs) == len(c.Wallets), err
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package morph
|
package initialize
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
@ -9,6 +9,14 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"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"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
@ -42,10 +50,10 @@ func TestInitialize(t *testing.T) {
|
||||||
testInitialize(t, 16)
|
testInitialize(t, 16)
|
||||||
})
|
})
|
||||||
t.Run("max nodes", func(t *testing.T) {
|
t.Run("max nodes", func(t *testing.T) {
|
||||||
testInitialize(t, maxAlphabetNodes)
|
testInitialize(t, constants.MaxAlphabetNodes)
|
||||||
})
|
})
|
||||||
t.Run("too many nodes", func(t *testing.T) {
|
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()
|
v := viper.GetViper()
|
||||||
|
|
||||||
require.NoError(t, generateTestData(t, testdataDir, committeeSize))
|
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.
|
// 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")
|
dumpPath := filepath.Join(testdataDir, "out")
|
||||||
require.NoError(t, initCmd.Flags().Set(localDumpFlag, dumpPath))
|
require.NoError(t, Cmd.Flags().Set(commonflags.LocalDumpFlag, dumpPath))
|
||||||
v.Set(alphabetWalletsFlag, testdataDir)
|
v.Set(commonflags.AlphabetWalletsFlag, testdataDir)
|
||||||
v.Set(epochDurationInitFlag, 1)
|
v.Set(commonflags.EpochDurationInitFlag, 1)
|
||||||
v.Set(maxObjectSizeInitFlag, 1024)
|
v.Set(commonflags.MaxObjectSizeInitFlag, 1024)
|
||||||
|
|
||||||
setTestCredentials(v, committeeSize)
|
setTestCredentials(v, committeeSize)
|
||||||
require.NoError(t, initializeSideChainCmd(initCmd, nil))
|
require.NoError(t, initializeSideChainCmd(Cmd, nil))
|
||||||
|
|
||||||
t.Run("force-new-epoch", func(t *testing.T) {
|
t.Run("force-new-epoch", func(t *testing.T) {
|
||||||
require.NoError(t, forceNewEpoch.Flags().Set(localDumpFlag, dumpPath))
|
require.NoError(t, netmap.ForceNewEpoch.Flags().Set(commonflags.LocalDumpFlag, dumpPath))
|
||||||
require.NoError(t, forceNewEpochCmd(forceNewEpoch, nil))
|
require.NoError(t, netmap.ForceNewEpochCmd(netmap.ForceNewEpoch, nil))
|
||||||
})
|
})
|
||||||
t.Run("set-config", func(t *testing.T) {
|
t.Run("set-config", func(t *testing.T) {
|
||||||
require.NoError(t, setConfig.Flags().Set(localDumpFlag, dumpPath))
|
require.NoError(t, cmdConfig.SetCmd.Flags().Set(commonflags.LocalDumpFlag, dumpPath))
|
||||||
require.NoError(t, setConfigCmd(setConfig, []string{"MaintenanceModeAllowed=true"}))
|
require.NoError(t, cmdConfig.SetConfigCmd(cmdConfig.SetCmd, []string{"MaintenanceModeAllowed=true"}))
|
||||||
})
|
})
|
||||||
t.Run("set-policy", func(t *testing.T) {
|
t.Run("set-policy", func(t *testing.T) {
|
||||||
require.NoError(t, setPolicy.Flags().Set(localDumpFlag, dumpPath))
|
require.NoError(t, policy.Set.Flags().Set(commonflags.LocalDumpFlag, dumpPath))
|
||||||
require.NoError(t, setPolicyCmd(setPolicy, []string{"ExecFeeFactor=1"}))
|
require.NoError(t, policy.SetPolicyCmd(policy.Set, []string{"ExecFeeFactor=1"}))
|
||||||
})
|
})
|
||||||
t.Run("remove-node", func(t *testing.T) {
|
t.Run("remove-node", func(t *testing.T) {
|
||||||
pk, err := keys.NewPrivateKey()
|
pk, err := keys.NewPrivateKey()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
pub := hex.EncodeToString(pk.PublicKey().Bytes())
|
pub := hex.EncodeToString(pk.PublicKey().Bytes())
|
||||||
require.NoError(t, removeNodes.Flags().Set(localDumpFlag, dumpPath))
|
require.NoError(t, node.RemoveCmd.Flags().Set(commonflags.LocalDumpFlag, dumpPath))
|
||||||
require.NoError(t, removeNodesCmd(removeNodes, []string{pub}))
|
require.NoError(t, node.RemoveNodesCmd(node.RemoveCmd, []string{pub}))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateTestData(t *testing.T, dir string, size int) error {
|
func generateTestData(t *testing.T, dir string, size int) error {
|
||||||
v := viper.GetViper()
|
v := viper.GetViper()
|
||||||
v.Set(alphabetWalletsFlag, dir)
|
v.Set(commonflags.AlphabetWalletsFlag, dir)
|
||||||
|
|
||||||
sizeStr := strconv.FormatUint(uint64(size), 10)
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
setTestCredentials(v, size)
|
setTestCredentials(v, size)
|
||||||
if err := generateAlphabetCreds(generateAlphabetCmd, nil); err != nil {
|
if err := generate.AlphabetCreds(generate.GenerateAlphabetCmd, nil); err != nil {
|
||||||
return err
|
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)
|
return fmt.Errorf("wallet doesn't exist: %w", err)
|
||||||
}
|
}
|
||||||
for _, acc := range w.Accounts {
|
for _, acc := range w.Accounts {
|
||||||
if acc.Label == singleAccountName {
|
if acc.Label == constants.SingleAccountName {
|
||||||
pub, ok := vm.ParseSignatureContract(acc.Contract.Script)
|
pub, ok := vm.ParseSignatureContract(acc.Contract.Script)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("could not parse signature script for %s", acc.Address)
|
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++ {
|
for i := 0; i < size; i++ {
|
||||||
v.Set("credentials."+innerring.GlagoliticLetter(i).String(), strconv.FormatUint(uint64(i), 10))
|
v.Set("credentials."+innerring.GlagoliticLetter(i).String(), strconv.FormatUint(uint64(i), 10))
|
||||||
}
|
}
|
||||||
v.Set("credentials.contract", testContractPassword)
|
v.Set("credentials.contract", constants.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)
|
|
||||||
}
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
package morph
|
package initialize
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"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/native"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"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/neo"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
"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/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"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/opcode"
|
||||||
|
@ -27,8 +28,8 @@ const (
|
||||||
initialProxyGASAmount = 50_000 * native.GASFactor
|
initialProxyGASAmount = 50_000 * native.GASFactor
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *initializeContext) transferFunds() error {
|
func transferFunds(c *helper.InitializeContext) error {
|
||||||
ok, err := c.transferFundsFinished()
|
ok, err := transferFundsFinished(c)
|
||||||
if ok || err != nil {
|
if ok || err != nil {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.Command.Println("Stage 1: already performed.")
|
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)
|
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 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]
|
acc := c.Accounts[0]
|
||||||
|
|
||||||
r := nep17.NewReader(c.ReadOnlyInvoker, gas.Hash)
|
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
|
return res.Cmp(big.NewInt(initialAlphabetGASAmount/2)) == 1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *initializeContext) multiSignAndSend(tx *transaction.Transaction, accType string) error {
|
func transferGASToProxy(c *helper.InitializeContext) error {
|
||||||
if err := c.multiSign(tx, accType); err != nil {
|
proxyCs := c.GetContract(constants.ProxyContract)
|
||||||
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)
|
|
||||||
|
|
||||||
r := nep17.NewReader(c.ReadOnlyInvoker, gas.Hash)
|
r := nep17.NewReader(c.ReadOnlyInvoker, gas.Hash)
|
||||||
bal, err := r.BalanceOf(proxyCs.Hash)
|
bal, err := r.BalanceOf(proxyCs.Hash)
|
||||||
|
@ -160,11 +101,11 @@ func (c *initializeContext) transferGASToProxy() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.multiSignAndSend(tx, committeeAccountName); err != nil {
|
if err := c.MultiSignAndSend(tx, constants.CommitteeAccountName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.awaitTx()
|
return c.AwaitTx()
|
||||||
}
|
}
|
||||||
|
|
||||||
type transferTarget struct {
|
type transferTarget struct {
|
||||||
|
@ -174,7 +115,7 @@ type transferTarget struct {
|
||||||
Data any
|
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()
|
from := acc.Contract.ScriptHash()
|
||||||
|
|
||||||
w := io.NewBufBinWriter()
|
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"
|
||||||
|
"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 (
|
import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
"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"
|
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||||
|
@ -11,7 +13,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func listNetmapCandidatesNodes(cmd *cobra.Command, _ []string) {
|
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)
|
commonCmd.ExitOnErr(cmd, "can't create N3 client: %w", err)
|
||||||
|
|
||||||
inv := invoker.New(c, nil)
|
inv := invoker.New(c, nil)
|
||||||
|
@ -20,7 +22,7 @@ func listNetmapCandidatesNodes(cmd *cobra.Command, _ []string) {
|
||||||
cs, err := r.GetContractByID(1)
|
cs, err := r.GetContractByID(1)
|
||||||
commonCmd.ExitOnErr(cmd, "can't get NNS contract info: %w", err)
|
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)
|
commonCmd.ExitOnErr(cmd, "can't get netmap contract hash: %w", err)
|
||||||
|
|
||||||
res, err := inv.Call(nmHash, "netmapCandidates")
|
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))
|
||||||
|
_ = 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 (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
netmapcontract "git.frostfs.info/TrueCloudLab/frostfs-contract/netmap"
|
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/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"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/management"
|
||||||
|
@ -14,7 +16,7 @@ import (
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
func removeNodesCmd(cmd *cobra.Command, args []string) error {
|
func RemoveNodesCmd(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return errors.New("at least one node key must be provided")
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("can't initialize context: %w", err)
|
return fmt.Errorf("can't initialize context: %w", err)
|
||||||
}
|
}
|
||||||
defer wCtx.close()
|
defer wCtx.Close()
|
||||||
|
|
||||||
r := management.NewReader(wCtx.ReadOnlyInvoker)
|
r := management.NewReader(wCtx.ReadOnlyInvoker)
|
||||||
cs, err := r.GetContractByID(1)
|
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)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
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())
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := wCtx.sendConsensusTx(bw.Bytes()); err != nil {
|
if err := wCtx.SendConsensusTx(bw.Bytes()); err != nil {
|
||||||
return err
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"strconv"
|
"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/cli/input"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"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/crypto/keys"
|
||||||
|
@ -20,9 +22,14 @@ import (
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
// defaultNotaryDepositLifetime is an amount of blocks notary deposit stays valid.
|
const (
|
||||||
// https://github.com/nspcc-dev/neo-go/blob/master/pkg/core/native/notary.go#L48
|
// defaultNotaryDepositLifetime is an amount of blocks notary deposit stays valid.
|
||||||
const defaultNotaryDepositLifetime = 5760
|
// 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 {
|
func depositNotary(cmd *cobra.Command, _ []string) error {
|
||||||
w, err := openWallet(cmd)
|
w, err := openWallet(cmd)
|
||||||
|
@ -54,11 +61,11 @@ func depositNotary(cmd *cobra.Command, _ []string) error {
|
||||||
return fmt.Errorf("can't unlock account: %v", err)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
gasAmount, err := parseGASAmount(gasStr)
|
gasAmount, err := helper.ParseGASAmount(gasStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := checkNotaryEnabled(c); err != nil {
|
if err := helper.CheckNotaryEnabled(c); err != nil {
|
||||||
return err
|
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 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) {
|
func openWallet(cmd *cobra.Command) (*wallet.Wallet, error) {
|
||||||
p, err := cmd.Flags().GetString(storageWalletFlag)
|
p, err := cmd.Flags().GetString(commonflags.StorageWalletFlag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if p == "" {
|
} 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)
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -7,6 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"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"
|
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/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||||
|
@ -23,8 +24,8 @@ const (
|
||||||
setFeeParam = "FeePerByte"
|
setFeeParam = "FeePerByte"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setPolicyCmd(cmd *cobra.Command, args []string) error {
|
func SetPolicyCmd(cmd *cobra.Command, args []string) error {
|
||||||
wCtx, err := newInitializeContext(cmd, viper.GetViper())
|
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't to initialize context: %w", err)
|
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))
|
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 err
|
||||||
}
|
}
|
||||||
|
|
||||||
return wCtx.awaitTx()
|
return wCtx.AwaitTx()
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpPolicyCmd(cmd *cobra.Command, _ []string) error {
|
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)
|
commonCmd.ExitOnErr(cmd, "can't create N3 client:", err)
|
||||||
|
|
||||||
inv := invoker.New(c, nil)
|
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 (
|
import (
|
||||||
"fmt"
|
"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"
|
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/encoding/address"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"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 {
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("can't to initialize context: %w", err)
|
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)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("can't get proxy contract hash: %w", err)
|
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()
|
bw := io.NewBufBinWriter()
|
||||||
emit.AppCall(bw.BinWriter, proxyHash, method, callflag.All, addr)
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = wCtx.awaitTx(); err != nil {
|
if err = wCtx.AwaitTx(); err != nil {
|
||||||
return err
|
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
|
package morph
|
||||||
|
|
||||||
import (
|
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/cobra"
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
// RootCmd is a root command of config section.
|
||||||
alphabetWalletsFlag = "alphabet-wallets"
|
var RootCmd = &cobra.Command{
|
||||||
alphabetWalletsFlagDesc = "Path to alphabet wallets dir"
|
Use: "morph",
|
||||||
alphabetSizeFlag = "size"
|
Short: "Section for morph network configuration commands",
|
||||||
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,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
initGenerateAlphabetCmd()
|
RootCmd.AddCommand(generate.RefillGasCmd)
|
||||||
initInitCmd()
|
RootCmd.AddCommand(initialize.Cmd)
|
||||||
initDeployCmd()
|
RootCmd.AddCommand(contract.DeployCmd)
|
||||||
initGenerateStorageCmd()
|
RootCmd.AddCommand(generate.GenerateStorageCmd)
|
||||||
initForceNewEpochCmd()
|
RootCmd.AddCommand(netmap.ForceNewEpoch)
|
||||||
initRemoveNodesCmd()
|
RootCmd.AddCommand(node.RemoveCmd)
|
||||||
initSetPolicyCmd()
|
RootCmd.AddCommand(policy.Set)
|
||||||
initDumpPolicyCmd()
|
RootCmd.AddCommand(policy.Dump)
|
||||||
initDumpContractHashesCmd()
|
RootCmd.AddCommand(contract.DumpHashesCmd)
|
||||||
initDumpNetworkConfigCmd()
|
RootCmd.AddCommand(config.SetCmd)
|
||||||
initSetConfigCmd()
|
RootCmd.AddCommand(config.DumpCmd)
|
||||||
initDumpBalancesCmd()
|
RootCmd.AddCommand(balance.DumpCmd)
|
||||||
initUpdateContractsCmd()
|
RootCmd.AddCommand(contract.UpdateCmd)
|
||||||
initDumpContainersCmd()
|
RootCmd.AddCommand(container.ListCmd)
|
||||||
initRestoreContainersCmd()
|
RootCmd.AddCommand(container.RestoreCmd)
|
||||||
initListContainersCmd()
|
RootCmd.AddCommand(container.DumpCmd)
|
||||||
initRefillGasCmd()
|
RootCmd.AddCommand(generate.GenerateAlphabetCmd)
|
||||||
initDepositoryNotaryCmd()
|
RootCmd.AddCommand(notary.DepositCmd)
|
||||||
initNetmapCandidatesCmd()
|
RootCmd.AddCommand(netmap.CandidatesCmd)
|
||||||
|
|
||||||
RootCmd.AddCommand(apeCmd)
|
RootCmd.AddCommand(ape.Cmd)
|
||||||
initAddRuleChainCmd()
|
RootCmd.AddCommand(proxy.AddAccountCmd)
|
||||||
initRemoveRuleChainCmd()
|
RootCmd.AddCommand(proxy.RemoveAccountCmd)
|
||||||
initListRuleChainsCmd()
|
|
||||||
initSetAdminCmd()
|
|
||||||
initGetAdminCmd()
|
|
||||||
|
|
||||||
initProxyAddAccount()
|
RootCmd.AddCommand(frostfsid.Cmd)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
It is used in multiple packages?
Yes, in
contract
andinitialize