frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper/actor.go
Airat Arifullin d336f2d487 [#1393] adm: Make NewLocalActor receive accout name
* Some RPC-clients for contracts require different wallet account types.
  Since, `Policy` contract gets `consensus` accounts while `NNS` gets
  `committee` accounts.

Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
2024-11-08 13:57:51 +00:00

178 lines
5.7 KiB
Go

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"
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
rpcInvoker invoker.RPCInvoke
}
// NewLocalActor create LocalActor with accounts form provided wallets.
// In case of empty wallets provided created actor with dummy account only for read operation.
//
// If wallets are provided, the contract client will use accounts with accName name from these wallets.
// To determine which account name should be used in a contract client, refer to how the contract
// verifies the transaction signature.
func NewLocalActor(cmd *cobra.Command, c actor.RPCActor, accName string) (*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, accName)
commonCmd.ExitOnErr(cmd, fmt.Sprintf("can't find %s account: %%w", accName), 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,
rpcInvoker: c,
}, 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")
}
func (a *LocalActor) GetRPCInvoker() invoker.RPCInvoke {
return a.rpcInvoker
}