From bc9dbb26ec0ca191b2b41e3f71caf6dbd7563346 Mon Sep 17 00:00:00 2001 From: Anton Nikiforov Date: Tue, 20 Feb 2024 11:36:36 +0300 Subject: [PATCH] [#932] adm: Add custom `Actor` to sign tx by all committee accounts Signed-off-by: Anton Nikiforov --- .../internal/modules/morph/ape/ape_util.go | 14 +- .../internal/modules/morph/helper/actor.go | 169 ++++++++++++++++++ .../internal/modules/morph/nns/helper.go | 9 +- .../internal/modules/morph/nns/root.go | 2 - 4 files changed, 177 insertions(+), 17 deletions(-) create mode 100644 cmd/frostfs-adm/internal/modules/morph/helper/actor.go diff --git a/cmd/frostfs-adm/internal/modules/morph/ape/ape_util.go b/cmd/frostfs-adm/internal/modules/morph/ape/ape_util.go index dccd8909..3a990fe1 100644 --- a/cmd/frostfs-adm/internal/modules/morph/ape/ape_util.go +++ b/cmd/frostfs-adm/internal/modules/morph/ape/ape_util.go @@ -11,7 +11,6 @@ import ( apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" morph "git.frostfs.info/TrueCloudLab/policy-engine/pkg/morph/policy" - "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" "github.com/nspcc-dev/neo-go/pkg/rpcclient/management" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/spf13/cobra" @@ -84,22 +83,19 @@ func parseChainName(cmd *cobra.Command) apechain.Name { return apeChainName } -func newPolicyContractInterface(cmd *cobra.Command) (*morph.ContractStorage, *actor.Actor) { - v := viper.GetViper() - c, err := helper.GetN3Client(v) +func newPolicyContractInterface(cmd *cobra.Command) (*morph.ContractStorage, *helper.LocalActor) { + c, err := helper.GetN3Client(viper.GetViper()) commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err) - committeeAcc := helper.GetComitteAcc(cmd, v) - ac, err := helper.NewActor(c, committeeAcc) + ac, err := helper.NewLocalActor(cmd, c) commonCmd.ExitOnErr(cmd, "can't create actor: %w", err) - inv := &ac.Invoker var ch util.Uint160 - r := management.NewReader(inv) + r := management.NewReader(ac.Invoker) nnsCs, err := r.GetContractByID(1) commonCmd.ExitOnErr(cmd, "can't get NNS contract state: %w", err) - ch, err = helper.NNSResolveHash(inv, nnsCs.Hash, helper.DomainOf(constants.PolicyContract)) + ch, err = helper.NNSResolveHash(ac.Invoker, nnsCs.Hash, helper.DomainOf(constants.PolicyContract)) commonCmd.ExitOnErr(cmd, "unable to resolve policy contract hash: %w", err) return morph.NewContractStorage(ac, ch), ac diff --git a/cmd/frostfs-adm/internal/modules/morph/helper/actor.go b/cmd/frostfs-adm/internal/modules/morph/helper/actor.go new file mode 100644 index 00000000..f920aa5b --- /dev/null +++ b/cmd/frostfs-adm/internal/modules/morph/helper/actor.go @@ -0,0 +1,169 @@ +package helper + +import ( + "fmt" + + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants" + commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" + "github.com/google/uuid" + "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "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/smartcontract/context" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/nspcc-dev/neo-go/pkg/wallet" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// LocalActor is a kludge, do not use it outside of the morph commands. +type LocalActor struct { + neoActor *actor.Actor + accounts []*wallet.Account + Invoker *invoker.Invoker +} + +// NewLocalActor create LocalActor with accounts form provided wallets. +// In case of empty wallets provided created actor with dummy account only for read operation. +func NewLocalActor(cmd *cobra.Command, c actor.RPCActor) (*LocalActor, error) { + walletDir := config.ResolveHomePath(viper.GetString(commonflags.AlphabetWalletsFlag)) + var act *actor.Actor + var accounts []*wallet.Account + if walletDir == "" { + account, err := wallet.NewAccount() + commonCmd.ExitOnErr(cmd, "unable to create dummy account: %w", err) + act, err = actor.New(c, []actor.SignerAccount{{ + Signer: transaction.Signer{ + Account: account.Contract.ScriptHash(), + Scopes: transaction.Global, + }, + Account: account, + }}) + if err != nil { + return nil, err + } + } else { + wallets, err := GetAlphabetWallets(viper.GetViper(), walletDir) + commonCmd.ExitOnErr(cmd, "unable to get alphabet wallets: %w", err) + + for _, w := range wallets { + acc, err := GetWalletAccount(w, constants.CommitteeAccountName) + commonCmd.ExitOnErr(cmd, "can't find committee account: %w", err) + accounts = append(accounts, acc) + } + act, err = actor.New(c, []actor.SignerAccount{{ + Signer: transaction.Signer{ + Account: accounts[0].Contract.ScriptHash(), + Scopes: transaction.Global, + }, + Account: accounts[0], + }}) + if err != nil { + return nil, err + } + } + return &LocalActor{ + neoActor: act, + accounts: accounts, + Invoker: &act.Invoker, + }, nil +} + +func (a *LocalActor) SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error) { + tx, err := a.neoActor.MakeCall(contract, method, params...) + if err != nil { + return util.Uint256{}, 0, err + } + err = a.resign(tx) + if err != nil { + return util.Uint256{}, 0, err + } + return a.neoActor.Send(tx) +} + +func (a *LocalActor) SendRun(script []byte) (util.Uint256, uint32, error) { + tx, err := a.neoActor.MakeRun(script) + if err != nil { + return util.Uint256{}, 0, err + } + err = a.resign(tx) + if err != nil { + return util.Uint256{}, 0, err + } + return a.neoActor.Send(tx) +} + +// resign is used to sign tx with committee accounts. +// Inside the methods `MakeCall` and `SendRun` of the NeoGO's actor transaction is signing by committee account, +// because actor uses committee wallet. +// But it is not enough, need to sign with another committee accounts. +func (a *LocalActor) resign(tx *transaction.Transaction) error { + if len(a.accounts[0].Contract.Parameters) > 1 { + // Use parameter context to avoid dealing with signature order. + network := a.neoActor.GetNetwork() + pc := context.NewParameterContext("", network, tx) + h := a.accounts[0].Contract.ScriptHash() + for _, acc := range a.accounts { + priv := acc.PrivateKey() + sign := priv.SignHashable(uint32(network), tx) + if err := pc.AddSignature(h, acc.Contract, priv.PublicKey(), sign); err != nil { + return fmt.Errorf("can't add signature: %w", err) + } + if len(pc.Items[h].Signatures) == len(acc.Contract.Parameters) { + break + } + } + + w, err := pc.GetWitness(h) + if err != nil { + return fmt.Errorf("incomplete signature: %w", err) + } + tx.Scripts[0] = *w + } + return nil +} + +func (a *LocalActor) Wait(h util.Uint256, vub uint32, err error) (*state.AppExecResult, error) { + return a.neoActor.Wait(h, vub, err) +} + +func (a *LocalActor) Sender() util.Uint160 { + return a.neoActor.Sender() +} + +func (a *LocalActor) Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error) { + return a.neoActor.Call(contract, operation, params...) +} + +func (a *LocalActor) CallAndExpandIterator(_ util.Uint160, _ string, _ int, _ ...any) (*result.Invoke, error) { + panic("unimplemented") +} + +func (a *LocalActor) TerminateSession(_ uuid.UUID) error { + panic("unimplemented") +} + +func (a *LocalActor) TraverseIterator(sessionID uuid.UUID, iterator *result.Iterator, num int) ([]stackitem.Item, error) { + return a.neoActor.TraverseIterator(sessionID, iterator, num) +} + +func (a *LocalActor) MakeRun(_ []byte) (*transaction.Transaction, error) { + panic("unimplemented") +} + +func (a *LocalActor) MakeUnsignedCall(_ util.Uint160, _ string, _ []transaction.Attribute, _ ...any) (*transaction.Transaction, error) { + panic("unimplemented") +} + +func (a *LocalActor) MakeUnsignedRun(_ []byte, _ []transaction.Attribute) (*transaction.Transaction, error) { + panic("unimplemented") +} + +func (a *LocalActor) MakeCall(_ util.Uint160, _ string, _ ...any) (*transaction.Transaction, error) { + panic("unimplemented") +} diff --git a/cmd/frostfs-adm/internal/modules/morph/nns/helper.go b/cmd/frostfs-adm/internal/modules/morph/nns/helper.go index 4c9c9e57..36c3dd2f 100644 --- a/cmd/frostfs-adm/internal/modules/morph/nns/helper.go +++ b/cmd/frostfs-adm/internal/modules/morph/nns/helper.go @@ -4,24 +4,21 @@ import ( client "git.frostfs.info/TrueCloudLab/frostfs-contract/rpcclient/nns" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper" commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" - "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" "github.com/nspcc-dev/neo-go/pkg/rpcclient/management" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/spf13/cobra" "github.com/spf13/viper" ) -func getRPCClient(cmd *cobra.Command) (*client.Contract, *actor.Actor, util.Uint160) { +func getRPCClient(cmd *cobra.Command) (*client.Contract, *helper.LocalActor, util.Uint160) { v := viper.GetViper() c, err := helper.GetN3Client(v) commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err) - committeeAcc := helper.GetComitteAcc(cmd, v) - ac, err := helper.NewActor(c, committeeAcc) + ac, err := helper.NewLocalActor(cmd, c) commonCmd.ExitOnErr(cmd, "can't create actor: %w", err) - inv := &ac.Invoker - r := management.NewReader(inv) + r := management.NewReader(ac.Invoker) nnsCs, err := r.GetContractByID(1) commonCmd.ExitOnErr(cmd, "can't get NNS contract state: %w", err) return client.New(ac, nnsCs.Hash), ac, nnsCs.Hash diff --git a/cmd/frostfs-adm/internal/modules/morph/nns/root.go b/cmd/frostfs-adm/internal/modules/morph/nns/root.go index 09133f93..e528e4b7 100644 --- a/cmd/frostfs-adm/internal/modules/morph/nns/root.go +++ b/cmd/frostfs-adm/internal/modules/morph/nns/root.go @@ -30,7 +30,6 @@ var ( Short: "List all registered domain names", PreRun: func(cmd *cobra.Command, _ []string) { _ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag)) - _ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag)) }, Run: listTokens, } @@ -75,7 +74,6 @@ var ( Short: "Returns domain record of the specified type", PreRun: func(cmd *cobra.Command, _ []string) { _ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag)) - _ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag)) }, Run: getRecords, }