From 37f9d083fbf1daa91d3dfef170e5cfcb79b0bcda Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Thu, 7 Apr 2022 11:10:35 +0300 Subject: [PATCH] [#722] neofs-adm: Hide N3 client behind an interface Signed-off-by: Evgenii Stratonikov --- .../internal/modules/morph/balance.go | 13 +++---- cmd/neofs-adm/internal/modules/morph/dump.go | 2 +- .../internal/modules/morph/initialize.go | 27 +++++++++++++- .../modules/morph/initialize_deploy.go | 17 +++------ .../internal/modules/morph/initialize_nns.go | 19 +++++++--- .../modules/morph/initialize_register.go | 23 ++++++------ .../modules/morph/initialize_transfer.go | 35 +++++++++++++++++-- .../internal/modules/morph/n3client.go | 31 ++++++++++++++-- .../internal/modules/morph/policy.go | 5 +-- 9 files changed, 126 insertions(+), 46 deletions(-) diff --git a/cmd/neofs-adm/internal/modules/morph/balance.go b/cmd/neofs-adm/internal/modules/morph/balance.go index a674f5d0f9..dcb06c94d5 100644 --- a/cmd/neofs-adm/internal/modules/morph/balance.go +++ b/cmd/neofs-adm/internal/modules/morph/balance.go @@ -13,7 +13,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "github.com/nspcc-dev/neo-go/pkg/io" - "github.com/nspcc-dev/neo-go/pkg/rpc/client" "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" @@ -57,11 +56,13 @@ func dumpBalances(cmd *cobra.Command, _ []string) error { return err } - gasHash, err := c.GetNativeContractHash(nativenames.Gas) - if err != nil { - return fmt.Errorf("can't fetch hash of the GAS contract: %w", err) + ns, err := getNativeHashes(c) + if err != nil || ns[nativenames.Gas].Equals(util.Uint160{}) { + return errors.New("can't fetch hash of the GAS contract") } + gasHash := ns[nativenames.Gas] + if !notaryEnabled || dumpStorage || dumpAlphabet || dumpProxy { nnsCs, err = c.GetContractStateByID(1) if err != nil { @@ -169,7 +170,7 @@ func dumpBalances(cmd *cobra.Command, _ []string) error { return nil } -func fetchIRNodes(c *client.Client, nmHash util.Uint160) ([]accBalancePair, error) { +func fetchIRNodes(c Client, nmHash util.Uint160) ([]accBalancePair, error) { var irList []accBalancePair if notaryEnabled { @@ -233,7 +234,7 @@ func printBalances(cmd *cobra.Command, prefix string, accounts []accBalancePair) } } -func fetchBalances(c *client.Client, gasHash util.Uint160, accounts []accBalancePair) error { +func fetchBalances(c Client, gasHash util.Uint160, accounts []accBalancePair) error { w := io.NewBufBinWriter() for i := range accounts { emit.AppCall(w.BinWriter, gasHash, "balanceOf", callflag.ReadStates, accounts[i].scriptHash) diff --git a/cmd/neofs-adm/internal/modules/morph/dump.go b/cmd/neofs-adm/internal/modules/morph/dump.go index d785b47e9e..928376350b 100644 --- a/cmd/neofs-adm/internal/modules/morph/dump.go +++ b/cmd/neofs-adm/internal/modules/morph/dump.go @@ -44,7 +44,7 @@ func dumpContractHashes(cmd *cobra.Command, _ []string) error { irSize := 0 for ; irSize < lastGlagoliticLetter; irSize++ { - ok, err := c.NNSIsAvailable(cs.Hash, getAlphabetNNSDomain(irSize)) + ok, err := nnsIsAvailable(c, cs.Hash, getAlphabetNNSDomain(irSize)) if err != nil { return err } else if ok { diff --git a/cmd/neofs-adm/internal/modules/morph/initialize.go b/cmd/neofs-adm/internal/modules/morph/initialize.go index b58701d778..344fe51489 100644 --- a/cmd/neofs-adm/internal/modules/morph/initialize.go +++ b/cmd/neofs-adm/internal/modules/morph/initialize.go @@ -344,6 +344,31 @@ func (c *initializeContext) sendCommitteeTx(script []byte, sysFee int64, tryGrou return c.multiSignAndSend(tx, committeeAccountName) } +// sendSingleTx creates transaction signed by a simple account and pushes in onto the chain. +// It neither waits until tx persists nor checks the execution result. +func (c *initializeContext) sendSingleTx(script []byte, sysFee int64, acc *wallet.Account) error { + tx, err := c.Client.CreateTxFromScript(script, acc, sysFee, 0, []client.SignerAccount{{ + Signer: transaction.Signer{ + Account: acc.Contract.ScriptHash(), + Scopes: transaction.CalledByEntry, + }, + Account: acc, + }}) + if err != nil { + return err + } + + magic, err := c.Client.GetNetwork() + if err != nil { + return fmt.Errorf("can't fetch network magic: %w", err) + } + if err := acc.SignTx(magic, tx); err != nil { + return fmt.Errorf("can't sign tx: %w", err) + } + + return c.sendTx(tx, c.Command, false) +} + func getWalletAccount(w *wallet.Wallet, typ string) (*wallet.Account, error) { for i := range w.Accounts { if w.Accounts[i].Label == typ { @@ -353,7 +378,7 @@ func getWalletAccount(w *wallet.Wallet, typ string) (*wallet.Account, error) { return nil, fmt.Errorf("account for '%s' not found", typ) } -func getNativeHashes(c *client.Client) (map[string]util.Uint160, error) { +func getNativeHashes(c Client) (map[string]util.Uint160, error) { ns, err := c.GetNativeContracts() if err != nil { return nil, fmt.Errorf("can't get native contract hashes: %w", err) diff --git a/cmd/neofs-adm/internal/modules/morph/initialize_deploy.go b/cmd/neofs-adm/internal/modules/morph/initialize_deploy.go index 4473fe5e3f..a41029b633 100644 --- a/cmd/neofs-adm/internal/modules/morph/initialize_deploy.go +++ b/cmd/neofs-adm/internal/modules/morph/initialize_deploy.go @@ -305,12 +305,11 @@ func (c *initializeContext) deployContracts() error { params := getContractDeployParameters(alphaCs.RawNEF, alphaCs.RawManifest, c.getAlphabetDeployParameters(i, len(c.Wallets))) - signer := transaction.Signer{ + + res, err := c.Client.InvokeFunction(invokeHash, deployMethodName, params, []transaction.Signer{{ Account: acc.Contract.ScriptHash(), Scopes: transaction.CalledByEntry, - } - - res, err := c.Client.InvokeFunction(invokeHash, deployMethodName, params, []transaction.Signer{signer}) + }}) if err != nil { return fmt.Errorf("can't deploy alphabet #%d contract: %w", i, err) } @@ -318,15 +317,9 @@ func (c *initializeContext) deployContracts() error { return fmt.Errorf("can't deploy alpabet #%d contract: %s", i, res.FaultException) } - h, err := c.Client.SignAndPushInvocationTx(res.Script, acc, -1, 0, []client.SignerAccount{{ - Signer: signer, - Account: acc, - }}) - if err != nil { - return fmt.Errorf("can't push deploy transaction: %w", err) + if err := c.sendSingleTx(res.Script, res.GasConsumed, acc); err != nil { + return err } - - c.Hashes = append(c.Hashes, h) } for _, ctrName := range contractList { diff --git a/cmd/neofs-adm/internal/modules/morph/initialize_nns.go b/cmd/neofs-adm/internal/modules/morph/initialize_nns.go index f74ecb1ff9..39ffb4cc4f 100644 --- a/cmd/neofs-adm/internal/modules/morph/initialize_nns.go +++ b/cmd/neofs-adm/internal/modules/morph/initialize_nns.go @@ -90,7 +90,7 @@ func (c *initializeContext) updateNNSGroup(nnsHash util.Uint160, pub *keys.Publi } func (c *initializeContext) emitUpdateNNSGroupScript(bw *io.BufBinWriter, nnsHash util.Uint160, pub *keys.PublicKey) (int64, error) { - isAvail, err := c.Client.NNSIsAvailable(nnsHash, morphClient.NNSGroupKeyName) + isAvail, err := nnsIsAvailable(c.Client, nnsHash, morphClient.NNSGroupKeyName) if err != nil { return 0, err } @@ -125,7 +125,7 @@ func getAlphabetNNSDomain(i int) string { } func (c *initializeContext) nnsRegisterDomainScript(nnsHash, expectedHash util.Uint160, domain string) ([]byte, error) { - ok, err := c.Client.NNSIsAvailable(nnsHash, domain) + ok, err := nnsIsAvailable(c.Client, nnsHash, domain) if err != nil { return nil, err } @@ -176,7 +176,7 @@ func (c *initializeContext) nnsRootRegistered(nnsHash util.Uint160) (bool, error var errMissingNNSRecord = errors.New("missing NNS record") // Returns errMissingNNSRecord if invocation fault exception contains "token not found". -func nnsResolveHash(c *client.Client, nnsHash util.Uint160, domain string) (util.Uint160, error) { +func nnsResolveHash(c Client, nnsHash util.Uint160, domain string) (util.Uint160, error) { item, err := nnsResolve(c, nnsHash, domain) if err != nil { return util.Uint160{}, err @@ -184,7 +184,7 @@ func nnsResolveHash(c *client.Client, nnsHash util.Uint160, domain string) (util return parseNNSResolveResult(item) } -func nnsResolve(c *client.Client, nnsHash util.Uint160, domain string) (stackitem.Item, error) { +func nnsResolve(c Client, nnsHash util.Uint160, domain string) (stackitem.Item, error) { result, err := c.InvokeFunction(nnsHash, "resolve", []smartcontract.Parameter{ { Type: smartcontract.StringType, @@ -210,7 +210,7 @@ func nnsResolve(c *client.Client, nnsHash util.Uint160, domain string) (stackite return result.Stack[len(result.Stack)-1], nil } -func nnsResolveKey(c *client.Client, nnsHash util.Uint160, domain string) (*keys.PublicKey, error) { +func nnsResolveKey(c Client, nnsHash util.Uint160, domain string) (*keys.PublicKey, error) { item, err := nnsResolve(c, nnsHash, domain) if err != nil { return nil, err @@ -243,3 +243,12 @@ func parseNNSResolveResult(res stackitem.Item) (util.Uint160, error) { } return util.Uint160DecodeStringLE(string(bs)) } + +func nnsIsAvailable(c Client, nnsHash util.Uint160, name string) (bool, error) { + switch ct := c.(type) { + case *client.Client: + return ct.NNSIsAvailable(nnsHash, name) + default: + panic("unimplemented") + } +} diff --git a/cmd/neofs-adm/internal/modules/morph/initialize_register.go b/cmd/neofs-adm/internal/modules/morph/initialize_register.go index 7d7881d68d..29d93756d3 100644 --- a/cmd/neofs-adm/internal/modules/morph/initialize_register.go +++ b/cmd/neofs-adm/internal/modules/morph/initialize_register.go @@ -6,7 +6,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "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/io" "github.com/nspcc-dev/neo-go/pkg/rpc/client" "github.com/nspcc-dev/neo-go/pkg/smartcontract" @@ -35,7 +34,7 @@ func (c *initializeContext) registerCandidates() error { } } - regPrice, err := c.Client.GetCandidateRegisterPrice() + regPrice, err := getCandidateRegisterPrice(c.Client) if err != nil { return fmt.Errorf("can't fetch registration price: %w", err) } @@ -46,18 +45,9 @@ func (c *initializeContext) registerCandidates() error { emit.AppCall(w.BinWriter, neoHash, "registerCandidate", callflag.States, acc.PrivateKey().PublicKey().Bytes()) emit.Opcodes(w.BinWriter, opcode.ASSERT) - h, err := c.Client.SignAndPushInvocationTx(w.Bytes(), acc, sysGas, 0, []client.SignerAccount{{ - Signer: transaction.Signer{ - Account: acc.Contract.ScriptHash(), - Scopes: transaction.CalledByEntry, - }, - Account: acc, - }}) - if err != nil { + if err := c.sendSingleTx(w.Bytes(), sysGas, acc); err != nil { return err } - - c.Hashes = append(c.Hashes, h) } return c.awaitTx() @@ -93,3 +83,12 @@ func (c *initializeContext) transferNEOFinished(neoHash util.Uint160) (bool, err bal, err := c.Client.NEP17BalanceOf(neoHash, c.CommitteeAcc.Contract.ScriptHash()) return bal < native.NEOTotalSupply, err } + +func getCandidateRegisterPrice(c Client) (int64, error) { + switch ct := c.(type) { + case *client.Client: + return ct.GetCandidateRegisterPrice() + default: + panic("unimplemented") + } +} diff --git a/cmd/neofs-adm/internal/modules/morph/initialize_transfer.go b/cmd/neofs-adm/internal/modules/morph/initialize_transfer.go index 4cf689ed41..40faf1aa7e 100644 --- a/cmd/neofs-adm/internal/modules/morph/initialize_transfer.go +++ b/cmd/neofs-adm/internal/modules/morph/initialize_transfer.go @@ -6,8 +6,13 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/rpc/client" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" scContext "github.com/nspcc-dev/neo-go/pkg/smartcontract/context" + "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/wallet" ) const ( @@ -56,7 +61,7 @@ func (c *initializeContext) transferFunds() error { }, ) - tx, err := c.Client.CreateNEP17MultiTransferTx(c.ConsensusAcc, 0, transfers, []client.SignerAccount{{ + tx, err := createNEP17MultiTransferTx(c.Client, c.ConsensusAcc, 0, transfers, []client.SignerAccount{{ Signer: transaction.Signer{ Account: c.ConsensusAcc.Contract.ScriptHash(), Scopes: transaction.CalledByEntry, @@ -138,7 +143,11 @@ func (c *initializeContext) transferGASToProxy() error { return err } - tx, err := c.Client.CreateNEP17TransferTx(c.CommitteeAcc, proxyCs.Hash, gasHash, initialProxyGASAmount, 0, nil, nil) + tx, err := createNEP17MultiTransferTx(c.Client, c.CommitteeAcc, 0, []client.TransferTarget{{ + Token: gasHash, + Address: proxyCs.Hash, + Amount: initialProxyGASAmount, + }}, nil) if err != nil { return err } @@ -149,3 +158,25 @@ func (c *initializeContext) transferGASToProxy() error { return c.awaitTx() } + +func createNEP17MultiTransferTx(c Client, acc *wallet.Account, netFee int64, + recipients []client.TransferTarget, cosigners []client.SignerAccount) (*transaction.Transaction, error) { + from := acc.Contract.ScriptHash() + + w := io.NewBufBinWriter() + for i := range recipients { + emit.AppCall(w.BinWriter, recipients[i].Token, "transfer", callflag.All, + from, recipients[i].Address, recipients[i].Amount, recipients[i].Data) + emit.Opcodes(w.BinWriter, opcode.ASSERT) + } + if w.Err != nil { + return nil, fmt.Errorf("failed to create transfer script: %w", w.Err) + } + return c.CreateTxFromScript(w.Bytes(), acc, -1, netFee, append([]client.SignerAccount{{ + Signer: transaction.Signer{ + Account: from, + Scopes: transaction.CalledByEntry, + }, + Account: acc, + }}, cosigners...)) +} diff --git a/cmd/neofs-adm/internal/modules/morph/n3client.go b/cmd/neofs-adm/internal/modules/morph/n3client.go index 77e304dcca..98fea08c6e 100644 --- a/cmd/neofs-adm/internal/modules/morph/n3client.go +++ b/cmd/neofs-adm/internal/modules/morph/n3client.go @@ -5,21 +5,46 @@ import ( "errors" "time" + "github.com/nspcc-dev/neo-go/pkg/config/netmode" + "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" + "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/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/rpc/client" + "github.com/nspcc-dev/neo-go/pkg/rpc/response/result" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/spf13/cobra" "github.com/spf13/viper" ) +// Client represents N3 client interface capable of test-invoking scripts +// and sending signed transactions to chain. +type Client interface { + GetBlockCount() (uint32, error) + GetDesignatedByRole(noderoles.Role, uint32) (keys.PublicKeys, error) + GetContractStateByID(int32) (*state.Contract, error) + GetContractStateByHash(util.Uint160) (*state.Contract, error) + GetNativeContracts() ([]state.NativeContract, error) + GetNetwork() (netmode.Magic, error) + GetApplicationLog(util.Uint256, *trigger.Type) (*result.ApplicationLog, error) + CreateTxFromScript([]byte, *wallet.Account, int64, int64, []client.SignerAccount) (*transaction.Transaction, error) + NEP17BalanceOf(util.Uint160, util.Uint160) (int64, error) + InvokeFunction(util.Uint160, string, []smartcontract.Parameter, []transaction.Signer) (*result.Invoke, error) + InvokeScript([]byte, []transaction.Signer) (*result.Invoke, error) + SendRawTransaction(*transaction.Transaction) (util.Uint256, error) +} + type clientContext struct { - Client *client.Client + Client Client Hashes []util.Uint256 WaitDuration time.Duration PollInterval time.Duration } -func getN3Client(v *viper.Viper) (*client.Client, error) { +func getN3Client(v *viper.Viper) (Client, error) { // number of opened connections // by neo-go client per one host const ( @@ -45,7 +70,7 @@ func getN3Client(v *viper.Viper) (*client.Client, error) { return c, nil } -func defaultClientContext(c *client.Client) *clientContext { +func defaultClientContext(c Client) *clientContext { return &clientContext{ Client: c, WaitDuration: time.Second * 30, diff --git a/cmd/neofs-adm/internal/modules/morph/policy.go b/cmd/neofs-adm/internal/modules/morph/policy.go index d4a3e05d7a..26994662b2 100644 --- a/cmd/neofs-adm/internal/modules/morph/policy.go +++ b/cmd/neofs-adm/internal/modules/morph/policy.go @@ -25,10 +25,7 @@ func setPolicyCmd(cmd *cobra.Command, args []string) error { return fmt.Errorf("can't to initialize context: %w", err) } - policyHash, err := wCtx.Client.GetNativeContractHash(nativenames.Policy) - if err != nil { - return fmt.Errorf("can't get policy contract hash: %w", err) - } + policyHash := wCtx.nativeHash(nativenames.Policy) bw := io.NewBufBinWriter() for i := range args {