From 76343f19e569f01949396a7a442f0004970cd67a Mon Sep 17 00:00:00 2001 From: Anton Nikiforov Date: Fri, 2 Feb 2024 09:49:03 +0300 Subject: [PATCH] [#932] adm: Move `update-contracts` to package `contract` Signed-off-by: Anton Nikiforov --- .../internal/modules/morph/contract/root.go | 36 +- .../internal/modules/morph/contract/update.go | 196 +++++++++ .../internal/modules/morph/deploy.go | 4 +- .../modules/morph/frostfsid/frostfsid.go | 1 - .../modules/morph/frostfsid/frostfsid_util.go | 24 -- .../morph/frostfsid/frostfsid_util_test.go | 7 +- .../internal/modules/morph/initialize.go | 2 +- .../modules/morph/initialize_deploy.go | 401 +----------------- .../internal/modules/morph/initialize_nns.go | 3 - .../internal/modules/morph/root.go | 21 +- .../internal/modules/morph/update.go | 22 - .../internal/modules/morph/util/const.go | 3 + .../internal/modules/morph/util/contract.go | 169 ++++++++ .../internal/modules/morph/util/frostfsid.go | 35 ++ .../modules/morph/{ => util}/group.go | 5 +- .../internal/modules/morph/util/netmap.go | 50 +++ 16 files changed, 498 insertions(+), 481 deletions(-) create mode 100644 cmd/frostfs-adm/internal/modules/morph/contract/update.go delete mode 100644 cmd/frostfs-adm/internal/modules/morph/update.go create mode 100644 cmd/frostfs-adm/internal/modules/morph/util/contract.go create mode 100644 cmd/frostfs-adm/internal/modules/morph/util/frostfsid.go rename cmd/frostfs-adm/internal/modules/morph/{ => util}/group.go (77%) diff --git a/cmd/frostfs-adm/internal/modules/morph/contract/root.go b/cmd/frostfs-adm/internal/modules/morph/contract/root.go index 8decfd4a3..71feb31cd 100644 --- a/cmd/frostfs-adm/internal/modules/morph/contract/root.go +++ b/cmd/frostfs-adm/internal/modules/morph/contract/root.go @@ -6,20 +6,40 @@ import ( "github.com/spf13/viper" ) -var DumpHashesCmd = &cobra.Command{ - Use: "dump-hashes", - Short: "Dump deployed contract hashes", - PreRun: func(cmd *cobra.Command, _ []string) { - _ = viper.BindPFlag(util.EndpointFlag, cmd.Flags().Lookup(util.EndpointFlag)) - }, - RunE: dumpContractHashes, -} +var ( + DumpHashesCmd = &cobra.Command{ + Use: "dump-hashes", + Short: "Dump deployed contract hashes", + PreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlag(util.EndpointFlag, cmd.Flags().Lookup(util.EndpointFlag)) + }, + RunE: dumpContractHashes, + } + UpdateCmd = &cobra.Command{ + Use: "update-contracts", + Short: "Update FrostFS contracts", + PreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlag(util.AlphabetWalletsFlag, cmd.Flags().Lookup(util.AlphabetWalletsFlag)) + _ = viper.BindPFlag(util.EndpointFlag, cmd.Flags().Lookup(util.EndpointFlag)) + }, + RunE: updateContracts, + } +) func initDumpContractHashesCmd() { DumpHashesCmd.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc) DumpHashesCmd.Flags().String(util.CustomZoneFlag, "", "Custom zone to search.") } +func initUpdateContractsCmd() { + UpdateCmd.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc) + UpdateCmd.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc) + UpdateCmd.Flags().String(util.ContractsInitFlag, "", util.ContractsInitFlagDesc) + UpdateCmd.Flags().String(util.ContractsURLFlag, "", util.ContractsURLFlagDesc) + UpdateCmd.MarkFlagsMutuallyExclusive(util.ContractsInitFlag, util.ContractsURLFlag) +} + func init() { initDumpContractHashesCmd() + initUpdateContractsCmd() } diff --git a/cmd/frostfs-adm/internal/modules/morph/contract/update.go b/cmd/frostfs-adm/internal/modules/morph/contract/update.go new file mode 100644 index 000000000..b1b0bf1b3 --- /dev/null +++ b/cmd/frostfs-adm/internal/modules/morph/contract/update.go @@ -0,0 +1,196 @@ +package contract + +import ( + "encoding/hex" + "errors" + "fmt" + "strings" + + "git.frostfs.info/TrueCloudLab/frostfs-contract/common" + "git.frostfs.info/TrueCloudLab/frostfs-contract/nns" + morphUtil "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/util" + 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 := morphUtil.NewInitializeContext(cmd, viper.GetViper()) + if err != nil { + return fmt.Errorf("initialization error: %w", err) + } + + if err := morphUtil.DeployNNS(wCtx, morphUtil.UpdateMethodName); err != nil { + return err + } + + return updateContractsInternal(wCtx) +} + +func updateContractsInternal(c *morphUtil.InitializeContext) error { + alphaCs := c.GetContract(morphUtil.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 *morphUtil.InitializeContext, nnsHash neoUtil.Uint160, w *io2.BufBinWriter, alphaCs *morphUtil.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 := morphUtil.NNSResolveHash(c.ReadOnlyInvoker, nnsHash, morphUtil.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 = morphUtil.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, morphUtil.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 *morphUtil.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 morphUtil.ContractList { + cs := c.GetContract(ctrName) + + method := morphUtil.UpdateMethodName + ctrHash, err := morphUtil.NNSResolveHash(c.ReadOnlyInvoker, nnsHash, morphUtil.DomainOf(ctrName)) + if err != nil { + if errors.Is(err, errMissingNNSRecord) { + // if contract not found we deploy it instead of update + method = morphUtil.DeployMethodName + } else { + return fmt.Errorf("can't resolve hash for contract update: %w", err) + } + } + + err = morphUtil.AddManifestGroup(c.ContractWallet, ctrHash, cs) + if err != nil { + return fmt.Errorf("can't sign manifest group: %v", err) + } + + invokeHash := management.Hash + if method == morphUtil.UpdateMethodName { + invokeHash = ctrHash + } + + args, err := morphUtil.GetContractDeployData(c, ctrName, keysParam, morphUtil.UpdateMethodName) + if err != nil { + return fmt.Errorf("%s: getting update params: %v", ctrName, err) + } + params := morphUtil.GetContractDeployParameters(cs, args) + res, err := c.CommitteeAct.MakeCall(invokeHash, method, params...) + if err != nil { + if method != morphUtil.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 == morphUtil.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 +} diff --git a/cmd/frostfs-adm/internal/modules/morph/deploy.go b/cmd/frostfs-adm/internal/modules/morph/deploy.go index a312b34cd..b5b2794ca 100644 --- a/cmd/frostfs-adm/internal/modules/morph/deploy.go +++ b/cmd/frostfs-adm/internal/modules/morph/deploy.go @@ -83,7 +83,7 @@ func deployContractCmd(cmd *cobra.Command, args []string) error { } callHash := management.Hash - method := deployMethodName + method := util.DeployMethodName zone, _ := cmd.Flags().GetString(util.CustomZoneFlag) domain := ctrName + "." + zone isUpdate, _ := cmd.Flags().GetBool(updateFlag) @@ -93,7 +93,7 @@ func deployContractCmd(cmd *cobra.Command, args []string) error { return fmt.Errorf("can't fetch contract hash from NNS: %w", err) } callHash = cs.Hash - method = updateMethodName + method = util.UpdateMethodName } else { cs.Hash = state.CreateContractHash( c.CommitteeAcc.Contract.ScriptHash(), diff --git a/cmd/frostfs-adm/internal/modules/morph/frostfsid/frostfsid.go b/cmd/frostfs-adm/internal/modules/morph/frostfsid/frostfsid.go index 76bbc7148..d40b09da3 100644 --- a/cmd/frostfs-adm/internal/modules/morph/frostfsid/frostfsid.go +++ b/cmd/frostfs-adm/internal/modules/morph/frostfsid/frostfsid.go @@ -28,7 +28,6 @@ const ( groupNameFlag = "group-name" groupIDFlag = "group-id" - frostfsIDAdminConfigKey = "frostfsid.admin" rootNamespacePlaceholder = "" ) diff --git a/cmd/frostfs-adm/internal/modules/morph/frostfsid/frostfsid_util.go b/cmd/frostfs-adm/internal/modules/morph/frostfsid/frostfsid_util.go index 99c04f57d..541a459c1 100644 --- a/cmd/frostfs-adm/internal/modules/morph/frostfsid/frostfsid_util.go +++ b/cmd/frostfs-adm/internal/modules/morph/frostfsid/frostfsid_util.go @@ -10,32 +10,8 @@ import ( "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/spf13/cobra" - "github.com/spf13/viper" ) -func GetFrostfsIDAdmin(v *viper.Viper) (util.Uint160, bool, error) { - admin := v.GetString(frostfsIDAdminConfigKey) - if admin == "" { - return util.Uint160{}, false, nil - } - - h, err := address.StringToUint160(admin) - if err == nil { - return h, true, nil - } - - h, err = util.Uint160DecodeStringLE(admin) - if err == nil { - return h, true, nil - } - - pk, err := keys.NewPublicKeyFromString(admin) - if err == nil { - return pk.GetScriptHash(), true, nil - } - return util.Uint160{}, true, fmt.Errorf("frostfsid: admin is invalid: '%s'", admin) -} - func getFrostfsIDSubjectKey(cmd *cobra.Command) *keys.PublicKey { subjKeyHex, _ := cmd.Flags().GetString(subjectKeyFlag) subjKey, err := keys.NewPublicKeyFromString(subjKeyHex) diff --git a/cmd/frostfs-adm/internal/modules/morph/frostfsid/frostfsid_util_test.go b/cmd/frostfs-adm/internal/modules/morph/frostfsid/frostfsid_util_test.go index 119e10d15..fa6d07bf2 100644 --- a/cmd/frostfs-adm/internal/modules/morph/frostfsid/frostfsid_util_test.go +++ b/cmd/frostfs-adm/internal/modules/morph/frostfsid/frostfsid_util_test.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "testing" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/util" "git.frostfs.info/TrueCloudLab/frostfs-node/internal/ape" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" @@ -30,7 +31,7 @@ func TestFrostfsIDConfig(t *testing.T) { v := viper.New() v.Set("frostfsid.admin", fmts[i]) - actual, found, err := GetFrostfsIDAdmin(v) + actual, found, err := util.GetFrostfsIDAdmin(v) require.NoError(t, err) require.True(t, found) require.Equal(t, pks[i].GetScriptHash(), actual) @@ -40,14 +41,14 @@ func TestFrostfsIDConfig(t *testing.T) { v := viper.New() v.Set("frostfsid.admin", "abc") - _, found, err := GetFrostfsIDAdmin(v) + _, found, err := util.GetFrostfsIDAdmin(v) require.Error(t, err) require.True(t, found) }) t.Run("missing key", func(t *testing.T) { v := viper.New() - _, found, err := GetFrostfsIDAdmin(v) + _, found, err := util.GetFrostfsIDAdmin(v) require.NoError(t, err) require.False(t, found) }) diff --git a/cmd/frostfs-adm/internal/modules/morph/initialize.go b/cmd/frostfs-adm/internal/modules/morph/initialize.go index d4219294c..30f7248cd 100644 --- a/cmd/frostfs-adm/internal/modules/morph/initialize.go +++ b/cmd/frostfs-adm/internal/modules/morph/initialize.go @@ -28,7 +28,7 @@ func initializeSideChainCmd(cmd *cobra.Command, _ []string) error { // 3. Deploy NNS contract. cmd.Println("Stage 3: deploy NNS contract.") - if err := deployNNS(initCtx, deployMethodName); err != nil { + if err := morphUtil.DeployNNS(initCtx, morphUtil.DeployMethodName); err != nil { return err } diff --git a/cmd/frostfs-adm/internal/modules/morph/initialize_deploy.go b/cmd/frostfs-adm/internal/modules/morph/initialize_deploy.go index 3a78f6042..4d5487419 100644 --- a/cmd/frostfs-adm/internal/modules/morph/initialize_deploy.go +++ b/cmd/frostfs-adm/internal/modules/morph/initialize_deploy.go @@ -1,252 +1,14 @@ package morph 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/frostfsid" morphUtil "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/util" - 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/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/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/spf13/viper" ) -var netmapConfigKeys = []string{ - netmap.EpochDurationConfig, - netmap.MaxObjectSizeConfig, - netmap.ContainerFeeConfig, - netmap.ContainerAliasFeeConfig, - netmap.IrCandidateFeeConfig, - netmap.WithdrawFeeConfig, - netmap.HomomorphicHashingDisabledKey, - netmap.MaintenanceModeAllowedConfig, -} - -const ( - updateMethodName = "update" - deployMethodName = "deploy" -) - -func deployNNS(c *morphUtil.InitializeContext, method string) error { - cs := c.GetContract(morphUtil.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 = 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 == 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", morphUtil.NNSContract, err) - } - - if err := c.MultiSignAndSend(tx, morphUtil.CommitteeAccountName); err != nil { - return fmt.Errorf("can't send deploy transaction: %w", err) - } - - return c.AwaitTx() -} - -func updateContractsInternal(c *morphUtil.InitializeContext) error { - alphaCs := c.GetContract(morphUtil.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 deployOrUpdateContracts(c *morphUtil.InitializeContext, 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 morphUtil.ContractList { - cs := c.GetContract(ctrName) - - method := updateMethodName - ctrHash, err := morphUtil.NNSResolveHash(c.ReadOnlyInvoker, nnsHash, morphUtil.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 = addManifestGroup(c.ContractWallet, 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 := getContractDeployData(c, 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 deployAlphabetAccounts(c *morphUtil.InitializeContext, nnsHash util.Uint160, w *io2.BufBinWriter, alphaCs *morphUtil.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 := morphUtil.NNSResolveHash(c.ReadOnlyInvoker, nnsHash, morphUtil.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 = 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, 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 deployContracts(c *morphUtil.InitializeContext) error { alphaCs := c.GetContract(morphUtil.AlphabetContract) @@ -263,20 +25,20 @@ func deployContracts(c *morphUtil.InitializeContext) error { } alphaCs.Manifest.Groups = baseGroups - err := addManifestGroup(c.ContractWallet, ctrHash, alphaCs) + err := morphUtil.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 := getContractDeployParameters(alphaCs, c.GetAlphabetDeployItems(i, len(c.Wallets))) + params := morphUtil.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...) + txHash, vub, err := act.SendCall(management.Hash, morphUtil.DeployMethodName, params...) if err != nil { return fmt.Errorf("can't deploy alphabet #%d contract: %w", i, err) } @@ -293,17 +55,17 @@ func deployContracts(c *morphUtil.InitializeContext) error { continue } - err := addManifestGroup(c.ContractWallet, ctrHash, cs) + err := morphUtil.AddManifestGroup(c.ContractWallet, ctrHash, cs) if err != nil { return fmt.Errorf("can't sign manifest group: %v", err) } - args, err := getContractDeployData(c, ctrName, keysParam, deployMethodName) + args, err := morphUtil.GetContractDeployData(c, ctrName, keysParam, morphUtil.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...) + params := morphUtil.GetContractDeployParameters(cs, args) + res, err := c.CommitteeAct.MakeCall(management.Hash, morphUtil.DeployMethodName, params...) if err != nil { return fmt.Errorf("can't deploy %s contract: %w", ctrName, err) } @@ -315,152 +77,3 @@ func deployContracts(c *morphUtil.InitializeContext) error { return c.AwaitTx() } - -func getContractDeployParameters(cs *morphUtil.ContractState, deployData []any) []any { - return []any{cs.RawNEF, cs.RawManifest, deployData} -} - -func getContractDeployData(c *morphUtil.InitializeContext, ctrName string, keysParam []any, method string) ([]any, error) { - items := make([]any, 0, 6) - - switch ctrName { - case morphUtil.FrostfsContract: - items = append(items, - c.Contracts[morphUtil.ProcessingContract].Hash, - keysParam, - smartcontract.Parameter{}) - case morphUtil.ProcessingContract: - items = append(items, c.Contracts[morphUtil.FrostfsContract].Hash) - return items[1:], nil // no notary info - case morphUtil.BalanceContract: - items = append(items, - c.Contracts[morphUtil.NetmapContract].Hash, - c.Contracts[morphUtil.ContainerContract].Hash) - case morphUtil.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[morphUtil.NetmapContract].Hash, - c.Contracts[morphUtil.BalanceContract].Hash, - c.Contracts[morphUtil.FrostfsIDContract].Hash, - nnsCs.Hash, - "container") - case morphUtil.FrostfsIDContract: - var ( - h util.Uint160 - found bool - err error - ) - if method == updateMethodName { - h, found, err = getFrostfsIDAdminFromContract(c.ReadOnlyInvoker) - } - if method != updateMethodName || err == nil && !found { - h, found, err = frostfsid.GetFrostfsIDAdmin(viper.GetViper()) - } - if err != nil { - return nil, err - } - - if found { - items = append(items, h) - } else { - items = append(items, c.Contracts[morphUtil.ProxyContract].Hash) - } - case morphUtil.NetmapContract: - md := morphUtil.GetDefaultNetmapContractConfigMap() - if method == 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[morphUtil.BalanceContract].Hash, - c.Contracts[morphUtil.ContainerContract].Hash, - keysParam, - configParam) - case morphUtil.ProxyContract: - items = nil - case morphUtil.PolicyContract: - items = append(items, c.Contracts[morphUtil.ProxyContract].Hash) - default: - panic(fmt.Sprintf("invalid contract name: %s", ctrName)) - } - return items, nil -} - -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 := morphUtil.NNSResolveHash(roInvoker, cs.Hash, morphUtil.DomainOf(morphUtil.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 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 := morphUtil.NNSResolveHash(roInvoker, cs.Hash, morphUtil.DomainOf(morphUtil.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 := morphUtil.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 -} diff --git a/cmd/frostfs-adm/internal/modules/morph/initialize_nns.go b/cmd/frostfs-adm/internal/modules/morph/initialize_nns.go index 53435e67b..f943ac4a1 100644 --- a/cmd/frostfs-adm/internal/modules/morph/initialize_nns.go +++ b/cmd/frostfs-adm/internal/modules/morph/initialize_nns.go @@ -2,7 +2,6 @@ package morph import ( "encoding/hex" - "errors" "fmt" "git.frostfs.info/TrueCloudLab/frostfs-contract/nns" @@ -132,5 +131,3 @@ func nnsRegisterDomain(c *morphUtil.InitializeContext, nnsHash, expectedHash uti domain, int64(nns.TXT), address.Uint160ToString(expectedHash)) return c.SendCommitteeTx(w.Bytes(), true) } - -var errMissingNNSRecord = errors.New("missing NNS record") diff --git a/cmd/frostfs-adm/internal/modules/morph/root.go b/cmd/frostfs-adm/internal/modules/morph/root.go index 66dd77556..3af6f78a5 100644 --- a/cmd/frostfs-adm/internal/modules/morph/root.go +++ b/cmd/frostfs-adm/internal/modules/morph/root.go @@ -98,16 +98,6 @@ var ( return refillGas(cmd, util.RefillGasAmountFlag, false) }, } - - updateContractsCmd = &cobra.Command{ - Use: "update-contracts", - Short: "Update FrostFS contracts", - PreRun: func(cmd *cobra.Command, _ []string) { - _ = viper.BindPFlag(util.AlphabetWalletsFlag, cmd.Flags().Lookup(util.AlphabetWalletsFlag)) - _ = viper.BindPFlag(util.EndpointFlag, cmd.Flags().Lookup(util.EndpointFlag)) - }, - RunE: updateContracts, - } ) func init() { @@ -123,7 +113,7 @@ func init() { RootCmd.AddCommand(config.SetCmd) RootCmd.AddCommand(config.DumpCmd) RootCmd.AddCommand(balance.DumpCmd) - initUpdateContractsCmd() + RootCmd.AddCommand(contract.UpdateCmd) RootCmd.AddCommand(container.ListCmd) RootCmd.AddCommand(container.RestoreCmd) RootCmd.AddCommand(container.DumpCmd) @@ -148,15 +138,6 @@ func initRefillGasCmd() { refillGasCmd.MarkFlagsMutuallyExclusive(walletAddressFlag, util.StorageWalletFlag) } -func initUpdateContractsCmd() { - RootCmd.AddCommand(updateContractsCmd) - updateContractsCmd.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc) - updateContractsCmd.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc) - updateContractsCmd.Flags().String(util.ContractsInitFlag, "", util.ContractsInitFlagDesc) - updateContractsCmd.Flags().String(util.ContractsURLFlag, "", util.ContractsURLFlagDesc) - updateContractsCmd.MarkFlagsMutuallyExclusive(util.ContractsInitFlag, util.ContractsURLFlag) -} - func initGenerateStorageCmd() { RootCmd.AddCommand(generateStorageCmd) generateStorageCmd.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc) diff --git a/cmd/frostfs-adm/internal/modules/morph/update.go b/cmd/frostfs-adm/internal/modules/morph/update.go deleted file mode 100644 index 2d5b24712..000000000 --- a/cmd/frostfs-adm/internal/modules/morph/update.go +++ /dev/null @@ -1,22 +0,0 @@ -package morph - -import ( - "fmt" - - "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/util" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -func updateContracts(cmd *cobra.Command, _ []string) error { - wCtx, err := util.NewInitializeContext(cmd, viper.GetViper()) - if err != nil { - return fmt.Errorf("initialization error: %w", err) - } - - if err := deployNNS(wCtx, updateMethodName); err != nil { - return err - } - - return updateContractsInternal(wCtx) -} diff --git a/cmd/frostfs-adm/internal/modules/morph/util/const.go b/cmd/frostfs-adm/internal/modules/morph/util/const.go index cdf1cd443..a37afc3da 100644 --- a/cmd/frostfs-adm/internal/modules/morph/util/const.go +++ b/cmd/frostfs-adm/internal/modules/morph/util/const.go @@ -53,6 +53,9 @@ const ( FrostfsOpsEmail = "ops@frostfs.info" DefaultExpirationTime = 10 * 365 * 24 * time.Hour / time.Second + + DeployMethodName = "deploy" + UpdateMethodName = "update" ) var ( diff --git a/cmd/frostfs-adm/internal/modules/morph/util/contract.go b/cmd/frostfs-adm/internal/modules/morph/util/contract.go new file mode 100644 index 000000000..8faa49d1c --- /dev/null +++ b/cmd/frostfs-adm/internal/modules/morph/util/contract.go @@ -0,0 +1,169 @@ +package util + +import ( + "fmt" + + "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(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 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 = getFrostfsIDAdminFromContract(c.ReadOnlyInvoker) + } + 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 := 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[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 GetContractDeployParameters(cs *ContractState, deployData []any) []any { + return []any{cs.RawNEF, cs.RawManifest, deployData} +} + +func DeployNNS(c *InitializeContext, 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 = 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 == 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() +} diff --git a/cmd/frostfs-adm/internal/modules/morph/util/frostfsid.go b/cmd/frostfs-adm/internal/modules/morph/util/frostfsid.go new file mode 100644 index 000000000..7aea1c492 --- /dev/null +++ b/cmd/frostfs-adm/internal/modules/morph/util/frostfsid.go @@ -0,0 +1,35 @@ +package util + +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) +} diff --git a/cmd/frostfs-adm/internal/modules/morph/group.go b/cmd/frostfs-adm/internal/modules/morph/util/group.go similarity index 77% rename from cmd/frostfs-adm/internal/modules/morph/group.go rename to cmd/frostfs-adm/internal/modules/morph/util/group.go index 655b8b2c3..a0306f3bc 100644 --- a/cmd/frostfs-adm/internal/modules/morph/group.go +++ b/cmd/frostfs-adm/internal/modules/morph/util/group.go @@ -1,15 +1,14 @@ -package morph +package util import ( "encoding/json" - util2 "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/util" "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 *util2.ContractState) error { +func AddManifestGroup(cw *wallet.Wallet, h util.Uint160, cs *ContractState) error { priv := cw.Accounts[0].PrivateKey() pub := priv.PublicKey() diff --git a/cmd/frostfs-adm/internal/modules/morph/util/netmap.go b/cmd/frostfs-adm/internal/modules/morph/util/netmap.go index 34c527d0b..bfe8ea165 100644 --- a/cmd/frostfs-adm/internal/modules/morph/util/netmap.go +++ b/cmd/frostfs-adm/internal/modules/morph/util/netmap.go @@ -6,6 +6,8 @@ import ( "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" @@ -14,6 +16,17 @@ import ( "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(EpochDurationInitFlag) @@ -67,3 +80,40 @@ func EmitNewEpochCall(bw *io.BufBinWriter, wCtx *InitializeContext, nmHash util. 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(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 +}