refactor: Move some methods inside TX Factory (backport #9421) (#10039)

* refactor: Move some methods inside TX Factory (#9421)

<!-- < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < ☺
v                               ✰  Thanks for creating a PR! ✰
v    Before smashing the submit button please review the checkboxes.
v    If a checkbox is n/a - please still include it but + a little note why
☺ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >  -->

## Description

<!-- Add a description of the changes that this PR introduces and the files that
are the most critical to review.
-->

Putting some things inside the factory (which was very anemic struct) has helped me to understand the flow. Feel free to merge if you see some benefit.

closes: #XXXX

---

Before we can merge this PR, please make sure that all the following items have been
checked off. If any of the checklist items are not applicable, please leave them but
write a little note why.

- [ ] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting))
- [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work.
- [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md).
- [ ] Wrote unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing)
- [ ] Updated relevant documentation (`docs/`) or specification (`x/<module>/spec/`)
- [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code).
- [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md`
- [ ] Re-reviewed `Files changed` in the Github PR explorer
- [ ] Review `Codecov Report` in the comment section below once CI passes

(cherry picked from commit e17be874bb)

# Conflicts:
#	CHANGELOG.md

* Update CHANGELOG.md

Co-authored-by: Jonathan Gimeno <jgimeno@gmail.com>
Co-authored-by: Robert Zaremba <robert@zaremba.ch>
This commit is contained in:
mergify[bot] 2021-08-31 19:00:30 +02:00 committed by GitHub
parent 370134ce01
commit 0155244d2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 151 additions and 149 deletions

View File

@ -52,7 +52,11 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Bug Fixes
+ [\#9965](https://github.com/cosmos/cosmos-sdk/pull/9965) Fixed `simd version` command output to report the right release tag.
+
### API Breaking Changes
* (client/tx) [\#9421](https://github.com/cosmos/cosmos-sdk/pull/9421/) `BuildUnsignedTx`, `BuildSimTx`, `PrintUnsignedStdTx` functions are moved to
the Tx Factory as methods.
## [v0.43.0](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.43.0) - 2021-08-10
### Features
@ -142,7 +146,6 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (x/capability) [\#9836](https://github.com/cosmos/cosmos-sdk/pull/9836) Removed `InitializeAndSeal(ctx sdk.Context)` and replaced with `Seal()`. App must add x/capability module to the begin blockers which will assure that the x/capability keeper is properly initialized. The x/capability begin blocker must be run before any other module which uses x/capability.
### State Machine Breaking
* (x/{bank,distrib,gov,slashing,staking}) [\#8363](https://github.com/cosmos/cosmos-sdk/issues/8363) Store keys have been modified to allow for variable-length addresses.

View File

@ -1,11 +1,16 @@
package tx
import (
"errors"
"fmt"
"os"
"github.com/spf13/pflag"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
)
@ -189,3 +194,132 @@ func (f Factory) WithTimeoutHeight(height uint64) Factory {
f.timeoutHeight = height
return f
}
// BuildUnsignedTx builds a transaction to be signed given a set of messages.
// Once created, the fee, memo, and messages are set.
func (f Factory) BuildUnsignedTx(msgs ...sdk.Msg) (client.TxBuilder, error) {
if f.chainID == "" {
return nil, fmt.Errorf("chain ID required but not specified")
}
fees := f.fees
if !f.gasPrices.IsZero() {
if !fees.IsZero() {
return nil, errors.New("cannot provide both fees and gas prices")
}
glDec := sdk.NewDec(int64(f.gas))
// Derive the fees based on the provided gas prices, where
// fee = ceil(gasPrice * gasLimit).
fees = make(sdk.Coins, len(f.gasPrices))
for i, gp := range f.gasPrices {
fee := gp.Amount.Mul(glDec)
fees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt())
}
}
tx := f.txConfig.NewTxBuilder()
if err := tx.SetMsgs(msgs...); err != nil {
return nil, err
}
tx.SetMemo(f.memo)
tx.SetFeeAmount(fees)
tx.SetGasLimit(f.gas)
tx.SetTimeoutHeight(f.TimeoutHeight())
return tx, nil
}
// PrintUnsignedTx will generate an unsigned transaction and print it to the writer
// specified by ctx.Output. If simulation was requested, the gas will be
// simulated and also printed to the same writer before the transaction is
// printed.
func (f Factory) PrintUnsignedTx(clientCtx client.Context, msgs ...sdk.Msg) error {
if f.SimulateAndExecute() {
if clientCtx.Offline {
return errors.New("cannot estimate gas in offline mode")
}
_, adjusted, err := CalculateGas(clientCtx, f, msgs...)
if err != nil {
return err
}
f = f.WithGas(adjusted)
_, _ = fmt.Fprintf(os.Stderr, "%s\n", GasEstimateResponse{GasEstimate: f.Gas()})
}
tx, err := f.BuildUnsignedTx(msgs...)
if err != nil {
return err
}
json, err := clientCtx.TxConfig.TxJSONEncoder()(tx.GetTx())
if err != nil {
return err
}
return clientCtx.PrintString(fmt.Sprintf("%s\n", json))
}
// BuildSimTx creates an unsigned tx with an empty single signature and returns
// the encoded transaction or an error if the unsigned transaction cannot be
// built.
func (f Factory) BuildSimTx(msgs ...sdk.Msg) ([]byte, error) {
txb, err := f.BuildUnsignedTx(msgs...)
if err != nil {
return nil, err
}
// Create an empty signature literal as the ante handler will populate with a
// sentinel pubkey.
sig := signing.SignatureV2{
PubKey: &secp256k1.PubKey{},
Data: &signing.SingleSignatureData{
SignMode: f.signMode,
},
Sequence: f.Sequence(),
}
if err := txb.SetSignatures(sig); err != nil {
return nil, err
}
return f.txConfig.TxEncoder()(txb.GetTx())
}
// Prepare ensures the account defined by ctx.GetFromAddress() exists and
// if the account number and/or the account sequence number are zero (not set),
// they will be queried for and set on the provided Factory. A new Factory with
// the updated fields will be returned.
func (f Factory) Prepare(clientCtx client.Context) (Factory, error) {
fc := f
from := clientCtx.GetFromAddress()
if err := fc.accountRetriever.EnsureExists(clientCtx, from); err != nil {
return fc, err
}
initNum, initSeq := fc.accountNumber, fc.sequence
if initNum == 0 || initSeq == 0 {
num, seq, err := fc.accountRetriever.GetAccountNumberSequence(clientCtx, from)
if err != nil {
return fc, err
}
if initNum == 0 {
fc = fc.WithAccountNumber(num)
}
if initSeq == 0 {
fc = fc.WithSequence(seq)
}
}
return fc, nil
}

View File

@ -14,7 +14,6 @@ import (
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/input"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
@ -45,49 +44,17 @@ func GenerateOrBroadcastTxWithFactory(clientCtx client.Context, txf Factory, msg
}
if clientCtx.GenerateOnly {
return GenerateTx(clientCtx, txf, msgs...)
return txf.PrintUnsignedTx(clientCtx, msgs...)
}
return BroadcastTx(clientCtx, txf, msgs...)
}
// GenerateTx will generate an unsigned transaction and print it to the writer
// specified by ctx.Output. If simulation was requested, the gas will be
// simulated and also printed to the same writer before the transaction is
// printed.
func GenerateTx(clientCtx client.Context, txf Factory, msgs ...sdk.Msg) error {
if txf.SimulateAndExecute() {
if clientCtx.Offline {
return errors.New("cannot estimate gas in offline mode")
}
_, adjusted, err := CalculateGas(clientCtx, txf, msgs...)
if err != nil {
return err
}
txf = txf.WithGas(adjusted)
_, _ = fmt.Fprintf(os.Stderr, "%s\n", GasEstimateResponse{GasEstimate: txf.Gas()})
}
tx, err := BuildUnsignedTx(txf, msgs...)
if err != nil {
return err
}
json, err := clientCtx.TxConfig.TxJSONEncoder()(tx.GetTx())
if err != nil {
return err
}
return clientCtx.PrintString(fmt.Sprintf("%s\n", json))
}
// BroadcastTx attempts to generate, sign and broadcast a transaction with the
// given set of messages. It will also simulate gas requirements if necessary.
// It will return an error upon failure.
func BroadcastTx(clientCtx client.Context, txf Factory, msgs ...sdk.Msg) error {
txf, err := prepareFactory(clientCtx, txf)
txf, err := txf.Prepare(clientCtx)
if err != nil {
return err
}
@ -106,7 +73,7 @@ func BroadcastTx(clientCtx client.Context, txf Factory, msgs ...sdk.Msg) error {
return nil
}
tx, err := BuildUnsignedTx(txf, msgs...)
tx, err := txf.BuildUnsignedTx(msgs...)
if err != nil {
return err
}
@ -197,7 +164,7 @@ func WriteGeneratedTxResponse(
}
}
tx, err := BuildUnsignedTx(txf, msgs...)
tx, err := txf.BuildUnsignedTx(msgs...)
if rest.CheckBadRequestError(w, err) {
return
}
@ -217,78 +184,12 @@ func WriteGeneratedTxResponse(
_, _ = w.Write(output)
}
// BuildUnsignedTx builds a transaction to be signed given a set of messages. The
// transaction is initially created via the provided factory's generator. Once
// created, the fee, memo, and messages are set.
func BuildUnsignedTx(txf Factory, msgs ...sdk.Msg) (client.TxBuilder, error) {
if txf.chainID == "" {
return nil, fmt.Errorf("chain ID required but not specified")
}
fees := txf.fees
if !txf.gasPrices.IsZero() {
if !fees.IsZero() {
return nil, errors.New("cannot provide both fees and gas prices")
}
glDec := sdk.NewDec(int64(txf.gas))
// Derive the fees based on the provided gas prices, where
// fee = ceil(gasPrice * gasLimit).
fees = make(sdk.Coins, len(txf.gasPrices))
for i, gp := range txf.gasPrices {
fee := gp.Amount.Mul(glDec)
fees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt())
}
}
tx := txf.txConfig.NewTxBuilder()
if err := tx.SetMsgs(msgs...); err != nil {
return nil, err
}
tx.SetMemo(txf.memo)
tx.SetFeeAmount(fees)
tx.SetGasLimit(txf.gas)
tx.SetTimeoutHeight(txf.TimeoutHeight())
return tx, nil
}
// BuildSimTx creates an unsigned tx with an empty single signature and returns
// the encoded transaction or an error if the unsigned transaction cannot be
// built.
func BuildSimTx(txf Factory, msgs ...sdk.Msg) ([]byte, error) {
txb, err := BuildUnsignedTx(txf, msgs...)
if err != nil {
return nil, err
}
// Create an empty signature literal as the ante handler will populate with a
// sentinel pubkey.
sig := signing.SignatureV2{
PubKey: &secp256k1.PubKey{},
Data: &signing.SingleSignatureData{
SignMode: txf.signMode,
},
Sequence: txf.Sequence(),
}
if err := txb.SetSignatures(sig); err != nil {
return nil, err
}
return txf.txConfig.TxEncoder()(txb.GetTx())
}
// CalculateGas simulates the execution of a transaction and returns the
// simulation response obtained by the query and the adjusted gas amount.
func CalculateGas(
clientCtx gogogrpc.ClientConn, txf Factory, msgs ...sdk.Msg,
) (*tx.SimulateResponse, uint64, error) {
txBytes, err := BuildSimTx(txf, msgs...)
txBytes, err := txf.BuildSimTx(msgs...)
if err != nil {
return nil, 0, err
}
@ -304,36 +205,6 @@ func CalculateGas(
return simRes, uint64(txf.GasAdjustment() * float64(simRes.GasInfo.GasUsed)), nil
}
// prepareFactory ensures the account defined by ctx.GetFromAddress() exists and
// if the account number and/or the account sequence number are zero (not set),
// they will be queried for and set on the provided Factory. A new Factory with
// the updated fields will be returned.
func prepareFactory(clientCtx client.Context, txf Factory) (Factory, error) {
from := clientCtx.GetFromAddress()
if err := txf.accountRetriever.EnsureExists(clientCtx, from); err != nil {
return txf, err
}
initNum, initSeq := txf.accountNumber, txf.sequence
if initNum == 0 || initSeq == 0 {
num, seq, err := txf.accountRetriever.GetAccountNumberSequence(clientCtx, from)
if err != nil {
return txf, err
}
if initNum == 0 {
txf = txf.WithAccountNumber(num)
}
if initSeq == 0 {
txf = txf.WithSequence(seq)
}
}
return txf, nil
}
// SignWithPrivKey signs a given tx with the given private key, and returns the
// corresponding SignatureV2 if the signing is successful.
func SignWithPrivKey(

View File

@ -107,7 +107,7 @@ func TestBuildSimTx(t *testing.T) {
WithSignMode(txCfg.SignModeHandler().DefaultMode())
msg := banktypes.NewMsgSend(sdk.AccAddress("from"), sdk.AccAddress("to"), nil)
bz, err := tx.BuildSimTx(txf, msg)
bz, err := txf.BuildSimTx(msg)
require.NoError(t, err)
require.NotNil(t, bz)
}
@ -122,7 +122,7 @@ func TestBuildUnsignedTx(t *testing.T) {
WithChainID("test-chain")
msg := banktypes.NewMsgSend(sdk.AccAddress("from"), sdk.AccAddress("to"), nil)
tx, err := tx.BuildUnsignedTx(txf, msg)
tx, err := txf.BuildUnsignedTx(msg)
require.NoError(t, err)
require.NotNil(t, tx)
@ -169,11 +169,11 @@ func TestSign(t *testing.T) {
WithSignMode(signingtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON)
msg1 := banktypes.NewMsgSend(info1.GetAddress(), sdk.AccAddress("to"), nil)
msg2 := banktypes.NewMsgSend(info2.GetAddress(), sdk.AccAddress("to"), nil)
txb, err := tx.BuildUnsignedTx(txfNoKeybase, msg1, msg2)
txb, err := txfNoKeybase.BuildUnsignedTx(msg1, msg2)
requireT.NoError(err)
txb2, err := tx.BuildUnsignedTx(txfNoKeybase, msg1, msg2)
txb2, err := txfNoKeybase.BuildUnsignedTx(msg1, msg2)
requireT.NoError(err)
txbSimple, err := tx.BuildUnsignedTx(txfNoKeybase, msg2)
txbSimple, err := txfNoKeybase.BuildUnsignedTx(msg2)
requireT.NoError(err)
testCases := []struct {

View File

@ -30,12 +30,6 @@ func (gr GasEstimateResponse) String() string {
return fmt.Sprintf("gas estimate: %d", gr.GasEstimate)
}
// PrintUnsignedStdTx builds an unsigned StdTx and prints it to os.Stdout.
func PrintUnsignedStdTx(txBldr tx.Factory, clientCtx client.Context, msgs []sdk.Msg) error {
err := tx.GenerateTx(clientCtx, txBldr, msgs...)
return err
}
// SignTx signs a transaction managed by the TxBuilder using a `name` key stored in Keybase.
// The new signature is appended to the TxBuilder when overwrite=false or overwritten otherwise.
// Don't perform online validation or lookups if offline is true.

View File

@ -155,14 +155,14 @@ $ %s gentx my-key-name 1000000stake --home=/path/to/home/dir --keyring-backend=o
if key.GetType() == keyring.TypeOffline || key.GetType() == keyring.TypeMulti {
cmd.PrintErrln("Offline key passed in. Use `tx sign` command to sign.")
return authclient.PrintUnsignedStdTx(txBldr, clientCtx, []sdk.Msg{msg})
return txBldr.PrintUnsignedTx(clientCtx, msg)
}
// write the unsigned transaction to the buffer
w := bytes.NewBuffer([]byte{})
clientCtx = clientCtx.WithOutput(w)
if err = authclient.PrintUnsignedStdTx(txBldr, clientCtx, []sdk.Msg{msg}); err != nil {
if err = txBldr.PrintUnsignedTx(clientCtx, msg); err != nil {
return errors.Wrap(err, "failed to print unsigned std tx")
}