Anton Nikiforov
802192cfef
All checks were successful
Vulncheck / Vulncheck (pull_request) Successful in 1m57s
DCO action / DCO (pull_request) Successful in 3m3s
Build / Build Components (1.21) (pull_request) Successful in 5m0s
Build / Build Components (1.20) (pull_request) Successful in 5m6s
Tests and linters / Tests (1.20) (pull_request) Successful in 7m1s
Tests and linters / Staticcheck (pull_request) Successful in 6m55s
Tests and linters / Lint (pull_request) Successful in 7m30s
Tests and linters / Tests (1.21) (pull_request) Successful in 7m53s
Tests and linters / Tests with -race (pull_request) Successful in 8m21s
To avoid conflicts with `util` packages in other imports. Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
197 lines
6.6 KiB
Go
197 lines
6.6 KiB
Go
package contract
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
|
morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
|
io2 "github.com/nspcc-dev/neo-go/pkg/io"
|
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
|
neoUtil "github.com/nspcc-dev/neo-go/pkg/util"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
var errMissingNNSRecord = errors.New("missing NNS record")
|
|
|
|
func updateContracts(cmd *cobra.Command, _ []string) error {
|
|
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
|
if err != nil {
|
|
return fmt.Errorf("initialization error: %w", err)
|
|
}
|
|
|
|
if err := helper.DeployNNS(wCtx, constants.UpdateMethodName); err != nil {
|
|
return err
|
|
}
|
|
|
|
return updateContractsInternal(wCtx)
|
|
}
|
|
|
|
func updateContractsInternal(c *helper.InitializeContext) error {
|
|
alphaCs := c.GetContract(constants.AlphabetContract)
|
|
|
|
nnsCs, err := c.NNSContractState()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nnsHash := nnsCs.Hash
|
|
|
|
w := io2.NewBufBinWriter()
|
|
|
|
// Update script size for a single-node committee is close to the maximum allowed size of 65535.
|
|
// Because of this we want to reuse alphabet contract NEF and manifest for different updates.
|
|
// The generated script is as following.
|
|
// 1. Initialize static slot for alphabet NEF.
|
|
// 2. Store NEF into the static slot.
|
|
// 3. Push parameters for each alphabet contract on stack.
|
|
// 4. Add contract group to the manifest.
|
|
// 5. For each alphabet contract, invoke `update` using parameters on stack and
|
|
// NEF from step 2 and manifest from step 4.
|
|
emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1})
|
|
emit.Bytes(w.BinWriter, alphaCs.RawNEF)
|
|
emit.Opcodes(w.BinWriter, opcode.STSFLD0)
|
|
|
|
keysParam, err := deployAlphabetAccounts(c, nnsHash, w, alphaCs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
w.Reset()
|
|
|
|
if err = deployOrUpdateContracts(c, w, nnsHash, keysParam); err != nil {
|
|
return err
|
|
}
|
|
|
|
groupKey := c.ContractWallet.Accounts[0].PrivateKey().PublicKey()
|
|
_, _, err = c.EmitUpdateNNSGroupScript(w, nnsHash, groupKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.Command.Printf("NNS: Set %s -> %s\n", morphClient.NNSGroupKeyName, hex.EncodeToString(groupKey.Bytes()))
|
|
|
|
emit.Opcodes(w.BinWriter, opcode.LDSFLD0)
|
|
emit.Int(w.BinWriter, 1)
|
|
emit.Opcodes(w.BinWriter, opcode.PACK)
|
|
emit.AppCallNoArgs(w.BinWriter, nnsHash, "setPrice", callflag.All)
|
|
|
|
if err := c.SendCommitteeTx(w.Bytes(), false); err != nil {
|
|
return err
|
|
}
|
|
return c.AwaitTx()
|
|
}
|
|
|
|
func deployAlphabetAccounts(c *helper.InitializeContext, nnsHash neoUtil.Uint160, w *io2.BufBinWriter, alphaCs *helper.ContractState) ([]any, error) {
|
|
var keysParam []any
|
|
|
|
baseGroups := alphaCs.Manifest.Groups
|
|
|
|
// alphabet contracts should be deployed by individual nodes to get different hashes.
|
|
for i, acc := range c.Accounts {
|
|
ctrHash, err := helper.NNSResolveHash(c.ReadOnlyInvoker, nnsHash, helper.GetAlphabetNNSDomain(i))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("can't resolve hash for contract update: %w", err)
|
|
}
|
|
|
|
keysParam = append(keysParam, acc.PrivateKey().PublicKey().Bytes())
|
|
|
|
params := c.GetAlphabetDeployItems(i, len(c.Wallets))
|
|
emit.Array(w.BinWriter, params...)
|
|
|
|
alphaCs.Manifest.Groups = baseGroups
|
|
err = helper.AddManifestGroup(c.ContractWallet, ctrHash, alphaCs)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("can't sign manifest group: %v", err)
|
|
}
|
|
|
|
emit.Bytes(w.BinWriter, alphaCs.RawManifest)
|
|
emit.Opcodes(w.BinWriter, opcode.LDSFLD0)
|
|
emit.Int(w.BinWriter, 3)
|
|
emit.Opcodes(w.BinWriter, opcode.PACK)
|
|
emit.AppCallNoArgs(w.BinWriter, ctrHash, constants.UpdateMethodName, callflag.All)
|
|
}
|
|
if err := c.SendCommitteeTx(w.Bytes(), false); err != nil {
|
|
if !strings.Contains(err.Error(), common.ErrAlreadyUpdated) {
|
|
return nil, err
|
|
}
|
|
c.Command.Println("Alphabet contracts are already updated.")
|
|
}
|
|
|
|
return keysParam, nil
|
|
}
|
|
|
|
func deployOrUpdateContracts(c *helper.InitializeContext, w *io2.BufBinWriter, nnsHash neoUtil.Uint160, keysParam []any) error {
|
|
emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1})
|
|
emit.AppCall(w.BinWriter, nnsHash, "getPrice", callflag.All)
|
|
emit.Opcodes(w.BinWriter, opcode.STSFLD0)
|
|
emit.AppCall(w.BinWriter, nnsHash, "setPrice", callflag.All, 1)
|
|
|
|
for _, ctrName := range constants.ContractList {
|
|
cs := c.GetContract(ctrName)
|
|
|
|
method := constants.UpdateMethodName
|
|
ctrHash, err := helper.NNSResolveHash(c.ReadOnlyInvoker, nnsHash, helper.DomainOf(ctrName))
|
|
if err != nil {
|
|
if errors.Is(err, errMissingNNSRecord) {
|
|
// if contract not found we deploy it instead of update
|
|
method = constants.DeployMethodName
|
|
} else {
|
|
return fmt.Errorf("can't resolve hash for contract update: %w", err)
|
|
}
|
|
}
|
|
|
|
err = helper.AddManifestGroup(c.ContractWallet, ctrHash, cs)
|
|
if err != nil {
|
|
return fmt.Errorf("can't sign manifest group: %v", err)
|
|
}
|
|
|
|
invokeHash := management.Hash
|
|
if method == constants.UpdateMethodName {
|
|
invokeHash = ctrHash
|
|
}
|
|
|
|
args, err := helper.GetContractDeployData(c, ctrName, keysParam, constants.UpdateMethodName)
|
|
if err != nil {
|
|
return fmt.Errorf("%s: getting update params: %v", ctrName, err)
|
|
}
|
|
params := helper.GetContractDeployParameters(cs, args)
|
|
res, err := c.CommitteeAct.MakeCall(invokeHash, method, params...)
|
|
if err != nil {
|
|
if method != constants.UpdateMethodName || !strings.Contains(err.Error(), common.ErrAlreadyUpdated) {
|
|
return fmt.Errorf("deploy contract: %w", err)
|
|
}
|
|
c.Command.Printf("%s contract is already updated.\n", ctrName)
|
|
continue
|
|
}
|
|
|
|
w.WriteBytes(res.Script)
|
|
|
|
if method == constants.DeployMethodName {
|
|
// same actions are done in InitializeContext.setNNS, can be unified
|
|
domain := ctrName + ".frostfs"
|
|
script, ok, err := c.NNSRegisterDomainScript(nnsHash, cs.Hash, domain)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !ok {
|
|
w.WriteBytes(script)
|
|
emit.AppCall(w.BinWriter, nnsHash, "deleteRecords", callflag.All, domain, int64(nns.TXT))
|
|
emit.AppCall(w.BinWriter, nnsHash, "addRecord", callflag.All,
|
|
domain, int64(nns.TXT), cs.Hash.StringLE())
|
|
emit.AppCall(w.BinWriter, nnsHash, "addRecord", callflag.All,
|
|
domain, int64(nns.TXT), address.Uint160ToString(cs.Hash))
|
|
}
|
|
c.Command.Printf("NNS: Set %s -> %s\n", domain, cs.Hash.StringLE())
|
|
}
|
|
}
|
|
return nil
|
|
}
|