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 }