forked from TrueCloudLab/frostfs-node
[#932] adm: Move update-contracts
to package contract
Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
This commit is contained in:
parent
e2557b2f0b
commit
76343f19e5
16 changed files with 498 additions and 481 deletions
|
@ -6,7 +6,8 @@ import (
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
var DumpHashesCmd = &cobra.Command{
|
var (
|
||||||
|
DumpHashesCmd = &cobra.Command{
|
||||||
Use: "dump-hashes",
|
Use: "dump-hashes",
|
||||||
Short: "Dump deployed contract hashes",
|
Short: "Dump deployed contract hashes",
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
|
@ -14,12 +15,31 @@ var DumpHashesCmd = &cobra.Command{
|
||||||
},
|
},
|
||||||
RunE: dumpContractHashes,
|
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() {
|
func initDumpContractHashesCmd() {
|
||||||
DumpHashesCmd.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc)
|
DumpHashesCmd.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc)
|
||||||
DumpHashesCmd.Flags().String(util.CustomZoneFlag, "", "Custom zone to search.")
|
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() {
|
func init() {
|
||||||
initDumpContractHashesCmd()
|
initDumpContractHashesCmd()
|
||||||
|
initUpdateContractsCmd()
|
||||||
}
|
}
|
||||||
|
|
196
cmd/frostfs-adm/internal/modules/morph/contract/update.go
Normal file
196
cmd/frostfs-adm/internal/modules/morph/contract/update.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -83,7 +83,7 @@ func deployContractCmd(cmd *cobra.Command, args []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
callHash := management.Hash
|
callHash := management.Hash
|
||||||
method := deployMethodName
|
method := util.DeployMethodName
|
||||||
zone, _ := cmd.Flags().GetString(util.CustomZoneFlag)
|
zone, _ := cmd.Flags().GetString(util.CustomZoneFlag)
|
||||||
domain := ctrName + "." + zone
|
domain := ctrName + "." + zone
|
||||||
isUpdate, _ := cmd.Flags().GetBool(updateFlag)
|
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)
|
return fmt.Errorf("can't fetch contract hash from NNS: %w", err)
|
||||||
}
|
}
|
||||||
callHash = cs.Hash
|
callHash = cs.Hash
|
||||||
method = updateMethodName
|
method = util.UpdateMethodName
|
||||||
} else {
|
} else {
|
||||||
cs.Hash = state.CreateContractHash(
|
cs.Hash = state.CreateContractHash(
|
||||||
c.CommitteeAcc.Contract.ScriptHash(),
|
c.CommitteeAcc.Contract.ScriptHash(),
|
||||||
|
|
|
@ -28,7 +28,6 @@ const (
|
||||||
groupNameFlag = "group-name"
|
groupNameFlag = "group-name"
|
||||||
groupIDFlag = "group-id"
|
groupIDFlag = "group-id"
|
||||||
|
|
||||||
frostfsIDAdminConfigKey = "frostfsid.admin"
|
|
||||||
rootNamespacePlaceholder = "<root>"
|
rootNamespacePlaceholder = "<root>"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/util"
|
||||||
"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 := util.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 := util.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 := util.GetFrostfsIDAdmin(v)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.False(t, found)
|
require.False(t, found)
|
||||||
})
|
})
|
||||||
|
|
|
@ -28,7 +28,7 @@ func initializeSideChainCmd(cmd *cobra.Command, _ []string) error {
|
||||||
|
|
||||||
// 3. Deploy NNS contract.
|
// 3. Deploy NNS contract.
|
||||||
cmd.Println("Stage 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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,252 +1,14 @@
|
||||||
package morph
|
package morph
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"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"
|
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/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/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/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 {
|
func deployContracts(c *morphUtil.InitializeContext) error {
|
||||||
alphaCs := c.GetContract(morphUtil.AlphabetContract)
|
alphaCs := c.GetContract(morphUtil.AlphabetContract)
|
||||||
|
|
||||||
|
@ -263,20 +25,20 @@ func deployContracts(c *morphUtil.InitializeContext) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
alphaCs.Manifest.Groups = baseGroups
|
alphaCs.Manifest.Groups = baseGroups
|
||||||
err := addManifestGroup(c.ContractWallet, ctrHash, alphaCs)
|
err := morphUtil.AddManifestGroup(c.ContractWallet, ctrHash, alphaCs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't sign manifest group: %v", err)
|
return fmt.Errorf("can't sign manifest group: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
keysParam = append(keysParam, acc.PrivateKey().PublicKey().Bytes())
|
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)
|
act, err := actor.NewSimple(c.Client, acc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not create actor: %w", err)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("can't deploy alphabet #%d contract: %w", i, err)
|
return fmt.Errorf("can't deploy alphabet #%d contract: %w", i, err)
|
||||||
}
|
}
|
||||||
|
@ -293,17 +55,17 @@ func deployContracts(c *morphUtil.InitializeContext) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err := addManifestGroup(c.ContractWallet, ctrHash, cs)
|
err := morphUtil.AddManifestGroup(c.ContractWallet, ctrHash, cs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't sign manifest group: %v", err)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: getting deploy params: %v", ctrName, err)
|
return fmt.Errorf("%s: getting deploy params: %v", ctrName, err)
|
||||||
}
|
}
|
||||||
params := getContractDeployParameters(cs, args)
|
params := morphUtil.GetContractDeployParameters(cs, args)
|
||||||
res, err := c.CommitteeAct.MakeCall(management.Hash, deployMethodName, params...)
|
res, err := c.CommitteeAct.MakeCall(management.Hash, morphUtil.DeployMethodName, params...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't deploy %s contract: %w", ctrName, err)
|
return fmt.Errorf("can't deploy %s contract: %w", ctrName, err)
|
||||||
}
|
}
|
||||||
|
@ -315,152 +77,3 @@ func deployContracts(c *morphUtil.InitializeContext) error {
|
||||||
|
|
||||||
return c.AwaitTx()
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ package morph
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
"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))
|
domain, int64(nns.TXT), address.Uint160ToString(expectedHash))
|
||||||
return c.SendCommitteeTx(w.Bytes(), true)
|
return c.SendCommitteeTx(w.Bytes(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
var errMissingNNSRecord = errors.New("missing NNS record")
|
|
||||||
|
|
|
@ -98,16 +98,6 @@ var (
|
||||||
return refillGas(cmd, util.RefillGasAmountFlag, false)
|
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() {
|
func init() {
|
||||||
|
@ -123,7 +113,7 @@ func init() {
|
||||||
RootCmd.AddCommand(config.SetCmd)
|
RootCmd.AddCommand(config.SetCmd)
|
||||||
RootCmd.AddCommand(config.DumpCmd)
|
RootCmd.AddCommand(config.DumpCmd)
|
||||||
RootCmd.AddCommand(balance.DumpCmd)
|
RootCmd.AddCommand(balance.DumpCmd)
|
||||||
initUpdateContractsCmd()
|
RootCmd.AddCommand(contract.UpdateCmd)
|
||||||
RootCmd.AddCommand(container.ListCmd)
|
RootCmd.AddCommand(container.ListCmd)
|
||||||
RootCmd.AddCommand(container.RestoreCmd)
|
RootCmd.AddCommand(container.RestoreCmd)
|
||||||
RootCmd.AddCommand(container.DumpCmd)
|
RootCmd.AddCommand(container.DumpCmd)
|
||||||
|
@ -148,15 +138,6 @@ func initRefillGasCmd() {
|
||||||
refillGasCmd.MarkFlagsMutuallyExclusive(walletAddressFlag, util.StorageWalletFlag)
|
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() {
|
func initGenerateStorageCmd() {
|
||||||
RootCmd.AddCommand(generateStorageCmd)
|
RootCmd.AddCommand(generateStorageCmd)
|
||||||
generateStorageCmd.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc)
|
generateStorageCmd.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc)
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -53,6 +53,9 @@ const (
|
||||||
FrostfsOpsEmail = "ops@frostfs.info"
|
FrostfsOpsEmail = "ops@frostfs.info"
|
||||||
|
|
||||||
DefaultExpirationTime = 10 * 365 * 24 * time.Hour / time.Second
|
DefaultExpirationTime = 10 * 365 * 24 * time.Hour / time.Second
|
||||||
|
|
||||||
|
DeployMethodName = "deploy"
|
||||||
|
UpdateMethodName = "update"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
169
cmd/frostfs-adm/internal/modules/morph/util/contract.go
Normal file
169
cmd/frostfs-adm/internal/modules/morph/util/contract.go
Normal file
|
@ -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()
|
||||||
|
}
|
35
cmd/frostfs-adm/internal/modules/morph/util/frostfsid.go
Normal file
35
cmd/frostfs-adm/internal/modules/morph/util/frostfsid.go
Normal file
|
@ -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)
|
||||||
|
}
|
|
@ -1,15 +1,14 @@
|
||||||
package morph
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"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/smartcontract/manifest"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"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()
|
priv := cw.Accounts[0].PrivateKey()
|
||||||
pub := priv.PublicKey()
|
pub := priv.PublicKey()
|
||||||
|
|
|
@ -6,6 +6,8 @@ import (
|
||||||
|
|
||||||
"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/management"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
"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/smartcontract/callflag"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
@ -14,6 +16,17 @@ import (
|
||||||
"github.com/spf13/viper"
|
"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 {
|
func GetDefaultNetmapContractConfigMap() map[string]any {
|
||||||
m := make(map[string]any)
|
m := make(map[string]any)
|
||||||
m[netmap.EpochDurationConfig] = viper.GetInt64(EpochDurationInitFlag)
|
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)
|
emit.AppCall(bw.BinWriter, nmHash, "newEpoch", callflag.All, newEpoch)
|
||||||
return bw.Err
|
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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue