From d4d23206eea55bde3369e45f2bab63604f41abf6 Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Mon, 3 Jun 2024 18:35:07 +0200 Subject: [PATCH 01/36] first approach --- client/v2/autocli/keyring/interface.go | 3 + client/v2/autocli/keyring/no_keyring.go | 4 + client/v2/tx/account.go | 24 + client/v2/tx/aux_builder.go | 277 ++++++++++ client/v2/tx/aux_builder_test.go | 255 ++++++++++ client/v2/tx/builder.go | 39 ++ client/v2/tx/config.go | 84 +++ client/v2/tx/factory.go | 651 ++++++++++++++++++++++++ client/v2/tx/tx.go | 258 ++++++++++ client/v2/tx/types.go | 81 +++ crypto/keyring/autocli.go | 12 + 11 files changed, 1688 insertions(+) create mode 100644 client/v2/tx/account.go create mode 100644 client/v2/tx/aux_builder.go create mode 100644 client/v2/tx/aux_builder_test.go create mode 100644 client/v2/tx/builder.go create mode 100644 client/v2/tx/config.go create mode 100644 client/v2/tx/factory.go create mode 100644 client/v2/tx/tx.go create mode 100644 client/v2/tx/types.go diff --git a/client/v2/autocli/keyring/interface.go b/client/v2/autocli/keyring/interface.go index fa448bd20599..6ec62cdf4f18 100644 --- a/client/v2/autocli/keyring/interface.go +++ b/client/v2/autocli/keyring/interface.go @@ -20,4 +20,7 @@ type Keyring interface { // Sign signs the given bytes with the key with the given name. Sign(name string, msg []byte, signMode signingv1beta1.SignMode) ([]byte, error) + + // KeyType returns the type of the key. + KeyType(name string) (uint, error) } diff --git a/client/v2/autocli/keyring/no_keyring.go b/client/v2/autocli/keyring/no_keyring.go index e14267cee5e3..01666a19f8b8 100644 --- a/client/v2/autocli/keyring/no_keyring.go +++ b/client/v2/autocli/keyring/no_keyring.go @@ -29,3 +29,7 @@ func (k NoKeyring) GetPubKey(name string) (cryptotypes.PubKey, error) { func (k NoKeyring) Sign(name string, msg []byte, signMode signingv1beta1.SignMode) ([]byte, error) { return nil, errNoKeyring } + +func (k NoKeyring) KeyType(name string) (uint, error) { + return 0, errNoKeyring +} diff --git a/client/v2/tx/account.go b/client/v2/tx/account.go new file mode 100644 index 000000000000..857813e1e81d --- /dev/null +++ b/client/v2/tx/account.go @@ -0,0 +1,24 @@ +package tx + +import ( + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Account defines a read-only version of the auth module's AccountI. +type Account interface { + GetAddress() sdk.AccAddress + GetPubKey() cryptotypes.PubKey // can return nil. + GetAccountNumber() uint64 + GetSequence() uint64 +} + +// AccountRetriever defines the interfaces required by transactions to +// ensure an account exists and to be able to query for account fields necessary +// for signing. +type AccountRetriever interface { + GetAccount(addr sdk.AccAddress) (Account, error) + GetAccountWithHeight(addr sdk.AccAddress) (Account, int64, error) + EnsureExists(addr sdk.AccAddress) error + GetAccountNumberSequence(addr sdk.AccAddress) (accNum, accSeq uint64, err error) +} diff --git a/client/v2/tx/aux_builder.go b/client/v2/tx/aux_builder.go new file mode 100644 index 000000000000..d396f97e927b --- /dev/null +++ b/client/v2/tx/aux_builder.go @@ -0,0 +1,277 @@ +package tx + +import ( + "context" + + "github.com/cosmos/gogoproto/proto" + "google.golang.org/protobuf/types/known/anypb" + + apisigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + apitx "cosmossdk.io/api/cosmos/tx/v1beta1" + "cosmossdk.io/core/transaction" + "cosmossdk.io/x/tx/signing" + "cosmossdk.io/x/tx/signing/aminojson" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +// AuxTxBuilder is a client-side builder for creating an AuxSignerData. +type AuxTxBuilder struct { + // msgs is used to store the sdk.Msgs that are added to the + // TxBuilder. It's also added inside body.Messages, because: + // - b.msgs is used for constructing the AMINO sign bz, + // - b.body is used for constructing the DIRECT_AUX sign bz. + msgs []transaction.Msg + body *apitx.TxBody + auxSignerData *apitx.AuxSignerData +} + +// NewAuxTxBuilder creates a new client-side builder for constructing an +// AuxSignerData. +func NewAuxTxBuilder() AuxTxBuilder { + return AuxTxBuilder{} +} + +// SetAddress sets the aux signer's bech32 address. +func (b *AuxTxBuilder) SetAddress(addr string) { + b.checkEmptyFields() + + b.auxSignerData.Address = addr +} + +// SetMemo sets a memo in the tx. +func (b *AuxTxBuilder) SetMemo(memo string) { + b.checkEmptyFields() + + b.body.Memo = memo + b.auxSignerData.SignDoc.BodyBytes = nil +} + +// SetTimeoutHeight sets a timeout height in the tx. +func (b *AuxTxBuilder) SetTimeoutHeight(height uint64) { + b.checkEmptyFields() + + b.body.TimeoutHeight = height + b.auxSignerData.SignDoc.BodyBytes = nil +} + +// SetMsgs sets an array of Msgs in the tx. +func (b *AuxTxBuilder) SetMsgs(msgs ...sdk.Msg) error { + anys := make([]*anypb.Any, len(msgs)) + for i, msg := range msgs { + legacyAny, err := codectypes.NewAnyWithValue(msg) + if err != nil { + return err + } + anys[i] = &anypb.Any{ + TypeUrl: legacyAny.TypeUrl, + Value: legacyAny.Value, + } + } + + b.checkEmptyFields() + + b.msgs = msgs + b.body.Messages = anys + b.auxSignerData.SignDoc.BodyBytes = nil + + return nil +} + +// SetAccountNumber sets the aux signer's account number in the AuxSignerData. +func (b *AuxTxBuilder) SetAccountNumber(accNum uint64) { + b.checkEmptyFields() + + b.auxSignerData.SignDoc.AccountNumber = accNum +} + +// SetChainID sets the chain id in the AuxSignerData. +func (b *AuxTxBuilder) SetChainID(chainID string) { + b.checkEmptyFields() + + b.auxSignerData.SignDoc.ChainId = chainID +} + +// SetSequence sets the aux signer's sequence in the AuxSignerData. +func (b *AuxTxBuilder) SetSequence(accSeq uint64) { + b.checkEmptyFields() + + b.auxSignerData.SignDoc.Sequence = accSeq +} + +// SetPubKey sets the aux signer's pubkey in the AuxSignerData. +func (b *AuxTxBuilder) SetPubKey(pk cryptotypes.PubKey) error { + anyPk, err := codectypes.NewAnyWithValue(pk) + if err != nil { + return err + } + + b.checkEmptyFields() + b.auxSignerData.SignDoc.PublicKey = &anypb.Any{ + TypeUrl: anyPk.TypeUrl, + Value: anyPk.Value, + } + + return nil +} + +// SetSignMode sets the aux signer's sign mode. Allowed sign modes are +// DIRECT_AUX and LEGACY_AMINO_JSON. +func (b *AuxTxBuilder) SetSignMode(mode apitxsigning.SignMode) error { + switch mode { + case apitxsigning.SignMode_SIGN_MODE_DIRECT_AUX, apitxsigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON: + default: + return sdkerrors.ErrInvalidRequest.Wrapf("AuxTxBuilder can only sign with %s or %s", + apisigning.SignMode_SIGN_MODE_DIRECT_AUX, apisigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON) + } + + b.auxSignerData.Mode = mode + return nil +} + +// SetSignature sets the aux signer's signature in the AuxSignerData. +func (b *AuxTxBuilder) SetSignature(sig []byte) { + b.checkEmptyFields() + + b.auxSignerData.Sig = sig +} + +// SetExtensionOptions sets the aux signer's extension options. +func (b *AuxTxBuilder) SetExtensionOptions(extOpts ...*codectypes.Any) { + b.checkEmptyFields() + + anyExtOpts := make([]*anypb.Any, len(extOpts)) + for i, extOpt := range extOpts { + anyExtOpts[i] = &anypb.Any{ + TypeUrl: extOpt.TypeUrl, + Value: extOpt.Value, + } + } + b.body.ExtensionOptions = anyExtOpts + b.auxSignerData.SignDoc.BodyBytes = nil +} + +// SetNonCriticalExtensionOptions sets the aux signer's non-critical extension options. +func (b *AuxTxBuilder) SetNonCriticalExtensionOptions(extOpts ...*codectypes.Any) { + b.checkEmptyFields() + + anyNonCritExtOpts := make([]*anypb.Any, len(extOpts)) + for i, extOpt := range extOpts { + anyNonCritExtOpts[i] = &anypb.Any{ + TypeUrl: extOpt.TypeUrl, + Value: extOpt.Value, + } + } + b.body.NonCriticalExtensionOptions = anyNonCritExtOpts + b.auxSignerData.SignDoc.BodyBytes = nil +} + +// GetSignBytes returns the builder's sign bytes. +func (b *AuxTxBuilder) GetSignBytes() ([]byte, error) { + auxTx := b.auxSignerData + if auxTx == nil { + return nil, sdkerrors.ErrLogic.Wrap("aux tx is nil, call setters on AuxTxBuilder first") + } + + body := b.body + if body == nil { + return nil, sdkerrors.ErrLogic.Wrap("tx body is nil, call setters on AuxTxBuilder first") + } + + sd := auxTx.SignDoc + if sd == nil { + return nil, sdkerrors.ErrLogic.Wrap("sign doc is nil, call setters on AuxTxBuilder first") + } + + bodyBz, err := proto.Marshal(body) + if err != nil { + return nil, err + } + + sd.BodyBytes = bodyBz + if err = sd.ValidateBasic(); err != nil { // TODO + return nil, err + } + + var signBz []byte + switch b.auxSignerData.Mode { + case apisigning.SignMode_SIGN_MODE_DIRECT_AUX: + { + signBz, err = proto.Marshal(b.auxSignerData.SignDoc) + if err != nil { + return nil, err + } + } + case apisigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON: + { + handler := aminojson.NewSignModeHandler(aminojson.SignModeHandlerOptions{ + FileResolver: proto.HybridResolver, + }) + + auxBody := &apitx.TxBody{ + Messages: body.Messages, + Memo: body.Memo, + TimeoutHeight: body.TimeoutHeight, + // AuxTxBuilder has no concern with extension options, so we set them to nil. + // This preserves pre-PR#16025 behavior where extension options were ignored, this code path: + // https://github.com/cosmos/cosmos-sdk/blob/ac3c209326a26b46f65a6cc6f5b5ebf6beb79b38/client/tx/aux_builder.go#L193 + // https://github.com/cosmos/cosmos-sdk/blob/ac3c209326a26b46f65a6cc6f5b5ebf6beb79b38/x/auth/migrations/legacytx/stdsign.go#L49 + ExtensionOptions: nil, + NonCriticalExtensionOptions: nil, + } + + signBz, err = handler.GetSignBytes( + context.Background(), + signing.SignerData{ + Address: b.auxSignerData.Address, + ChainID: b.auxSignerData.SignDoc.ChainId, + AccountNumber: b.auxSignerData.SignDoc.AccountNumber, + Sequence: b.auxSignerData.SignDoc.Sequence, + PubKey: nil, + }, + signing.TxData{ + Body: auxBody, + AuthInfo: &apitx.AuthInfo{ + SignerInfos: nil, + // Aux signer never signs over fee. + // For LEGACY_AMINO_JSON, we use the convention to sign + // over empty fees. + // ref: https://github.com/cosmos/cosmos-sdk/pull/10348 + Fee: &apitx.Fee{}, + }, + }, + ) + return signBz, err + } + default: + return nil, sdkerrors.ErrInvalidRequest.Wrapf("got unknown sign mode %s", b.auxSignerData.Mode) + } + + return signBz, nil +} + +// GetAuxSignerData returns the builder's AuxSignerData. +func (b *AuxTxBuilder) GetAuxSignerData() (*apitx.AuxSignerData, error) { + if err := b.auxSignerData.ValidateBasic(); err != nil { // TODO + return nil, err + } + + return b.auxSignerData, nil +} + +func (b *AuxTxBuilder) checkEmptyFields() { + if b.body == nil { + b.body = &apitx.TxBody{} + } + + if b.auxSignerData == nil { + b.auxSignerData = &apitx.AuxSignerData{} + if b.auxSignerData.SignDoc == nil { + b.auxSignerData.SignDoc = &apitx.SignDocDirectAux{} + } + } +} diff --git a/client/v2/tx/aux_builder_test.go b/client/v2/tx/aux_builder_test.go new file mode 100644 index 000000000000..7d561306fc69 --- /dev/null +++ b/client/v2/tx/aux_builder_test.go @@ -0,0 +1,255 @@ +package tx_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + apisigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + apitx "cosmossdk.io/api/cosmos/tx/v1beta1" + "cosmossdk.io/client/v2/tx" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/testutil" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + "github.com/cosmos/cosmos-sdk/testutil/x/counter" + countertypes "github.com/cosmos/cosmos-sdk/testutil/x/counter/types" + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + typestx "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/cosmos/cosmos-sdk/types/tx/signing" +) + +const ( + memo = "waboom" + timeoutHeight = uint64(5) +) + +var ( + _, pub1, addr1 = testdata.KeyTestPubAddr() + rawSig = []byte("dummy") + msg1 = &countertypes.MsgIncreaseCounter{Signer: addr1.String(), Count: 1} + + chainID = "test-chain" +) + +func TestAuxTxBuilder(t *testing.T) { + counterModule := counter.AppModule{} + cdc := moduletestutil.MakeTestEncodingConfig(testutil.CodecOptions{}, counterModule).Codec + reg := codectypes.NewInterfaceRegistry() + + testdata.RegisterInterfaces(reg) + // required for test case: "GetAuxSignerData works for DIRECT_AUX" + counterModule.RegisterInterfaces(reg) + + var b tx.AuxTxBuilder + + testcases := []struct { + name string + malleate func() error + expErr bool + expErrStr string + }{ + { + "cannot set SIGN_MODE_DIRECT", + func() error { + return b.SetSignMode(apisigning.SignMode_SIGN_MODE_DIRECT) + }, + true, "AuxTxBuilder can only sign with SIGN_MODE_DIRECT_AUX or SIGN_MODE_LEGACY_AMINO_JSON", + }, + { + "cannot set invalid pubkey", + func() error { + return b.SetPubKey(cryptotypes.PubKey(nil)) + }, + true, "failed packing protobuf message to Any", + }, + { + "cannot set invalid Msg", + func() error { + return b.SetMsgs(sdk.Msg(nil)) + }, + true, "failed packing protobuf message to Any", + }, + { + "GetSignBytes body should not be nil", + func() error { + _, err := b.GetSignBytes() + return err + }, + true, "aux tx is nil, call setters on AuxTxBuilder first", + }, + { + "GetSignBytes pubkey should not be nil", + func() error { + require.NoError(t, b.SetMsgs(msg1)) + + _, err := b.GetSignBytes() + return err + }, + true, "public key cannot be empty: invalid pubkey", + }, + { + "GetSignBytes invalid sign mode", + func() error { + require.NoError(t, b.SetMsgs(msg1)) + require.NoError(t, b.SetPubKey(pub1)) + + _, err := b.GetSignBytes() + return err + }, + true, "got unknown sign mode SIGN_MODE_UNSPECIFIED", + }, + { + "GetSignBytes works for DIRECT_AUX", + func() error { + require.NoError(t, b.SetMsgs(msg1)) + require.NoError(t, b.SetPubKey(pub1)) + require.NoError(t, b.SetSignMode(apisigning.SignMode_SIGN_MODE_DIRECT_AUX)) + + _, err := b.GetSignBytes() + return err + }, + false, "", + }, + { + "GetAuxSignerData address should not be empty", + func() error { + require.NoError(t, b.SetMsgs(msg1)) + require.NoError(t, b.SetPubKey(pub1)) + require.NoError(t, b.SetSignMode(apisigning.SignMode_SIGN_MODE_DIRECT_AUX)) + + _, err := b.GetSignBytes() + require.NoError(t, err) + + _, err = b.GetAuxSignerData() + return err + }, + true, "address cannot be empty: invalid request", + }, + { + "GetAuxSignerData signature should not be empty", + func() error { + require.NoError(t, b.SetMsgs(msg1)) + require.NoError(t, b.SetPubKey(pub1)) + b.SetAddress(addr1.String()) + require.NoError(t, b.SetSignMode(apisigning.SignMode_SIGN_MODE_DIRECT_AUX)) + + _, err := b.GetSignBytes() + require.NoError(t, err) + + _, err = b.GetAuxSignerData() + return err + }, + true, "signature cannot be empty: no signatures supplied", + }, + { + "GetAuxSignerData works for DIRECT_AUX", + func() error { + b.SetAccountNumber(1) + b.SetSequence(2) + b.SetTimeoutHeight(timeoutHeight) + b.SetMemo(memo) + b.SetChainID(chainID) + require.NoError(t, b.SetMsgs(msg1)) + require.NoError(t, b.SetPubKey(pub1)) + b.SetAddress(addr1.String()) + err := b.SetSignMode(apisigning.SignMode_SIGN_MODE_DIRECT_AUX) + require.NoError(t, err) + + _, err = b.GetSignBytes() + require.NoError(t, err) + b.SetSignature(rawSig) + + auxSignerData, err := b.GetAuxSignerData() + + // Make sure auxSignerData is correctly populated + checkCorrectData(t, cdc, auxSignerData, signing.SignMode_SIGN_MODE_DIRECT_AUX) + + return err + }, + false, "", + }, + { + "GetSignBytes works for LEGACY_AMINO_JSON", + func() error { + require.NoError(t, b.SetMsgs(msg1)) + require.NoError(t, b.SetPubKey(pub1)) + b.SetAddress(addr1.String()) + err := b.SetSignMode(apisigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON) + require.NoError(t, err) + + _, err = b.GetSignBytes() + return err + }, + false, "", + }, + { + "GetAuxSignerData works for LEGACY_AMINO_JSON", + func() error { + b.SetAccountNumber(1) + b.SetSequence(2) + b.SetTimeoutHeight(timeoutHeight) + b.SetMemo(memo) + b.SetChainID(chainID) + require.NoError(t, b.SetMsgs(msg1)) + require.NoError(t, b.SetPubKey(pub1)) + b.SetAddress(addr1.String()) + err := b.SetSignMode(apisigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON) + require.NoError(t, err) + + _, err = b.GetSignBytes() + require.NoError(t, err) + b.SetSignature(rawSig) + + auxSignerData, err := b.GetAuxSignerData() + + // Make sure auxSignerData is correctly populated + checkCorrectData(t, cdc, auxSignerData, signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON) + + return err + }, + false, "", + }, + } + + for _, tc := range testcases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + b = tx.NewAuxTxBuilder() + err := tc.malleate() + + if tc.expErr { + require.Error(t, err) + require.Contains(t, err.Error(), tc.expErrStr) + } else { + require.NoError(t, err) + } + }) + } +} + +// checkCorrectData that the auxSignerData's content matches the inputs we gave. +func checkCorrectData(t *testing.T, cdc codec.Codec, auxSignerData *apitx.AuxSignerData, signMode signing.SignMode) { + t.Helper() + pkAny, err := codectypes.NewAnyWithValue(pub1) + require.NoError(t, err) + msgAny, err := codectypes.NewAnyWithValue(msg1) + require.NoError(t, err) + + var body typestx.TxBody + err = cdc.Unmarshal(auxSignerData.SignDoc.BodyBytes, &body) + require.NoError(t, err) + + require.Equal(t, uint64(1), auxSignerData.SignDoc.AccountNumber) + require.Equal(t, uint64(2), auxSignerData.SignDoc.Sequence) + require.Equal(t, timeoutHeight, body.TimeoutHeight) + require.Equal(t, memo, body.Memo) + require.Equal(t, chainID, auxSignerData.SignDoc.ChainId) + require.Equal(t, msgAny, body.GetMessages()[0]) + require.Equal(t, pkAny, auxSignerData.SignDoc.PublicKey) + require.Equal(t, signMode, auxSignerData.Mode) + require.Equal(t, rawSig, auxSignerData.Sig) +} diff --git a/client/v2/tx/builder.go b/client/v2/tx/builder.go new file mode 100644 index 000000000000..35350cefc167 --- /dev/null +++ b/client/v2/tx/builder.go @@ -0,0 +1,39 @@ +package tx + +import ( + gogoany "github.com/cosmos/gogoproto/types/any" + + base "cosmossdk.io/api/cosmos/base/v1beta1" + typestx "cosmossdk.io/api/cosmos/tx/v1beta1" + "cosmossdk.io/core/transaction" + "cosmossdk.io/x/tx/signing" +) + +type ExtendedTxBuilder interface { + SetExtensionOptions(...*gogoany.Any) // TODO: sdk.Any? +} + +// TxBuilder defines an interface which an application-defined concrete transaction +// type must implement. Namely, it must be able to set messages, generate +// signatures, and provide canonical bytes to sign over. The transaction must +// also know how to encode itself. +type TxBuilder interface { + GetTx() typestx.Tx + GetSigningTxData() signing.TxData + + SetMsgs(...transaction.Msg) error + SetMemo(string) + SetFeeAmount([]*base.Coin) + SetFeePayer(string) + SetGasLimit(uint64) + SetTimeoutHeight(uint64) + SetFeeGranter(string) + SetUnordered(bool) + SetSignatures(...Signature) error + SetAuxSignerData(typestx.AuxSignerData) error +} + +type TxBuilderProvider interface { + NewTxBuilder() TxBuilder + WrapTxBuilder(typestx.Tx) (TxBuilder, error) +} diff --git a/client/v2/tx/config.go b/client/v2/tx/config.go new file mode 100644 index 000000000000..0fb3dfd8294a --- /dev/null +++ b/client/v2/tx/config.go @@ -0,0 +1,84 @@ +package tx + +import ( + base "cosmossdk.io/api/cosmos/base/v1beta1" + apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + "cosmossdk.io/x/tx/signing" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" +) + +// TxConfig defines an interface a client can utilize to generate an +// application-defined concrete transaction type. The type returned must +// implement TxBuilder. +type TxConfig interface { + TxEncodingConfig + TxSigningConfig + TxBuilderProvider +} + +// TxEncodingConfig defines an interface that contains transaction +// encoders and decoders +type TxEncodingConfig interface { + TxEncoder() TxApiEncoder + TxDecoder() TxApiDecoder + TxJSONEncoder() TxApiEncoder + TxJSONDecoder() TxApiDecoder +} + +type TxSigningConfig interface { + SignModeHandler() *signing.HandlerMap + SigningContext() *signing.Context + MarshalSignatureJSON([]signingtypes.SignatureV2) ([]byte, error) + UnmarshalSignatureJSON([]byte) ([]signingtypes.SignatureV2, error) +} + +type TxParameters struct { + timeoutHeight uint64 + chainID string + memo string + signMode apitxsigning.SignMode + + AccountConfig + GasConfig + FeeConfig + ExecutionOptions + ExtensionOptions +} + +// AccountConfig defines the 'account' related fields in a transaction. +type AccountConfig struct { + accountNumber uint64 + sequence uint64 + fromName string + fromAddress sdk.AccAddress +} + +// GasConfig defines the 'gas' related fields in a transaction. +type GasConfig struct { + gas uint64 + gasAdjustment float64 + gasPrices []*base.DecCoin +} + +// FeeConfig defines the 'fee' related fields in a transaction. +type FeeConfig struct { + fees []*base.Coin + feeGranter sdk.AccAddress + feePayer sdk.AccAddress +} + +// ExecutionOptions defines the transaction execution options ran by the client +type ExecutionOptions struct { + unordered bool + offline bool + generateOnly bool + simulateAndExecute bool + preprocessTxHook PreprocessTxFn +} + +type ExtensionOptions struct { + ExtOptions []*codectypes.Any +} diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go new file mode 100644 index 000000000000..00e4360b3791 --- /dev/null +++ b/client/v2/tx/factory.go @@ -0,0 +1,651 @@ +package tx + +import ( + "context" + "errors" + "fmt" + "math/big" + "os" + "strings" + + "github.com/cosmos/go-bip39" + gogogrpc "github.com/cosmos/gogoproto/grpc" + "google.golang.org/protobuf/types/known/anypb" + + base "cosmossdk.io/api/cosmos/base/v1beta1" + apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + "cosmossdk.io/client/v2/autocli/keyring" + "cosmossdk.io/core/address" + "cosmossdk.io/core/transaction" + "cosmossdk.io/math" + authtypes "cosmossdk.io/x/auth/types" + "cosmossdk.io/x/tx/signing" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// TODO +type FactoryI interface { + BuildAuxSignerData() + BuildUnsignedTx() + BuildSignedTx() + BuildSimulationTx() +} + +// Factory defines a client transaction factory that facilitates generating and +// signing an application-specific transaction. +type Factory struct { + keybase keyring.Keyring + accountRetriever authtypes.AccountRetriever // TODO: define in v2/tx + ac address.Codec + conn gogogrpc.ClientConn + txConfig TxConfig + txParams TxParameters +} + +func NewFactoryCLI(keybase keyring.Keyring, accRetriever authtypes.AccountRetriever, ac address.Codec, txConfig TxConfig, parameters TxParameters) (Factory, error) { + //if clientCtx.Viper == nil { + // clientCtx = clientCtx.WithViper("") + //} + // + //if err := clientCtx.Viper.BindPFlags(flagSet); err != nil { + // return Factory{}, fmt.Errorf("failed to bind flags to viper: %w", err) + //} + // + //var accNum, accSeq uint64 + //if clientCtx.Offline { + // if flagSet.Changed(flags.FlagAccountNumber) && flagSet.Changed(flags.FlagSequence) { + // accNum = clientCtx.Viper.GetUint64(flags.FlagAccountNumber) + // accSeq = clientCtx.Viper.GetUint64(flags.FlagSequence) + // } else { + // return Factory{}, fmt.Errorf("account-number and sequence must be set in offline mode") + // } + //} + // + //if clientCtx.Offline && clientCtx.GenerateOnly { + // if clientCtx.ChainID != "" { + // return Factory{}, errors.New("chain ID cannot be used when offline and generate-only flags are set") + // } + //} else if clientCtx.ChainID == "" { + // return Factory{}, errors.New("chain ID required but not specified") + //} + // + //signMode := flags.ParseSignModeStr(clientCtx.SignModeStr) + //memo := clientCtx.Viper.GetString(flags.FlagNote) + //timeoutHeight := clientCtx.Viper.GetUint64(flags.FlagTimeoutHeight) + //unordered := clientCtx.Viper.GetBool(flags.FlagUnordered) + // + //gasAdj := clientCtx.Viper.GetFloat64(flags.FlagGasAdjustment) + //gasStr := clientCtx.Viper.GetString(flags.FlagGas) + //gasSetting, _ := flags.ParseGasSetting(gasStr) + //gasPricesStr := clientCtx.Viper.GetString(flags.FlagGasPrices) + // + //feesStr := clientCtx.Viper.GetString(flags.FlagFees) + + f := Factory{ + keybase: keybase, + accountRetriever: accRetriever, + ac: ac, + txConfig: txConfig, + txParams: parameters, + } + + // Properties that need special parsing + //f = f.WithFees(feesStr).WithGasPrices(gasPricesStr) + return f, nil +} + +// 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. +// Note: When in offline mode, the Prepare does nothing and returns the original factory. +func (f Factory) Prepare(clientCtx tx.Context) (Factory, error) { + if f.txParams.ExecutionOptions.offline { + return f, nil + } + + if f.txParams.fromAddress.Empty() { + return f, errors.New("missing 'from address' field") + } + + if err := f.accountRetriever.EnsureExists(clientCtx, f.txParams.fromAddress); err != nil { + return f, err + } + + if f.txParams.accountNumber == 0 || f.txParams.sequence == 0 { + fc := f + num, seq, err := fc.accountRetriever.GetAccountNumberSequence(clientCtx, f.txParams.fromAddress) + if err != nil { + return f, err + } + + if f.txParams.accountNumber == 0 { + fc = fc.WithAccountNumber(num) + } + + if f.txParams.sequence == 0 { + fc = fc.WithSequence(seq) + } + + return fc, nil + } + + return f, nil +} + +var ( + _ withAmount = &base.Coin{} + _ withAmount = &base.DecCoin{} +) + +type withAmount interface { + GetAmount() string +} + +func isZero[T withAmount](coins []T) (bool, error) { + for _, coin := range coins { + amount, ok := math.NewIntFromString(coin.GetAmount()) + if !ok { + return false, errors.New("invalid coin amount") + } + if !amount.IsZero() { + return false, nil + } + } + return true, nil +} + +// 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 ...transaction.Msg) (TxBuilder, error) { + if f.txParams.offline && f.txParams.generateOnly { + if f.txParams.chainID != "" { + return nil, errors.New("chain ID cannot be used when offline and generate-only flags are set") + } + } else if f.txParams.chainID == "" { + return nil, errors.New("chain ID required but not specified") + } + + fees := f.txParams.fees + + gasPriceIsZero, err := isZero(f.txParams.gasPrices) + if err != nil { + return nil, err + } + if !gasPriceIsZero { + feesIsZero, err := isZero(fees) + if err != nil { + return nil, err + } + if !feesIsZero { + return nil, errors.New("cannot provide both fees and gas prices") + } + + // f.gas is an uint64 and we should convert to LegacyDec + // without the risk of under/overflow via uint64->int64. + glDec := math.LegacyNewDecFromBigInt(new(big.Int).SetUint64(f.txParams.gas)) + + // Derive the fees based on the provided gas prices, where + // fee = ceil(gasPrice * gasLimit). + fees = make([]*base.Coin, len(f.txParams.gasPrices)) + + for i, gp := range f.txParams.gasPrices { + fee, err := math.LegacyNewDecFromStr(gp.Amount) + if err != nil { + return nil, err + } + fee = fee.Mul(glDec) + fees[i] = &base.Coin{Denom: gp.Denom, Amount: fee.Ceil().RoundInt().String()} + } + } + + if err := ValidateMemo(f.txParams.memo); err != nil { + return nil, err + } + + txBuilder := f.txConfig.NewTxBuilder() + if err := txBuilder.SetMsgs(msgs...); err != nil { + return nil, err + } + + txBuilder.SetMemo(f.txParams.memo) + txBuilder.SetFeeAmount(fees) + txBuilder.SetGasLimit(f.txParams.gas) + txBuilder.SetFeeGranter(f.txParams.feeGranter.String()) + txBuilder.SetFeePayer(f.txParams.feePayer.String()) + txBuilder.SetTimeoutHeight(f.txParams.timeoutHeight) + + if etx, ok := txBuilder.(ExtendedTxBuilder); ok { + etx.SetExtensionOptions(f.txParams.ExtOptions...) + } + + return txBuilder, 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. +// TODO: this should notbe part of factory or at least just return the json encoded string. +func (f Factory) PrintUnsignedTx(clientCtx tx.Context, msgs ...transaction.Msg) (string, error) { + if f.SimulateAndExecute() { + if f.txParams.offline { + return "", errors.New("cannot estimate gas in offline mode") + } + + // Prepare TxFactory with acc & seq numbers as CalculateGas requires + // account and sequence numbers to be set + preparedTxf, err := f.Prepare(clientCtx) + if err != nil { + return "", err + } + + _, adjusted, err := CalculateGas(f.conn, preparedTxf, msgs...) + if err != nil { + return "", err + } + + f.WithGas(adjusted) + _, _ = fmt.Fprintf(os.Stderr, "%s\n", GasEstimateResponse{GasEstimate: f.Gas()}) + } + + unsignedTx, err := f.BuildUnsignedTx(msgs...) + if err != nil { + return "", err + } + + encoder := f.txConfig.TxJSONEncoder() + if encoder == nil { + return "", errors.New("cannot print unsigned tx: tx json encoder is nil") + } + + json, err := encoder(unsignedTx.GetTx()) + if err != nil { + return "", err + } + + return fmt.Sprintf("%s\n", json), 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 (f Factory) BuildSimTx(msgs ...transaction.Msg) ([]byte, error) { + txb, err := f.BuildUnsignedTx(msgs...) + if err != nil { + return nil, err + } + + pk, err := f.getSimPK() + if err != nil { + return nil, err + } + + // Create an empty signature literal as the ante handler will populate with a + // sentinel pubkey. + sig := Signature{ + PubKey: pk, + Data: f.getSimSignatureData(pk), + Sequence: f.Sequence(), + } + if err := txb.SetSignatures(sig); err != nil { + return nil, err + } + + encoder := f.txConfig.TxEncoder() + if encoder == nil { + return nil, fmt.Errorf("cannot simulate tx: tx encoder is nil") + } + + return encoder(txb.GetTx()) +} + +// Sign signs a given tx with a named key. The bytes signed over are canonical. +// The resulting signature will be added to the transaction builder overwriting the previous +// ones if overwrite=true (otherwise, the signature will be appended). +// Signing a transaction with multiple signers in the DIRECT mode is not supported and will +// return an error. +// An error is returned upon failure. +func (f Factory) Sign(ctx context.Context, name string, txBuilder TxBuilder, overwriteSig bool) error { + if f.keybase == nil { + return errors.New("keybase must be set prior to signing a transaction") + } + + var err error + signMode := f.txParams.signMode + if signMode == apitxsigning.SignMode_SIGN_MODE_UNSPECIFIED { + signMode = f.txConfig.SignModeHandler().DefaultMode() + } + + pubKey, err := f.keybase.GetPubKey(name) + if err != nil { + return err + } + + signerData := signing.SignerData{ + ChainID: f.txParams.chainID, + AccountNumber: f.txParams.accountNumber, + Sequence: f.txParams.sequence, + PubKey: &anypb.Any{ + TypeUrl: pubKey.Type(), // TODO: check correctness + Value: pubKey.Bytes(), + }, + Address: sdk.AccAddress(pubKey.Address()).String(), + } + + tx := txBuilder.GetTx() + txWrap := TxWrapper{Tx: &tx} + + // For SIGN_MODE_DIRECT, calling SetSignatures calls setSignerInfos on + // TxBuilder under the hood, and SignerInfos is needed to be generated the + // sign bytes. This is the reason for setting SetSignatures here, with a + // nil signature. + // + // Note: this line is not needed for SIGN_MODE_LEGACY_AMINO, but putting it + // also doesn't affect its generated sign bytes, so for code's simplicity + // sake, we put it here. + sigData := SingleSignatureData{ + SignMode: signMode, + Signature: nil, + } + sig := Signature{ + PubKey: pubKey, + Data: &sigData, + Sequence: f.txParams.sequence, + } + + var prevSignatures []Signature + if !overwriteSig { + prevSignatures, err = txWrap.GetSignatures() + if err != nil { + return err + } + } + // Overwrite or append signer infos. + var sigs []Signature + if overwriteSig { + sigs = []Signature{sig} + } else { + sigs = append(sigs, prevSignatures...) + sigs = append(sigs, sig) + } + if err := txBuilder.SetSignatures(sigs...); err != nil { + return err + } + + if err := checkMultipleSigners(txWrap); err != nil { + return err + } + + bytesToSign, err := f.GetSignBytesAdapter(ctx, signerData, txBuilder) + if err != nil { + return err + } + + // Sign those bytes + sigBytes, err := f.keybase.Sign(name, bytesToSign, signMode) + if err != nil { + return err + } + + // Construct the SignatureV2 struct + sigData = SingleSignatureData{ + SignMode: signMode, + Signature: sigBytes, + } + sig = Signature{ + PubKey: pubKey, + Data: &sigData, + Sequence: f.txParams.sequence, + } + + if overwriteSig { + err = txBuilder.SetSignatures(sig) + } else { + prevSignatures = append(prevSignatures, sig) + err = txBuilder.SetSignatures(prevSignatures...) + } + + if err != nil { + return fmt.Errorf("unable to set signatures on payload: %w", err) + } + + // Run optional preprocessing if specified. By default, this is unset + // and will return nil. + return f.PreprocessTx(name, txBuilder) +} + +// GetSignBytesAdapter returns the sign bytes for a given transaction and sign mode. +func (f Factory) GetSignBytesAdapter(ctx context.Context, signerData signing.SignerData, builder TxBuilder) ([]byte, error) { + // TODO + txSignerData := signing.SignerData{ + ChainID: signerData.ChainID, + AccountNumber: signerData.AccountNumber, + Sequence: signerData.Sequence, + Address: signerData.Address, + PubKey: signerData.PubKey, + } + // Generate the bytes to be signed. + return f.txConfig.SignModeHandler().GetSignBytes(ctx, f.SignMode(), txSignerData, builder.GetSigningTxData()) +} + +func ValidateMemo(memo string) error { + // Prevent simple inclusion of a valid mnemonic in the memo field + if memo != "" && bip39.IsMnemonicValid(strings.ToLower(memo)) { + return errors.New("cannot provide a valid mnemonic seed in the memo field") + } + + return nil +} + +// WithAccountRetriever returns a copy of the Factory with an updated AccountRetriever. +func (f Factory) WithAccountRetriever(ar authtypes.AccountRetriever) Factory { + f.accountRetriever = ar + return f +} + +// WithChainID returns a copy of the Factory with an updated chainID. +func (f Factory) WithChainID(chainID string) Factory { + f.txParams.chainID = chainID + return f +} + +// WithGas returns a copy of the Factory with an updated gas value. +func (f Factory) WithGas(gas uint64) Factory { + f.txParams.gas = gas + return f +} + +// WithFees returns a copy of the Factory with an updated fee. +func (f Factory) WithFees(fees string) Factory { + parsedFees, err := sdk.ParseCoinsNormalized(fees) // TODO: do it here to avoid sdk dependency + if err != nil { + panic(err) + } + + finalFees := make([]*base.Coin, len(parsedFees)) + for i, coin := range parsedFees { + finalFees[i] = &base.Coin{ + Denom: coin.Denom, + Amount: coin.Amount.String(), + } + } + + f.txParams.fees = finalFees + return f +} + +// WithGasPrices returns a copy of the Factory with updated gas prices. +func (f Factory) WithGasPrices(gasPrices string) Factory { + parsedGasPrices, err := sdk.ParseDecCoins(gasPrices) // TODO: do it here to avoid sdk dependency + if err != nil { + panic(err) + } + + finalGasPrices := make([]*base.DecCoin, len(parsedGasPrices)) + for i, coin := range parsedGasPrices { + finalGasPrices[i] = &base.DecCoin{ + Denom: coin.Denom, + Amount: coin.Amount.String(), + } + } + + f.txParams.gasPrices = finalGasPrices + return f +} + +// WithKeybase returns a copy of the Factory with updated Keybase. +func (f Factory) WithKeybase(keybase keyring.Keyring) Factory { + f.keybase = keybase + return f +} + +// WithFromName returns a copy of the Factory with updated fromName +// fromName will be use for building a simulation tx. +func (f Factory) WithFromName(fromName string) Factory { + f.txParams.fromName = fromName + return f +} + +// WithSequence returns a copy of the Factory with an updated sequence number. +func (f Factory) WithSequence(sequence uint64) Factory { + f.txParams.sequence = sequence + return f +} + +// WithMemo returns a copy of the Factory with an updated memo. +func (f Factory) WithMemo(memo string) Factory { + f.txParams.memo = memo + return f +} + +// WithAccountNumber returns a copy of the Factory with an updated account number. +func (f Factory) WithAccountNumber(accnum uint64) Factory { + f.txParams.accountNumber = accnum + return f +} + +// WithGasAdjustment returns a copy of the Factory with an updated gas adjustment. +func (f Factory) WithGasAdjustment(gasAdj float64) Factory { + f.txParams.gasAdjustment = gasAdj + return f +} + +// WithSimulateAndExecute returns a copy of the Factory with an updated gas +// simulation value. +func (f Factory) WithSimulateAndExecute(sim bool) Factory { + f.txParams.simulateAndExecute = sim + return f +} + +// WithSignMode returns a copy of the Factory with an updated sign mode value. +func (f Factory) WithSignMode(mode apitxsigning.SignMode) Factory { + f.txParams.signMode = mode + return f +} + +// WithTimeoutHeight returns a copy of the Factory with an updated timeout height. +func (f Factory) WithTimeoutHeight(height uint64) Factory { + f.txParams.timeoutHeight = height + return f +} + +// WithFeeGranter returns a copy of the Factory with an updated fee granter. +func (f Factory) WithFeeGranter(fg sdk.AccAddress) Factory { + f.txParams.feeGranter = fg + return f +} + +// WithFeePayer returns a copy of the Factory with an updated fee granter. +func (f Factory) WithFeePayer(fp sdk.AccAddress) Factory { + f.txParams.feePayer = fp + return f +} + +// WithPreprocessTxHook returns a copy of the Factory with an updated preprocess tx function, +// allows for preprocessing of transaction data using the TxBuilder. +func (f Factory) WithPreprocessTxHook(preprocessFn PreprocessTxFn) Factory { + f.txParams.preprocessTxHook = preprocessFn + return f +} + +func (f Factory) WithExtensionOptions(extOpts ...*codectypes.Any) Factory { + f.txParams.ExtOptions = extOpts + return f +} + +// PreprocessTx calls the preprocessing hook with the factory parameters and +// returns the result. +func (f Factory) PreprocessTx(keyname string, builder TxBuilder) error { + if f.txParams.preprocessTxHook == nil { + // Allow pass-through + return nil + } + + keyType, err := f.Keybase().KeyType(keyname) + if err != nil { + return err + } + return f.txParams.preprocessTxHook(f.txParams.chainID, keyType, builder) +} + +func (f Factory) AccountNumber() uint64 { return f.txParams.accountNumber } +func (f Factory) Sequence() uint64 { return f.txParams.sequence } +func (f Factory) Gas() uint64 { return f.txParams.gas } +func (f Factory) GasAdjustment() float64 { return f.txParams.gasAdjustment } +func (f Factory) Keybase() keyring.Keyring { return f.keybase } +func (f Factory) ChainID() string { return f.txParams.chainID } +func (f Factory) Memo() string { return f.txParams.memo } +func (f Factory) Fees() []*base.Coin { return f.txParams.fees } +func (f Factory) GasPrices() []*base.DecCoin { return f.txParams.gasPrices } +func (f Factory) AccountRetriever() authtypes.AccountRetriever { return f.accountRetriever } +func (f Factory) TimeoutHeight() uint64 { return f.txParams.timeoutHeight } +func (f Factory) FromName() string { return f.txParams.fromName } +func (f Factory) SimulateAndExecute() bool { return f.txParams.simulateAndExecute } +func (f Factory) SignMode() apitxsigning.SignMode { return f.txParams.signMode } + +// getSimPK gets the public key to use for building a simulation tx. +// Note, we should only check for keys in the keybase if we are in simulate and execute mode, +// e.g. when using --gas=auto. +// When using --dry-run, we are is simulation mode only and should not check the keybase. +// Ref: https://github.com/cosmos/cosmos-sdk/issues/11283 +func (f Factory) getSimPK() (cryptotypes.PubKey, error) { + var ( + err error + pk cryptotypes.PubKey = &secp256k1.PubKey{} + ) + + if f.txParams.simulateAndExecute && f.keybase != nil { + pk, err = f.keybase.GetPubKey(f.txParams.fromName) + if err != nil { + return nil, err + } + } + + return pk, nil +} + +// getSimSignatureData based on the pubKey type gets the correct SignatureData type +// to use for building a simulation tx. +func (f Factory) getSimSignatureData(pk cryptotypes.PubKey) SignatureData { + multisigPubKey, ok := pk.(*multisig.LegacyAminoPubKey) + if !ok { + return &SingleSignatureData{SignMode: f.txParams.signMode} + } + + multiSignatureData := make([]SignatureData, 0, multisigPubKey.Threshold) + for i := uint32(0); i < multisigPubKey.Threshold; i++ { + multiSignatureData = append(multiSignatureData, &SingleSignatureData{ + SignMode: f.SignMode(), + }) + } + + return &MultiSignatureData{ + Signatures: multiSignatureData, + } +} diff --git a/client/v2/tx/tx.go b/client/v2/tx/tx.go new file mode 100644 index 000000000000..f671d6738731 --- /dev/null +++ b/client/v2/tx/tx.go @@ -0,0 +1,258 @@ +package tx + +import ( + "bufio" + "context" + apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + apitx "cosmossdk.io/api/cosmos/tx/v1beta1" + tx2 "cosmossdk.io/client/v2/internal" + "errors" + "fmt" + "github.com/cosmos/cosmos-sdk/client/input" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + gogogrpc "github.com/cosmos/gogoproto/grpc" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/spf13/pflag" + "os" +) + +// GenerateOrBroadcastTxCLI will either generate and print an unsigned transaction +// or sign it and broadcast it returning an error upon failure. +func GenerateOrBroadcastTxCLI(clientCtx tx2.Context, flagSet *pflag.FlagSet, msgs ...sdk.Msg) error { + txf, err := NewFactoryCLI(clientCtx, flagSet) + if err != nil { + return err + } + + return GenerateOrBroadcastTxWithFactory(clientCtx, txf, msgs...) +} + +// GenerateOrBroadcastTxWithFactory will either generate and print an unsigned transaction +// or sign it and broadcast it returning an error upon failure. +func GenerateOrBroadcastTxWithFactory(clientCtx tx2.Context, txf Factory, msgs ...sdk.Msg) error { + // Validate all msgs before generating or broadcasting the tx. + // We were calling ValidateBasic separately in each CLI handler before. + // Right now, we're factorizing that call inside this function. + // ref: https://github.com/cosmos/cosmos-sdk/pull/9236#discussion_r623803504 + for _, msg := range msgs { + m, ok := msg.(sdk.HasValidateBasic) + if !ok { + continue + } + + if err := m.ValidateBasic(); err != nil { + return err + } + } + + // If the --aux flag is set, we simply generate and print the AuxSignerData. + if clientCtx.IsAux { + auxSignerData, err := makeAuxSignerData(clientCtx, txf, msgs...) + if err != nil { + return err + } + + return clientCtx.PrintProto(&auxSignerData) + } + + if clientCtx.GenerateOnly { + return txf.PrintUnsignedTx(clientCtx, msgs...) + } + + return BroadcastTx(clientCtx, txf, msgs...) +} + +// 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 tx2.Context, txf Factory, msgs ...sdk.Msg) error { + txf, err := txf.Prepare(clientCtx) + if err != nil { + return err + } + + if txf.SimulateAndExecute() || clientCtx.Simulate { + 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()}) + } + + if clientCtx.Simulate { + return nil + } + + tx, err := txf.BuildUnsignedTx(msgs...) + if err != nil { + return err + } + + if !clientCtx.SkipConfirm { + encoder := txf.txConfig.TxJSONEncoder() + if encoder == nil { + return errors.New("failed to encode transaction: tx json encoder is nil") + } + + txBytes, err := encoder(tx.GetTx()) + if err != nil { + return fmt.Errorf("failed to encode transaction: %w", err) + } + + if err := clientCtx.PrintRaw(txBytes); err != nil { + _, _ = fmt.Fprintf(os.Stderr, "error: %v\n%s\n", err, txBytes) + } + + buf := bufio.NewReader(os.Stdin) + ok, err := input.GetConfirmation("confirm transaction before signing and broadcasting", buf, os.Stderr) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "error: %v\ncanceled transaction\n", err) + return err + } + if !ok { + _, _ = fmt.Fprintln(os.Stderr, "canceled transaction") + return nil + } + } + + if err = txf.Sign(clientCtx.CmdContext, clientCtx.FromName, tx, true); err != nil { + return err + } + + txBytes, err := clientCtx.TxConfig.TxEncoder()(tx.GetTx()) + if err != nil { + return err + } + + // broadcast to a CometBFT node + res, err := clientCtx.BroadcastTx(txBytes) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) +} + +// 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 := txf.BuildSimTx(msgs...) + if err != nil { + return nil, 0, err + } + + txSvcClient := tx.NewServiceClient(clientCtx) + simRes, err := txSvcClient.Simulate(context.Background(), &tx.SimulateRequest{ + TxBytes: txBytes, + }) + if err != nil { + return nil, 0, err + } + + return simRes, uint64(txf.GasAdjustment() * float64(simRes.GasInfo.GasUsed)), nil +} + +// makeAuxSignerData generates an AuxSignerData from the client inputs. +func makeAuxSignerData(clientCtx tx2.Context, f Factory, msgs ...sdk.Msg) (*apitx.AuxSignerData, error) { + b := NewAuxTxBuilder() + fromAddress, name, _, err := tx2.GetFromFields(clientCtx, clientCtx.Keyring, clientCtx.From) + if err != nil { + return nil, err + } + + b.SetAddress(fromAddress.String()) + if f.txParams.offline { + b.SetAccountNumber(f.AccountNumber()) + b.SetSequence(f.Sequence()) + } else { + accNum, seq, err := clientCtx.AccountRetriever.GetAccountNumberSequence(clientCtx, fromAddress) + if err != nil { + return nil, err + } + b.SetAccountNumber(accNum) + b.SetSequence(seq) + } + + err = b.SetMsgs(msgs...) + if err != nil { + return nil, err + } + + err = b.SetSignMode(f.SignMode()) + if err != nil { + return nil, err + } + + pubKey, err := clientCtx.Keyring.GetPubKey(name) + if err != nil { + return nil, err + } + + err = b.SetPubKey(pubKey) + if err != nil { + return nil, err + } + + b.SetChainID(clientCtx.ChainID) + signBz, err := b.GetSignBytes() + if err != nil { + return nil, err + } + + sig, err := clientCtx.Keyring.Sign(name, signBz, f.SignMode()) + if err != nil { + return nil, err + } + b.SetSignature(sig) + + return b.GetAuxSignerData() +} + +// checkMultipleSigners checks that there can be maximum one DIRECT signer in +// a tx. +func checkMultipleSigners(tx TxWrapper) error { + directSigners := 0 + sigsV2, err := tx.GetSignatures() + if err != nil { + return err + } + for _, sig := range sigsV2 { + directSigners += countDirectSigners(sig.Data) + if directSigners > 1 { + return sdkerrors.ErrNotSupported.Wrap("txs signed with CLI can have maximum 1 DIRECT signer") + } + } + + return nil +} + +// countDirectSigners counts the number of DIRECT signers in a signature data. +func countDirectSigners(sigData SignatureData) int { + switch data := sigData.(type) { + case *SingleSignatureData: + if data.SignMode == apitxsigning.SignMode_SIGN_MODE_DIRECT { + return 1 + } + + return 0 + case *MultiSignatureData: + directSigners := 0 + for _, d := range data.Signatures { + directSigners += countDirectSigners(d) + } + + return directSigners + default: + panic("unreachable case") + } +} diff --git a/client/v2/tx/types.go b/client/v2/tx/types.go new file mode 100644 index 000000000000..01b87a631c02 --- /dev/null +++ b/client/v2/tx/types.go @@ -0,0 +1,81 @@ +package tx + +import ( + "fmt" + + apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + typestx "cosmossdk.io/api/cosmos/tx/v1beta1" + "cosmossdk.io/core/transaction" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" +) + +// PreprocessTxFn defines a hook by which chains can preprocess transactions before broadcasting +type PreprocessTxFn func(chainID string, key uint, tx TxBuilder) error + +// TxApiDecoder unmarshals transaction bytes into API Tx type +type TxApiDecoder func(txBytes []byte) (typestx.Tx, error) + +// TxApiEncoder marshals transaction to bytes +type TxApiEncoder func(tx typestx.Tx) ([]byte, error) + +// GasEstimateResponse defines a response definition for tx gas estimation. +type GasEstimateResponse struct { + GasEstimate uint64 `json:"gas_estimate" yaml:"gas_estimate"` +} + +func (gr GasEstimateResponse) String() string { + return fmt.Sprintf("gas estimate: %d", gr.GasEstimate) +} + +type TxWrapper struct { + Tx *typestx.Tx +} + +func (tx TxWrapper) GetMsgs() ([]transaction.Msg, error) { + //TODO implement me + panic("implement me") +} + +func (tx TxWrapper) GetSignatures() ([]Signature, error) { + //TODO implement me + panic("implement me") +} + +type Signature struct { + // PubKey is the public key to use for verifying the signature + PubKey cryptotypes.PubKey + + // Data is the actual data of the signature which includes SignMode's and + // the signatures themselves for either single or multi-signatures. + Data SignatureData + + // Sequence is the sequence of this account. Only populated in + // SIGN_MODE_DIRECT. + Sequence uint64 +} + +type SignatureData interface { + isSignatureData() +} + +// SingleSignatureData represents the signature and SignMode of a single (non-multisig) signer +type SingleSignatureData struct { + // SignMode represents the SignMode of the signature + SignMode apitxsigning.SignMode + + // Signature is the raw signature. + Signature []byte +} + +// MultiSignatureData represents the nested SignatureData of a multisig signature +type MultiSignatureData struct { + // BitArray is a compact way of indicating which signers from the multisig key + // have signed + BitArray *cryptotypes.CompactBitArray + + // Signatures is the nested SignatureData's for each signer + Signatures []SignatureData +} + +func (m *SingleSignatureData) isSignatureData() {} +func (m *MultiSignatureData) isSignatureData() {} diff --git a/crypto/keyring/autocli.go b/crypto/keyring/autocli.go index 51569f19170d..23f56c012793 100644 --- a/crypto/keyring/autocli.go +++ b/crypto/keyring/autocli.go @@ -21,6 +21,9 @@ type autoCLIKeyring interface { // Sign signs the given bytes with the key with the given name. Sign(name string, msg []byte, signMode signingv1beta1.SignMode) ([]byte, error) + + // KeyType returns the type of the key. + KeyType(name string) (uint, error) } // NewAutoCLIKeyring wraps the SDK keyring and make it compatible with the AutoCLI keyring interfaces. @@ -84,3 +87,12 @@ func (a *autoCLIKeyringAdapter) Sign(name string, msg []byte, signMode signingv1 signBytes, _, err := a.Keyring.Sign(record.Name, msg, sdkSignMode) return signBytes, err } + +func (a *autoCLIKeyringAdapter) KeyType(name string) (uint, error) { + record, err := a.Keyring.Key(name) + if err != nil { + return 0, err + } + + return uint(record.GetType()), nil +} From 2a777df3ea5fb23480a55cff3e569f90ada20bda Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Wed, 5 Jun 2024 11:53:43 +0200 Subject: [PATCH 02/36] TxBuilder implemented --- client/v2/tx/aux_builder.go | 34 +++- client/v2/tx/builder.go | 344 +++++++++++++++++++++++++++++++++++- client/v2/tx/config.go | 5 +- client/v2/tx/factory.go | 110 +++++++----- client/v2/tx/tx.go | 82 ++++++--- client/v2/tx/types.go | 12 +- 6 files changed, 504 insertions(+), 83 deletions(-) diff --git a/client/v2/tx/aux_builder.go b/client/v2/tx/aux_builder.go index d396f97e927b..2d6b6b9a3e33 100644 --- a/client/v2/tx/aux_builder.go +++ b/client/v2/tx/aux_builder.go @@ -2,6 +2,8 @@ package tx import ( "context" + "errors" + "fmt" "github.com/cosmos/gogoproto/proto" "google.golang.org/protobuf/types/known/anypb" @@ -170,6 +172,34 @@ func (b *AuxTxBuilder) SetNonCriticalExtensionOptions(extOpts ...*codectypes.Any b.auxSignerData.SignDoc.BodyBytes = nil } +// TODO: better place +func validateSignDoc(sd *apitx.SignDocDirectAux) error { + if len(sd.BodyBytes) == 0 { + return errors.New("body bytes is empty") + } + if sd.PublicKey == nil { + return errors.New("public key is empty") + } + return nil +} + +// TODO: better place +func validateAuxSignerData(a *apitx.AuxSignerData) error { + if a.Address == "" { + return errors.New("address is empty") + } + + if a.Mode != apisigning.SignMode_SIGN_MODE_DIRECT_AUX && a.Mode != apisigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON { + return fmt.Errorf("AuxTxBuilder can only sign with %s or %s", apisigning.SignMode_SIGN_MODE_DIRECT_AUX, apisigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON) + } + + if len(a.Sig) == 0 { + return errors.New("signature is empty") + } + + return validateSignDoc(a.GetSignDoc()) +} + // GetSignBytes returns the builder's sign bytes. func (b *AuxTxBuilder) GetSignBytes() ([]byte, error) { auxTx := b.auxSignerData @@ -193,7 +223,7 @@ func (b *AuxTxBuilder) GetSignBytes() ([]byte, error) { } sd.BodyBytes = bodyBz - if err = sd.ValidateBasic(); err != nil { // TODO + if err = validateSignDoc(sd); err != nil { // TODO return nil, err } @@ -256,7 +286,7 @@ func (b *AuxTxBuilder) GetSignBytes() ([]byte, error) { // GetAuxSignerData returns the builder's AuxSignerData. func (b *AuxTxBuilder) GetAuxSignerData() (*apitx.AuxSignerData, error) { - if err := b.auxSignerData.ValidateBasic(); err != nil { // TODO + if err := validateAuxSignerData(b.auxSignerData); err != nil { // TODO return nil, err } diff --git a/client/v2/tx/builder.go b/client/v2/tx/builder.go index 35350cefc167..8b0d4cb1ad88 100644 --- a/client/v2/tx/builder.go +++ b/client/v2/tx/builder.go @@ -1,39 +1,369 @@ package tx import ( - gogoany "github.com/cosmos/gogoproto/types/any" + "errors" + "fmt" + + protov2 "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" base "cosmossdk.io/api/cosmos/base/v1beta1" - typestx "cosmossdk.io/api/cosmos/tx/v1beta1" + apitx "cosmossdk.io/api/cosmos/tx/v1beta1" + "cosmossdk.io/core/address" "cosmossdk.io/core/transaction" + txdecode "cosmossdk.io/x/tx/decode" "cosmossdk.io/x/tx/signing" + "github.com/cosmos/cosmos-sdk/codec" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + gogoany "github.com/cosmos/gogoproto/types/any" +) + +var ( + _ TxBuilder = &txBuilder{} + _ TxBuilderProvider = BuilderProvider{} ) type ExtendedTxBuilder interface { SetExtensionOptions(...*gogoany.Any) // TODO: sdk.Any? } +var marshalOption = protov2.MarshalOptions{ + Deterministic: true, +} + // TxBuilder defines an interface which an application-defined concrete transaction // type must implement. Namely, it must be able to set messages, generate // signatures, and provide canonical bytes to sign over. The transaction must // also know how to encode itself. type TxBuilder interface { - GetTx() typestx.Tx - GetSigningTxData() signing.TxData + GetTx() (*apitx.Tx, error) + GetSigningTxData() (*signing.TxData, error) // TODO: check this SetMsgs(...transaction.Msg) error SetMemo(string) SetFeeAmount([]*base.Coin) - SetFeePayer(string) + SetFeePayer(string) error SetGasLimit(uint64) SetTimeoutHeight(uint64) SetFeeGranter(string) SetUnordered(bool) SetSignatures(...Signature) error - SetAuxSignerData(typestx.AuxSignerData) error + SetAuxSignerData(*apitx.AuxSignerData) error } type TxBuilderProvider interface { NewTxBuilder() TxBuilder - WrapTxBuilder(typestx.Tx) (TxBuilder, error) + WrapTxBuilder(*apitx.Tx) (TxBuilder, error) +} + +type BuilderProvider struct { + addressCodec address.Codec + decoder *txdecode.Decoder + codec codec.BinaryCodec +} + +func NewBuilderProvider(addressCodec address.Codec, decoder *txdecode.Decoder, codec codec.BinaryCodec) *BuilderProvider { + return &BuilderProvider{ + addressCodec: addressCodec, + decoder: decoder, + codec: codec, + } +} +func (b BuilderProvider) NewTxBuilder() TxBuilder { + return newTxBuilder(b.addressCodec, b.decoder, b.codec) +} + +// TODO: work on this +func (b BuilderProvider) WrapTxBuilder(tx *apitx.Tx) (TxBuilder, error) { + return &txBuilder{ + addressCodec: b.addressCodec, + decoder: b.decoder, + codec: b.codec, + }, nil +} + +type txBuilder struct { + addressCodec address.Codec + decoder *txdecode.Decoder + codec codec.BinaryCodec + + msgs []transaction.Msg + timeoutHeight uint64 + granter []byte + payer []byte + unordered bool + memo string + gasLimit uint64 + fees []*base.Coin + signerInfos []*apitx.SignerInfo + signatures [][]byte + + extensionOptions []*anypb.Any + nonCriticalExtensionOptions []*anypb.Any +} + +func newTxBuilder(addressCodec address.Codec, decoder *txdecode.Decoder, codec codec.BinaryCodec) TxBuilder { + return &txBuilder{ + addressCodec: addressCodec, + decoder: decoder, + codec: codec, + } +} + +func (b *txBuilder) GetTx() (*apitx.Tx, error) { + msgs, err := msgsV1toAnyV2(b.msgs) + if err != nil { + return nil, err + } + + // TODO: maybe just another func? + body := &apitx.TxBody{ + Messages: msgs, + Memo: b.memo, + TimeoutHeight: b.timeoutHeight, + Unordered: b.unordered, + ExtensionOptions: b.extensionOptions, + NonCriticalExtensionOptions: b.nonCriticalExtensionOptions, + } + + fee, err := b.getFee() + if err != nil { + return nil, err + } + + authInfo := &apitx.AuthInfo{ + SignerInfos: b.signerInfos, + Fee: fee, + } + + return &apitx.Tx{ + Body: body, + AuthInfo: authInfo, + Signatures: b.signatures, // TODO: I don't like this + }, nil +} + +func msgsV1toAnyV2(msgs []transaction.Msg) ([]*anypb.Any, error) { + anys := make([]*codectypes.Any, len(msgs)) + for i, msg := range msgs { + anyMsg, err := codectypes.NewAnyWithValue(msg) + if err != nil { + return nil, err + } + anys[i] = anyMsg + } + + return intoAnyV2(anys), nil +} + +func intoAnyV2(v1s []*codectypes.Any) []*anypb.Any { + v2s := make([]*anypb.Any, len(v1s)) + for i, v1 := range v1s { + v2s[i] = &anypb.Any{ + TypeUrl: v1.TypeUrl, + Value: v1.Value, + } + } + return v2s +} + +func (b *txBuilder) getFee() (fee *apitx.Fee, err error) { + granterStr := "" + if b.granter != nil { + granterStr, err = b.addressCodec.BytesToString(b.granter) + if err != nil { + return nil, err + } + } + + payerStr := "" + if b.payer != nil { + payerStr, err = b.addressCodec.BytesToString(b.payer) + if err != nil { + return nil, err + } + } + fee = &apitx.Fee{ + Amount: b.fees, + GasLimit: b.gasLimit, + Payer: payerStr, + Granter: granterStr, + } + + return fee, nil +} + +func (b *txBuilder) GetSigningTxData() (*signing.TxData, error) { + tx, err := b.GetTx() + if err != nil { + return nil, err + } + + bodyBytes, err := marshalOption.Marshal(tx.Body) + if err != nil { + return nil, err + } + authBytes, err := marshalOption.Marshal(tx.AuthInfo) + if err != nil { + return nil, err + } + + rawTx, err := marshalOption.Marshal(&apitx.TxRaw{ + BodyBytes: bodyBytes, + AuthInfoBytes: authBytes, + Signatures: b.signatures, + }) + if err != nil { + return nil, err + } + + decodedTx, err := b.decoder.Decode(rawTx) + if err != nil { + return nil, err + } + + return &signing.TxData{ + Body: decodedTx.Tx.Body, + AuthInfo: decodedTx.Tx.AuthInfo, + BodyBytes: decodedTx.TxRaw.BodyBytes, + AuthInfoBytes: decodedTx.TxRaw.AuthInfoBytes, + BodyHasUnknownNonCriticals: decodedTx.TxBodyHasUnknownNonCriticals, + }, nil +} + +func (b *txBuilder) SetMsgs(msgs ...transaction.Msg) error { + b.msgs = msgs + return nil +} + +func (b *txBuilder) SetMemo(memo string) { + b.memo = memo +} + +func (b *txBuilder) SetFeeAmount(coins []*base.Coin) { + b.fees = coins +} + +func (b *txBuilder) SetFeePayer(feePayer string) error { + addr, err := b.addressCodec.StringToBytes(feePayer) + if err != nil { + return err + } + b.payer = addr + return nil +} + +func (b *txBuilder) SetGasLimit(gasLimit uint64) { + b.gasLimit = gasLimit +} + +func (b *txBuilder) SetTimeoutHeight(timeoutHeight uint64) { + b.timeoutHeight = timeoutHeight +} + +func (b *txBuilder) SetFeeGranter(feeGranter string) { + b.granter = []byte(feeGranter) +} + +func (b *txBuilder) SetUnordered(unordered bool) { + b.unordered = unordered +} + +func (b *txBuilder) SetSignatures(signatures ...Signature) error { + n := len(signatures) + signerInfos := make([]*apitx.SignerInfo, n) + rawSignatures := make([][]byte, n) + + for i, sig := range signatures { + var ( + modeInfo *apitx.ModeInfo + pubKey *codectypes.Any + err error + anyPk *anypb.Any + ) + + modeInfo, rawSignatures[i] = SignatureDataToModeInfoAndSig(sig.Data) + if sig.PubKey != nil { + pubKey, err = codectypes.NewAnyWithValue(sig.PubKey) + if err != nil { + return err + } + anyPk = &anypb.Any{ + TypeUrl: pubKey.TypeUrl, + Value: pubKey.Value, + } + } + + signerInfos[i] = &apitx.SignerInfo{ + PublicKey: anyPk, + ModeInfo: modeInfo, + Sequence: sig.Sequence, + } + } + + b.signerInfos = signerInfos + b.signatures = rawSignatures + + return nil +} + +// TODO: check this +func (b *txBuilder) SetAuxSignerData(data *apitx.AuxSignerData) error { + /* + if data == nil { + return errors.New("aux signer data cannot be nil") + } + any, err := codectypes.NewAnyWithValue(data) + if err != nil { + return err + } + b.extensionOptions = append(b.extensionOptions, any) + return nil + */ + return errors.New("not supported") +} + +// SignatureDataToModeInfoAndSig converts a SignatureData to a ModeInfo and raw bytes signature +func SignatureDataToModeInfoAndSig(data SignatureData) (*apitx.ModeInfo, []byte) { + if data == nil { + return nil, nil + } + + switch data := data.(type) { + case *SingleSignatureData: + return &apitx.ModeInfo{ + Sum: &apitx.ModeInfo_Single_{ + Single: &apitx.ModeInfo_Single{Mode: data.SignMode}, + }, + }, data.Signature + case *MultiSignatureData: + n := len(data.Signatures) + modeInfos := make([]*apitx.ModeInfo, n) + sigs := make([][]byte, n) + + for i, d := range data.Signatures { + modeInfos[i], sigs[i] = SignatureDataToModeInfoAndSig(d) + } + + multisig := cryptotypes.MultiSignature{ + Signatures: sigs, + } + sig, err := multisig.Marshal() + if err != nil { + panic(err) + } + + return &apitx.ModeInfo{ + Sum: &apitx.ModeInfo_Multi_{ + Multi: &apitx.ModeInfo_Multi{ + Bitarray: data.BitArray, + ModeInfos: modeInfos, + }, + }, + }, sig + default: + panic(fmt.Sprintf("unexpected signature data type %T", data)) + } } diff --git a/client/v2/tx/config.go b/client/v2/tx/config.go index 0fb3dfd8294a..4193d56b5619 100644 --- a/client/v2/tx/config.go +++ b/client/v2/tx/config.go @@ -7,7 +7,6 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" - signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" ) // TxConfig defines an interface a client can utilize to generate an @@ -31,8 +30,8 @@ type TxEncodingConfig interface { type TxSigningConfig interface { SignModeHandler() *signing.HandlerMap SigningContext() *signing.Context - MarshalSignatureJSON([]signingtypes.SignatureV2) ([]byte, error) - UnmarshalSignatureJSON([]byte) ([]signingtypes.SignatureV2, error) + MarshalSignatureJSON([]Signature) ([]byte, error) + UnmarshalSignatureJSON([]byte) ([]Signature, error) } type TxParameters struct { diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index 00e4360b3791..3dadd95fce2c 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -18,7 +18,6 @@ import ( "cosmossdk.io/core/address" "cosmossdk.io/core/transaction" "cosmossdk.io/math" - authtypes "cosmossdk.io/x/auth/types" "cosmossdk.io/x/tx/signing" codectypes "github.com/cosmos/cosmos-sdk/codec/types" @@ -28,10 +27,12 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// TODO +// TODO: +// FactoryI type FactoryI interface { BuildAuxSignerData() - BuildUnsignedTx() + BuildUnsignedTx(msg ...transaction.Msg) (TxBuilder, error) // TODO: should return a TxBuilder or a tx already? + SignUnsignedTx() BuildSignedTx() BuildSimulationTx() } @@ -40,14 +41,15 @@ type FactoryI interface { // signing an application-specific transaction. type Factory struct { keybase keyring.Keyring - accountRetriever authtypes.AccountRetriever // TODO: define in v2/tx + accountRetriever AccountRetriever ac address.Codec conn gogogrpc.ClientConn txConfig TxConfig txParams TxParameters } -func NewFactoryCLI(keybase keyring.Keyring, accRetriever authtypes.AccountRetriever, ac address.Codec, txConfig TxConfig, parameters TxParameters) (Factory, error) { +// NewFactory returns a factory +func NewFactory(keybase keyring.Keyring, ac address.Codec, conn gogogrpc.ClientConn, parameters TxParameters) (Factory, error) { //if clientCtx.Viper == nil { // clientCtx = clientCtx.WithViper("") //} @@ -87,11 +89,12 @@ func NewFactoryCLI(keybase keyring.Keyring, accRetriever authtypes.AccountRetrie //feesStr := clientCtx.Viper.GetString(flags.FlagFees) f := Factory{ - keybase: keybase, - accountRetriever: accRetriever, - ac: ac, - txConfig: txConfig, - txParams: parameters, + keybase: keybase, + //accountRetriever: accRetriever, // TODO: pass as argument or call constructor + ac: ac, + conn: conn, + //txConfig: txConfig, // TODO: pass as argument or call constructor + txParams: parameters, } // Properties that need special parsing @@ -104,7 +107,7 @@ func NewFactoryCLI(keybase keyring.Keyring, accRetriever authtypes.AccountRetrie // they will be queried for and set on the provided Factory. // A new Factory with the updated fields will be returned. // Note: When in offline mode, the Prepare does nothing and returns the original factory. -func (f Factory) Prepare(clientCtx tx.Context) (Factory, error) { +func (f Factory) Prepare() (Factory, error) { if f.txParams.ExecutionOptions.offline { return f, nil } @@ -113,13 +116,13 @@ func (f Factory) Prepare(clientCtx tx.Context) (Factory, error) { return f, errors.New("missing 'from address' field") } - if err := f.accountRetriever.EnsureExists(clientCtx, f.txParams.fromAddress); err != nil { + if err := f.accountRetriever.EnsureExists(f.txParams.fromAddress); err != nil { return f, err } if f.txParams.accountNumber == 0 || f.txParams.sequence == 0 { fc := f - num, seq, err := fc.accountRetriever.GetAccountNumberSequence(clientCtx, f.txParams.fromAddress) + num, seq, err := fc.accountRetriever.GetAccountNumberSequence(f.txParams.fromAddress) if err != nil { return f, err } @@ -173,16 +176,16 @@ func (f Factory) BuildUnsignedTx(msgs ...transaction.Msg) (TxBuilder, error) { fees := f.txParams.fees - gasPriceIsZero, err := isZero(f.txParams.gasPrices) + isGasPriceZero, err := isZero(f.txParams.gasPrices) if err != nil { return nil, err } - if !gasPriceIsZero { - feesIsZero, err := isZero(fees) + if !isGasPriceZero { + areFeesZero, err := isZero(fees) if err != nil { return nil, err } - if !feesIsZero { + if !areFeesZero { return nil, errors.New("cannot provide both fees and gas prices") } @@ -231,8 +234,8 @@ func (f Factory) BuildUnsignedTx(msgs ...transaction.Msg) (TxBuilder, error) { // 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. -// TODO: this should notbe part of factory or at least just return the json encoded string. -func (f Factory) PrintUnsignedTx(clientCtx tx.Context, msgs ...transaction.Msg) (string, error) { +// TODO: this should not be part of factory or at least just return the json encoded string. +func (f Factory) PrintUnsignedTx(msgs ...transaction.Msg) (string, error) { if f.SimulateAndExecute() { if f.txParams.offline { return "", errors.New("cannot estimate gas in offline mode") @@ -240,7 +243,7 @@ func (f Factory) PrintUnsignedTx(clientCtx tx.Context, msgs ...transaction.Msg) // Prepare TxFactory with acc & seq numbers as CalculateGas requires // account and sequence numbers to be set - preparedTxf, err := f.Prepare(clientCtx) + preparedTxf, err := f.Prepare() if err != nil { return "", err } @@ -254,7 +257,7 @@ func (f Factory) PrintUnsignedTx(clientCtx tx.Context, msgs ...transaction.Msg) _, _ = fmt.Fprintf(os.Stderr, "%s\n", GasEstimateResponse{GasEstimate: f.Gas()}) } - unsignedTx, err := f.BuildUnsignedTx(msgs...) + builder, err := f.BuildUnsignedTx(msgs...) if err != nil { return "", err } @@ -263,8 +266,13 @@ func (f Factory) PrintUnsignedTx(clientCtx tx.Context, msgs ...transaction.Msg) if encoder == nil { return "", errors.New("cannot print unsigned tx: tx json encoder is nil") } + + tx, err := builder.GetTx() + if err != nil { + return "", err + } - json, err := encoder(unsignedTx.GetTx()) + json, err := encoder(tx) if err != nil { return "", err } @@ -302,7 +310,11 @@ func (f Factory) BuildSimTx(msgs ...transaction.Msg) ([]byte, error) { return nil, fmt.Errorf("cannot simulate tx: tx encoder is nil") } - return encoder(txb.GetTx()) + tx, err := txb.GetTx() + if err != nil { + return nil, err + } + return encoder(tx) } // Sign signs a given tx with a named key. The bytes signed over are canonical. @@ -327,6 +339,11 @@ func (f Factory) Sign(ctx context.Context, name string, txBuilder TxBuilder, ove return err } + addr, err := f.ac.BytesToString(pubKey.Address()) + if err != nil { + return err + } + signerData := signing.SignerData{ ChainID: f.txParams.chainID, AccountNumber: f.txParams.accountNumber, @@ -335,11 +352,14 @@ func (f Factory) Sign(ctx context.Context, name string, txBuilder TxBuilder, ove TypeUrl: pubKey.Type(), // TODO: check correctness Value: pubKey.Bytes(), }, - Address: sdk.AccAddress(pubKey.Address()).String(), + Address: addr, } - tx := txBuilder.GetTx() - txWrap := TxWrapper{Tx: &tx} + tx, err := txBuilder.GetTx() + if err != nil { + return err + } + txWrap := TxWrapper{Tx: tx} // For SIGN_MODE_DIRECT, calling SetSignatures calls setSignerInfos on // TxBuilder under the hood, and SignerInfos is needed to be generated the @@ -430,8 +450,14 @@ func (f Factory) GetSignBytesAdapter(ctx context.Context, signerData signing.Sig Address: signerData.Address, PubKey: signerData.PubKey, } + + txData, err := builder.GetSigningTxData() + if err != nil { + return nil, err + } + // Generate the bytes to be signed. - return f.txConfig.SignModeHandler().GetSignBytes(ctx, f.SignMode(), txSignerData, builder.GetSigningTxData()) + return f.txConfig.SignModeHandler().GetSignBytes(ctx, f.SignMode(), txSignerData, *txData) } func ValidateMemo(memo string) error { @@ -444,7 +470,7 @@ func ValidateMemo(memo string) error { } // WithAccountRetriever returns a copy of the Factory with an updated AccountRetriever. -func (f Factory) WithAccountRetriever(ar authtypes.AccountRetriever) Factory { +func (f Factory) WithAccountRetriever(ar AccountRetriever) Factory { f.accountRetriever = ar return f } @@ -594,20 +620,20 @@ func (f Factory) PreprocessTx(keyname string, builder TxBuilder) error { return f.txParams.preprocessTxHook(f.txParams.chainID, keyType, builder) } -func (f Factory) AccountNumber() uint64 { return f.txParams.accountNumber } -func (f Factory) Sequence() uint64 { return f.txParams.sequence } -func (f Factory) Gas() uint64 { return f.txParams.gas } -func (f Factory) GasAdjustment() float64 { return f.txParams.gasAdjustment } -func (f Factory) Keybase() keyring.Keyring { return f.keybase } -func (f Factory) ChainID() string { return f.txParams.chainID } -func (f Factory) Memo() string { return f.txParams.memo } -func (f Factory) Fees() []*base.Coin { return f.txParams.fees } -func (f Factory) GasPrices() []*base.DecCoin { return f.txParams.gasPrices } -func (f Factory) AccountRetriever() authtypes.AccountRetriever { return f.accountRetriever } -func (f Factory) TimeoutHeight() uint64 { return f.txParams.timeoutHeight } -func (f Factory) FromName() string { return f.txParams.fromName } -func (f Factory) SimulateAndExecute() bool { return f.txParams.simulateAndExecute } -func (f Factory) SignMode() apitxsigning.SignMode { return f.txParams.signMode } +func (f Factory) AccountNumber() uint64 { return f.txParams.accountNumber } +func (f Factory) Sequence() uint64 { return f.txParams.sequence } +func (f Factory) Gas() uint64 { return f.txParams.gas } +func (f Factory) GasAdjustment() float64 { return f.txParams.gasAdjustment } +func (f Factory) Keybase() keyring.Keyring { return f.keybase } +func (f Factory) ChainID() string { return f.txParams.chainID } +func (f Factory) Memo() string { return f.txParams.memo } +func (f Factory) Fees() []*base.Coin { return f.txParams.fees } +func (f Factory) GasPrices() []*base.DecCoin { return f.txParams.gasPrices } +func (f Factory) AccountRetriever() AccountRetriever { return f.accountRetriever } +func (f Factory) TimeoutHeight() uint64 { return f.txParams.timeoutHeight } +func (f Factory) FromName() string { return f.txParams.fromName } +func (f Factory) SimulateAndExecute() bool { return f.txParams.simulateAndExecute } +func (f Factory) SignMode() apitxsigning.SignMode { return f.txParams.signMode } // getSimPK gets the public key to use for building a simulation tx. // Note, we should only check for keys in the keybase if we are in simulate and execute mode, diff --git a/client/v2/tx/tx.go b/client/v2/tx/tx.go index f671d6738731..0508591398ae 100644 --- a/client/v2/tx/tx.go +++ b/client/v2/tx/tx.go @@ -3,35 +3,56 @@ package tx import ( "bufio" "context" - apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" - apitx "cosmossdk.io/api/cosmos/tx/v1beta1" - tx2 "cosmossdk.io/client/v2/internal" "errors" "fmt" - "github.com/cosmos/cosmos-sdk/client/input" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "os" + gogogrpc "github.com/cosmos/gogoproto/grpc" + "github.com/spf13/pflag" + apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + apitx "cosmossdk.io/api/cosmos/tx/v1beta1" + "cosmossdk.io/core/transaction" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/input" + "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/tx" - "github.com/spf13/pflag" - "os" ) // GenerateOrBroadcastTxCLI will either generate and print an unsigned transaction // or sign it and broadcast it returning an error upon failure. -func GenerateOrBroadcastTxCLI(clientCtx tx2.Context, flagSet *pflag.FlagSet, msgs ...sdk.Msg) error { - txf, err := NewFactoryCLI(clientCtx, flagSet) +// TODO: remove the client.Context +func GenerateOrBroadcastTxCLI(ctx client.Context, flagSet *pflag.FlagSet, msgs ...transaction.Msg) error { + k, err := keyring.NewAutoCLIKeyring(ctx.Keyring) + if err != nil { + return err + } + // TODO: fulfill with flagSet + params := TxParameters{ + timeoutHeight: 0, + chainID: "", + memo: "", + signMode: 0, + AccountConfig: AccountConfig{}, + GasConfig: GasConfig{}, + FeeConfig: FeeConfig{}, + ExecutionOptions: ExecutionOptions{}, + ExtensionOptions: ExtensionOptions{}, + } + txf, err := NewFactory(k, ctx.AddressCodec, ctx, params) if err != nil { return err } - return GenerateOrBroadcastTxWithFactory(clientCtx, txf, msgs...) + return GenerateOrBroadcastTxWithFactory(ctx, txf, msgs...) } // GenerateOrBroadcastTxWithFactory will either generate and print an unsigned transaction // or sign it and broadcast it returning an error upon failure. -func GenerateOrBroadcastTxWithFactory(clientCtx tx2.Context, txf Factory, msgs ...sdk.Msg) error { +func GenerateOrBroadcastTxWithFactory(clientCtx client.Context, txf Factory, msgs ...transaction.Msg) error { // Validate all msgs before generating or broadcasting the tx. // We were calling ValidateBasic separately in each CLI handler before. // Right now, we're factorizing that call inside this function. @@ -54,11 +75,15 @@ func GenerateOrBroadcastTxWithFactory(clientCtx tx2.Context, txf Factory, msgs . return err } - return clientCtx.PrintProto(&auxSignerData) + return clientCtx.PrintString(auxSignerData.String()) // TODO: check this } if clientCtx.GenerateOnly { - return txf.PrintUnsignedTx(clientCtx, msgs...) + uTx, err := txf.PrintUnsignedTx(msgs...) + if err != nil { + return err + } + return clientCtx.PrintString(uTx) } return BroadcastTx(clientCtx, txf, msgs...) @@ -67,8 +92,8 @@ func GenerateOrBroadcastTxWithFactory(clientCtx tx2.Context, txf Factory, msgs . // 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 tx2.Context, txf Factory, msgs ...sdk.Msg) error { - txf, err := txf.Prepare(clientCtx) +func BroadcastTx(clientCtx client.Context, txf Factory, msgs ...sdk.Msg) error { + txf, err := txf.Prepare() if err != nil { return err } @@ -91,7 +116,7 @@ func BroadcastTx(clientCtx tx2.Context, txf Factory, msgs ...sdk.Msg) error { return nil } - tx, err := txf.BuildUnsignedTx(msgs...) + builder, err := txf.BuildUnsignedTx(msgs...) if err != nil { return err } @@ -102,7 +127,11 @@ func BroadcastTx(clientCtx tx2.Context, txf Factory, msgs ...sdk.Msg) error { return errors.New("failed to encode transaction: tx json encoder is nil") } - txBytes, err := encoder(tx.GetTx()) + unsigTx, err := builder.GetTx() + if err != nil { + return err + } + txBytes, err := encoder(unsigTx) if err != nil { return fmt.Errorf("failed to encode transaction: %w", err) } @@ -123,11 +152,16 @@ func BroadcastTx(clientCtx tx2.Context, txf Factory, msgs ...sdk.Msg) error { } } - if err = txf.Sign(clientCtx.CmdContext, clientCtx.FromName, tx, true); err != nil { + if err = txf.Sign(clientCtx.CmdContext, clientCtx.FromName, builder, true); err != nil { + return err + } + + signedTx, err := builder.GetTx() + if err != nil { return err } - txBytes, err := clientCtx.TxConfig.TxEncoder()(tx.GetTx()) + txBytes, err := txf.txConfig.TxEncoder()(signedTx) if err != nil { return err } @@ -163,9 +197,9 @@ func CalculateGas( } // makeAuxSignerData generates an AuxSignerData from the client inputs. -func makeAuxSignerData(clientCtx tx2.Context, f Factory, msgs ...sdk.Msg) (*apitx.AuxSignerData, error) { +func makeAuxSignerData(clientCtx client.Context, f Factory, msgs ...sdk.Msg) (*apitx.AuxSignerData, error) { b := NewAuxTxBuilder() - fromAddress, name, _, err := tx2.GetFromFields(clientCtx, clientCtx.Keyring, clientCtx.From) + fromAddress, name, _, err := client.GetFromFields(clientCtx, clientCtx.Keyring, clientCtx.From) if err != nil { return nil, err } @@ -175,7 +209,7 @@ func makeAuxSignerData(clientCtx tx2.Context, f Factory, msgs ...sdk.Msg) (*apit b.SetAccountNumber(f.AccountNumber()) b.SetSequence(f.Sequence()) } else { - accNum, seq, err := clientCtx.AccountRetriever.GetAccountNumberSequence(clientCtx, fromAddress) + accNum, seq, err := f.accountRetriever.GetAccountNumberSequence(fromAddress) if err != nil { return nil, err } @@ -193,7 +227,7 @@ func makeAuxSignerData(clientCtx tx2.Context, f Factory, msgs ...sdk.Msg) (*apit return nil, err } - pubKey, err := clientCtx.Keyring.GetPubKey(name) + pubKey, err := f.keybase.GetPubKey(name) if err != nil { return nil, err } @@ -209,7 +243,7 @@ func makeAuxSignerData(clientCtx tx2.Context, f Factory, msgs ...sdk.Msg) (*apit return nil, err } - sig, err := clientCtx.Keyring.Sign(name, signBz, f.SignMode()) + sig, err := f.keybase.Sign(name, signBz, f.SignMode()) if err != nil { return nil, err } diff --git a/client/v2/tx/types.go b/client/v2/tx/types.go index 01b87a631c02..0448d420fe20 100644 --- a/client/v2/tx/types.go +++ b/client/v2/tx/types.go @@ -3,9 +3,11 @@ package tx import ( "fmt" + apicrypto "cosmossdk.io/api/cosmos/crypto/multisig/v1beta1" apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" - typestx "cosmossdk.io/api/cosmos/tx/v1beta1" + apitx "cosmossdk.io/api/cosmos/tx/v1beta1" "cosmossdk.io/core/transaction" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" ) @@ -13,10 +15,10 @@ import ( type PreprocessTxFn func(chainID string, key uint, tx TxBuilder) error // TxApiDecoder unmarshals transaction bytes into API Tx type -type TxApiDecoder func(txBytes []byte) (typestx.Tx, error) +type TxApiDecoder func(txBytes []byte) (apitx.Tx, error) // TxApiEncoder marshals transaction to bytes -type TxApiEncoder func(tx typestx.Tx) ([]byte, error) +type TxApiEncoder func(tx *apitx.Tx) ([]byte, error) // GasEstimateResponse defines a response definition for tx gas estimation. type GasEstimateResponse struct { @@ -28,7 +30,7 @@ func (gr GasEstimateResponse) String() string { } type TxWrapper struct { - Tx *typestx.Tx + Tx *apitx.Tx } func (tx TxWrapper) GetMsgs() ([]transaction.Msg, error) { @@ -71,7 +73,7 @@ type SingleSignatureData struct { type MultiSignatureData struct { // BitArray is a compact way of indicating which signers from the multisig key // have signed - BitArray *cryptotypes.CompactBitArray + BitArray *apicrypto.CompactBitArray // Signatures is the nested SignatureData's for each signer Signatures []SignatureData From ffe66ac72750559cf6eb265ee07f0ed3460e933a Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Thu, 6 Jun 2024 10:59:21 +0200 Subject: [PATCH 03/36] add: txBuilder tests --- client/v2/tx/aux_builder.go | 6 +- client/v2/tx/aux_builder_test.go | 18 +- client/v2/tx/builder.go | 21 +- client/v2/tx/builder_test.go | 652 +++++++++++++++++++++++++++++++ 4 files changed, 681 insertions(+), 16 deletions(-) create mode 100644 client/v2/tx/builder_test.go diff --git a/client/v2/tx/aux_builder.go b/client/v2/tx/aux_builder.go index 2d6b6b9a3e33..5a587e400549 100644 --- a/client/v2/tx/aux_builder.go +++ b/client/v2/tx/aux_builder.go @@ -178,7 +178,7 @@ func validateSignDoc(sd *apitx.SignDocDirectAux) error { return errors.New("body bytes is empty") } if sd.PublicKey == nil { - return errors.New("public key is empty") + return errors.New("public key cannot be empty: invalid pubkey") } return nil } @@ -186,7 +186,7 @@ func validateSignDoc(sd *apitx.SignDocDirectAux) error { // TODO: better place func validateAuxSignerData(a *apitx.AuxSignerData) error { if a.Address == "" { - return errors.New("address is empty") + return errors.New("address cannot be empty: invalid request") } if a.Mode != apisigning.SignMode_SIGN_MODE_DIRECT_AUX && a.Mode != apisigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON { @@ -194,7 +194,7 @@ func validateAuxSignerData(a *apitx.AuxSignerData) error { } if len(a.Sig) == 0 { - return errors.New("signature is empty") + return errors.New("signature cannot be empty: no signatures supplied") } return validateSignDoc(a.GetSignDoc()) diff --git a/client/v2/tx/aux_builder_test.go b/client/v2/tx/aux_builder_test.go index 7d561306fc69..203c02b61f7d 100644 --- a/client/v2/tx/aux_builder_test.go +++ b/client/v2/tx/aux_builder_test.go @@ -1,6 +1,8 @@ package tx_test import ( + "cosmossdk.io/core/transaction" + "google.golang.org/protobuf/types/known/anypb" "testing" "github.com/stretchr/testify/require" @@ -16,10 +18,8 @@ import ( "github.com/cosmos/cosmos-sdk/testutil/testdata" "github.com/cosmos/cosmos-sdk/testutil/x/counter" countertypes "github.com/cosmos/cosmos-sdk/testutil/x/counter/types" - sdk "github.com/cosmos/cosmos-sdk/types" moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" typestx "github.com/cosmos/cosmos-sdk/types/tx" - "github.com/cosmos/cosmos-sdk/types/tx/signing" ) const ( @@ -69,7 +69,7 @@ func TestAuxTxBuilder(t *testing.T) { { "cannot set invalid Msg", func() error { - return b.SetMsgs(sdk.Msg(nil)) + return b.SetMsgs(transaction.Msg(nil)) }, true, "failed packing protobuf message to Any", }, @@ -166,7 +166,7 @@ func TestAuxTxBuilder(t *testing.T) { auxSignerData, err := b.GetAuxSignerData() // Make sure auxSignerData is correctly populated - checkCorrectData(t, cdc, auxSignerData, signing.SignMode_SIGN_MODE_DIRECT_AUX) + checkCorrectData(t, cdc, auxSignerData, apisigning.SignMode_SIGN_MODE_DIRECT_AUX) return err }, @@ -207,7 +207,7 @@ func TestAuxTxBuilder(t *testing.T) { auxSignerData, err := b.GetAuxSignerData() // Make sure auxSignerData is correctly populated - checkCorrectData(t, cdc, auxSignerData, signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON) + checkCorrectData(t, cdc, auxSignerData, apisigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON) return err }, @@ -232,10 +232,14 @@ func TestAuxTxBuilder(t *testing.T) { } // checkCorrectData that the auxSignerData's content matches the inputs we gave. -func checkCorrectData(t *testing.T, cdc codec.Codec, auxSignerData *apitx.AuxSignerData, signMode signing.SignMode) { +func checkCorrectData(t *testing.T, cdc codec.Codec, auxSignerData *apitx.AuxSignerData, signMode apisigning.SignMode) { t.Helper() - pkAny, err := codectypes.NewAnyWithValue(pub1) + pk, err := codectypes.NewAnyWithValue(pub1) require.NoError(t, err) + pkAny := &anypb.Any{ + TypeUrl: pk.TypeUrl, + Value: pk.Value, + } msgAny, err := codectypes.NewAnyWithValue(msg1) require.NoError(t, err) diff --git a/client/v2/tx/builder.go b/client/v2/tx/builder.go index 8b0d4cb1ad88..ea14f6627af7 100644 --- a/client/v2/tx/builder.go +++ b/client/v2/tx/builder.go @@ -47,7 +47,7 @@ type TxBuilder interface { SetFeePayer(string) error SetGasLimit(uint64) SetTimeoutHeight(uint64) - SetFeeGranter(string) + SetFeeGranter(string) error SetUnordered(bool) SetSignatures(...Signature) error SetAuxSignerData(*apitx.AuxSignerData) error @@ -71,6 +71,7 @@ func NewBuilderProvider(addressCodec address.Codec, decoder *txdecode.Decoder, c codec: codec, } } + func (b BuilderProvider) NewTxBuilder() TxBuilder { return newTxBuilder(b.addressCodec, b.decoder, b.codec) } @@ -104,7 +105,7 @@ type txBuilder struct { nonCriticalExtensionOptions []*anypb.Any } -func newTxBuilder(addressCodec address.Codec, decoder *txdecode.Decoder, codec codec.BinaryCodec) TxBuilder { +func newTxBuilder(addressCodec address.Codec, decoder *txdecode.Decoder, codec codec.BinaryCodec) *txBuilder { return &txBuilder{ addressCodec: addressCodec, decoder: decoder, @@ -118,7 +119,6 @@ func (b *txBuilder) GetTx() (*apitx.Tx, error) { return nil, err } - // TODO: maybe just another func? body := &apitx.TxBody{ Messages: msgs, Memo: b.memo, @@ -141,7 +141,7 @@ func (b *txBuilder) GetTx() (*apitx.Tx, error) { return &apitx.Tx{ Body: body, AuthInfo: authInfo, - Signatures: b.signatures, // TODO: I don't like this + Signatures: b.signatures, }, nil } @@ -185,6 +185,9 @@ func (b *txBuilder) getFee() (fee *apitx.Fee, err error) { return nil, err } } + + // TODO: what if not fee payer nor granted are empty? + fee = &apitx.Fee{ Amount: b.fees, GasLimit: b.gasLimit, @@ -263,8 +266,14 @@ func (b *txBuilder) SetTimeoutHeight(timeoutHeight uint64) { b.timeoutHeight = timeoutHeight } -func (b *txBuilder) SetFeeGranter(feeGranter string) { - b.granter = []byte(feeGranter) +func (b *txBuilder) SetFeeGranter(feeGranter string) error { + addr, err := b.addressCodec.StringToBytes(feeGranter) + if err != nil { + return err + } + b.granter = addr + + return nil } func (b *txBuilder) SetUnordered(unordered bool) { diff --git a/client/v2/tx/builder_test.go b/client/v2/tx/builder_test.go new file mode 100644 index 000000000000..baa19b88cc26 --- /dev/null +++ b/client/v2/tx/builder_test.go @@ -0,0 +1,652 @@ +package tx + +import ( + "google.golang.org/protobuf/types/known/anypb" + "reflect" + "testing" + + base "cosmossdk.io/api/cosmos/base/v1beta1" + apisigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + apitx "cosmossdk.io/api/cosmos/tx/v1beta1" + "cosmossdk.io/core/address" + "cosmossdk.io/core/transaction" + txdecode "cosmossdk.io/x/tx/decode" + "cosmossdk.io/x/tx/signing" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/codec" + addrcodec "github.com/cosmos/cosmos-sdk/codec/address" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + countertypes "github.com/cosmos/cosmos-sdk/testutil/x/counter/types" +) + +var ( + key = secp256k1.GenPrivKey() + cdc = codec.NewProtoCodec(codectypes.NewInterfaceRegistry()) + ac = addrcodec.NewBech32Codec("cosmos") + signingOptions = signing.Options{ + AddressCodec: ac, + ValidatorAddressCodec: addrcodec.NewBech32Codec("cosmosval"), + } + signingContext, _ = signing.NewContext(signingOptions) + decodeOptions = txdecode.Options{SigningContext: signingContext} + decoder, _ = txdecode.NewDecoder(decodeOptions) + + counterMsg = &countertypes.MsgIncreaseCounter{ + Signer: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + Count: 0, + } +) + +func TestNewBuilderProvider(t *testing.T) { + type args struct { + addressCodec address.Codec + decoder *txdecode.Decoder + codec codec.BinaryCodec + } + tests := []struct { + name string + args args + }{ + { + name: "create new builder provider", + args: args{ + addressCodec: ac, + decoder: decoder, + codec: cdc, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := NewBuilderProvider(tt.args.addressCodec, tt.args.decoder, tt.args.codec) + require.NotNil(t, got) + }) + } +} + +func TestBuilderProvider_NewTxBuilder(t *testing.T) { + type fields struct { + addressCodec address.Codec + decoder *txdecode.Decoder + codec codec.BinaryCodec + } + tests := []struct { + name string + fields fields + }{ + { + name: "New txBuilder", + fields: fields{ + addressCodec: ac, + decoder: decoder, + codec: cdc, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := BuilderProvider{ + addressCodec: tt.fields.addressCodec, + decoder: tt.fields.decoder, + codec: tt.fields.codec, + } + got := b.NewTxBuilder() + require.NotNil(t, got) + }) + } +} + +func Test_newTxBuilder(t *testing.T) { + type args struct { + addressCodec address.Codec + decoder *txdecode.Decoder + codec codec.BinaryCodec + } + tests := []struct { + name string + args args + }{ + { + name: "new txBuilder", + args: args{ + addressCodec: ac, + decoder: decoder, + codec: cdc, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := newTxBuilder(tt.args.addressCodec, tt.args.decoder, tt.args.codec) + require.NotNil(t, got) + require.Equal(t, got.addressCodec, tt.args.addressCodec) + require.Equal(t, got.decoder, tt.args.decoder) + require.Equal(t, got.codec, tt.args.codec) + }) + } +} + +func Test_txBuilder_GetTx(t *testing.T) { + tests := []struct { + name string + txSetter func() *txBuilder + checkResult func(*apitx.Tx) bool + }{ + { + name: "empty tx", + txSetter: func() *txBuilder { + return newTxBuilder(ac, decoder, cdc) + }, + checkResult: func(tx *apitx.Tx) bool { + if !reflect.DeepEqual(tx.Body, &apitx.TxBody{ + Messages: []*anypb.Any{}, + Memo: "", + TimeoutHeight: 0, + Unordered: false, + ExtensionOptions: nil, + NonCriticalExtensionOptions: nil}) { + return false + } + if !reflect.DeepEqual(tx.AuthInfo, &apitx.AuthInfo{ + SignerInfos: nil, + Fee: &apitx.Fee{ + Amount: nil, + GasLimit: 0, + Payer: "", + Granter: ""}}) { + return false + } + if tx.Signatures != nil { + return false + } + return true + }, + }, + { + name: "full tx", + txSetter: func() *txBuilder { + pk := secp256k1.GenPrivKey().PubKey() + addr, _ := ac.BytesToString(pk.Address()) + b := newTxBuilder(ac, decoder, cdc) + + err := b.SetMsgs([]transaction.Msg{&countertypes.MsgIncreaseCounter{ + Signer: addr, + Count: 0, + }}...) + require.NoError(t, err) + + err = b.SetFeePayer(addr) + require.NoError(t, err) + + b.SetFeeAmount([]*base.Coin{{ + Denom: "cosmos", + Amount: "1000", + }}) + + err = b.SetSignatures([]Signature{{ + PubKey: pk, + Data: &SingleSignatureData{ + SignMode: apisigning.SignMode_SIGN_MODE_DIRECT, + Signature: nil, + }, + Sequence: 0, + }}...) + require.NoError(t, err) + return b + }, + checkResult: func(tx *apitx.Tx) bool { + if len(tx.Body.Messages) < 1 { + return false + } + if tx.AuthInfo.SignerInfos == nil || tx.AuthInfo.Fee.Amount == nil { + return false + } + if tx.Signatures == nil { + return false + } + return true + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := tt.txSetter() + got, err := b.GetTx() + require.NoError(t, err) + require.NotNil(t, got) + require.True(t, tt.checkResult(got)) + }) + } +} + +func Test_msgsV1toAnyV2(t *testing.T) { + tests := []struct { + name string + msgs []transaction.Msg + }{ + { + name: "convert msgV1 to V2", + msgs: []transaction.Msg{ + &countertypes.MsgIncreaseCounter{ + Signer: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + Count: 0, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := msgsV1toAnyV2(tt.msgs) + require.NoError(t, err) + require.NotNil(t, got) + }) + } +} + +func Test_intoAnyV2(t *testing.T) { + type args struct { + v1s []*codectypes.Any + } + tests := []struct { + name string + msgs []*codectypes.Any + }{ + { + name: "any to v2", + msgs: []*codectypes.Any{ + { + TypeUrl: "/random/msg", + Value: []byte("random message"), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := intoAnyV2(tt.msgs) + require.NotNil(t, got) + require.Equal(t, len(got), len(tt.msgs)) + for i, msg := range got { + require.Equal(t, msg.TypeUrl, tt.msgs[i].TypeUrl) + require.Equal(t, msg.Value, tt.msgs[i].Value) + } + }) + } +} + +func Test_txBuilder_getFee(t *testing.T) { + tests := []struct { + name string + feeAmount []*base.Coin + feeGranter string + feePayer string + }{ + { + name: "get fee with payer", + feeAmount: []*base.Coin{ + { + Denom: "cosmos", + Amount: "1000", + }, + }, + feeGranter: "", + feePayer: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + }, + { + name: "get fee with granter", + feeAmount: []*base.Coin{ + { + Denom: "cosmos", + Amount: "1000", + }, + }, + feeGranter: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + feePayer: "", + }, + { + name: "get fee with granter and granter", + feeAmount: []*base.Coin{ + { + Denom: "cosmos", + Amount: "1000", + }, + }, + feeGranter: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + feePayer: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := newTxBuilder(ac, decoder, cdc) + b.SetFeeAmount(tt.feeAmount) + b.SetFeeGranter(tt.feeGranter) + b.SetFeePayer(tt.feePayer) + + fee, err := b.getFee() + require.NoError(t, err) + require.NotNil(t, fee) + + require.Equal(t, fee.Amount, tt.feeAmount) + require.Equal(t, fee.Granter, tt.feeGranter) + require.Equal(t, fee.Payer, tt.feePayer) + }) + } +} + +func Test_txBuilder_GetSigningTxData(t *testing.T) { + tests := []struct { + name string + txSetter func() *txBuilder + }{ + { + name: "empty tx", + txSetter: func() *txBuilder { + return newTxBuilder(ac, decoder, cdc) + }, + }, + { + name: "full tx", + txSetter: func() *txBuilder { + pk := secp256k1.GenPrivKey().PubKey() + addr, _ := ac.BytesToString(pk.Address()) + b := newTxBuilder(ac, decoder, cdc) + + err := b.SetMsgs([]transaction.Msg{&countertypes.MsgIncreaseCounter{ + Signer: addr, + Count: 0, + }}...) + require.NoError(t, err) + + err = b.SetFeePayer(addr) + require.NoError(t, err) + + b.SetFeeAmount([]*base.Coin{{ + Denom: "cosmos", + Amount: "1000", + }}) + + err = b.SetSignatures([]Signature{{ + PubKey: pk, + Data: &SingleSignatureData{ + SignMode: apisigning.SignMode_SIGN_MODE_DIRECT, + Signature: nil, + }, + Sequence: 0, + }}...) + require.NoError(t, err) + + return b + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := tt.txSetter() + got, err := b.GetSigningTxData() + require.NoError(t, err) + require.NotNil(t, got) + }) + } +} + +func Test_txBuilder_SetMsgs(t *testing.T) { + tests := []struct { + name string + msgs []transaction.Msg + wantErr bool + }{ + { + name: "set msgs", + msgs: []transaction.Msg{ + &countertypes.MsgIncreaseCounter{ + Signer: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + Count: 0, + }, + &countertypes.MsgIncreaseCounter{ + Signer: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + Count: 1, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := newTxBuilder(ac, decoder, cdc) + err := b.SetMsgs(tt.msgs...) + require.NoError(t, err) + require.Equal(t, len(tt.msgs), len(tt.msgs)) + + for i, msg := range tt.msgs { + require.Equal(t, msg, tt.msgs[i]) + } + }) + } +} + +func Test_txBuilder_SetMemo(t *testing.T) { + tests := []struct { + name string + memo string + }{ + { + name: "set memo", + memo: "test", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := newTxBuilder(ac, decoder, cdc) + b.SetMemo(tt.memo) + require.Equal(t, b.memo, tt.memo) + }) + } +} + +func Test_txBuilder_SetFeeAmount(t *testing.T) { + tests := []struct { + name string + coins []*base.Coin + }{ + { + name: "set coins", + coins: []*base.Coin{ + { + Denom: "cosmos", + Amount: "1000", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := newTxBuilder(ac, decoder, cdc) + b.SetFeeAmount(tt.coins) + require.Equal(t, len(tt.coins), len(tt.coins)) + + for i, coin := range tt.coins { + require.Equal(t, coin.Amount, tt.coins[i].Amount) + } + }) + } +} + +func Test_txBuilder_SetGasLimit(t *testing.T) { + tests := []struct { + name string + gasLimit uint64 + }{ + { + name: "set gas limit", + gasLimit: 1, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := newTxBuilder(ac, decoder, cdc) + b.SetGasLimit(tt.gasLimit) + require.Equal(t, b.gasLimit, tt.gasLimit) + }) + } +} + +func Test_txBuilder_SetUnordered(t *testing.T) { + tests := []struct { + name string + unordered bool + }{ + { + name: "unordered", + unordered: true, + }, + { + name: "not unordered", + unordered: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := newTxBuilder(ac, decoder, cdc) + b.SetUnordered(tt.unordered) + require.Equal(t, b.unordered, tt.unordered) + }) + } +} + +func Test_txBuilder_SetSignatures(t *testing.T) { + tests := []struct { + name string + signatures func() []Signature + }{ + { + name: "set empty single signature", + signatures: func() []Signature { + return []Signature{{ + PubKey: secp256k1.GenPrivKey().PubKey(), + Data: &SingleSignatureData{ + SignMode: apisigning.SignMode_SIGN_MODE_DIRECT, + Signature: nil, + }, + Sequence: 0, + }} + }, + }, + { + name: "set empty single signature", + signatures: func() []Signature { + return []Signature{{ + PubKey: secp256k1.GenPrivKey().PubKey(), + Data: &SingleSignatureData{ + SignMode: apisigning.SignMode_SIGN_MODE_DIRECT, + Signature: []byte("signature"), + }, + Sequence: 0, + }} + }, + }, + { + name: "set empty multi signature", + signatures: func() []Signature { + return []Signature{{ + PubKey: multisig.NewLegacyAminoPubKey(1, []cryptotypes.PubKey{secp256k1.GenPrivKey().PubKey()}), + Data: &MultiSignatureData{ + BitArray: nil, + Signatures: []SignatureData{ + &SingleSignatureData{ + SignMode: apisigning.SignMode_SIGN_MODE_DIRECT, + Signature: nil, + }, + }, + }, + Sequence: 0, + }} + }, + }, + { + name: "set multi signature", + signatures: func() []Signature { + return []Signature{{ + PubKey: multisig.NewLegacyAminoPubKey(1, []cryptotypes.PubKey{secp256k1.GenPrivKey().PubKey()}), + Data: &MultiSignatureData{ + BitArray: nil, + Signatures: []SignatureData{ + &SingleSignatureData{ + SignMode: apisigning.SignMode_SIGN_MODE_DIRECT, + Signature: []byte("signature"), + }, + }, + }, + Sequence: 0, + }} + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := newTxBuilder(ac, decoder, cdc) + sigs := tt.signatures() + err := b.SetSignatures(sigs...) + require.NoError(t, err) + }) + } +} + +func TestSignatureDataToModeInfoAndSig(t *testing.T) { + tests := []struct { + name string + data SignatureData + mIResult *apitx.ModeInfo + sigResult []byte + }{ + { + name: "single signature", + data: &SingleSignatureData{ + SignMode: apisigning.SignMode_SIGN_MODE_DIRECT, + Signature: []byte("signature"), + }, + mIResult: &apitx.ModeInfo{ + Sum: &apitx.ModeInfo_Single_{ + Single: &apitx.ModeInfo_Single{Mode: apisigning.SignMode_SIGN_MODE_DIRECT}, + }, + }, + sigResult: []byte("signature"), + }, + { + name: "multi signature", + data: &MultiSignatureData{ + BitArray: nil, + Signatures: []SignatureData{ + &SingleSignatureData{ + SignMode: apisigning.SignMode_SIGN_MODE_DIRECT, + Signature: []byte("signature"), + }, + }, + }, + mIResult: &apitx.ModeInfo{ + Sum: &apitx.ModeInfo_Multi_{ + Multi: &apitx.ModeInfo_Multi{ + Bitarray: nil, + ModeInfos: []*apitx.ModeInfo{ + { + Sum: &apitx.ModeInfo_Single_{ + Single: &apitx.ModeInfo_Single{Mode: apisigning.SignMode_SIGN_MODE_DIRECT}, + }, + }, + }, + }, + }, + }, + sigResult: []byte("\n\tsignature"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + modeInfo, signature := SignatureDataToModeInfoAndSig(tt.data) + require.Equal(t, tt.mIResult, modeInfo) + require.Equal(t, tt.sigResult, signature) + }) + } +} From 3cbcdcb3b7dbfd1fb4ce6a72a93c9d4c2c95a23c Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Fri, 7 Jun 2024 12:25:56 +0200 Subject: [PATCH 04/36] txconfig --- client/v2/tx/builder.go | 5 - client/v2/tx/builder_test.go | 9 +- client/v2/tx/config.go | 228 ++++++++++++++++++++++++++++------- client/v2/tx/config_test.go | 135 +++++++++++++++++++++ client/v2/tx/encoder.go | 42 +++++++ client/v2/tx/encoder_test.go | 103 ++++++++++++++++ client/v2/tx/signature.go | 46 +++++++ client/v2/tx/types.go | 94 ++++++++------- 8 files changed, 563 insertions(+), 99 deletions(-) create mode 100644 client/v2/tx/config_test.go create mode 100644 client/v2/tx/encoder.go create mode 100644 client/v2/tx/encoder_test.go create mode 100644 client/v2/tx/signature.go diff --git a/client/v2/tx/builder.go b/client/v2/tx/builder.go index ea14f6627af7..40c4a9749131 100644 --- a/client/v2/tx/builder.go +++ b/client/v2/tx/builder.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" - protov2 "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/anypb" base "cosmossdk.io/api/cosmos/base/v1beta1" @@ -29,10 +28,6 @@ type ExtendedTxBuilder interface { SetExtensionOptions(...*gogoany.Any) // TODO: sdk.Any? } -var marshalOption = protov2.MarshalOptions{ - Deterministic: true, -} - // TxBuilder defines an interface which an application-defined concrete transaction // type must implement. Namely, it must be able to set messages, generate // signatures, and provide canonical bytes to sign over. The transaction must diff --git a/client/v2/tx/builder_test.go b/client/v2/tx/builder_test.go index baa19b88cc26..c1eed38002a7 100644 --- a/client/v2/tx/builder_test.go +++ b/client/v2/tx/builder_test.go @@ -1,10 +1,11 @@ package tx import ( - "google.golang.org/protobuf/types/known/anypb" "reflect" "testing" + "google.golang.org/protobuf/types/known/anypb" + base "cosmossdk.io/api/cosmos/base/v1beta1" apisigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" apitx "cosmossdk.io/api/cosmos/tx/v1beta1" @@ -24,7 +25,6 @@ import ( ) var ( - key = secp256k1.GenPrivKey() cdc = codec.NewProtoCodec(codectypes.NewInterfaceRegistry()) ac = addrcodec.NewBech32Codec("cosmos") signingOptions = signing.Options{ @@ -34,11 +34,6 @@ var ( signingContext, _ = signing.NewContext(signingOptions) decodeOptions = txdecode.Options{SigningContext: signingContext} decoder, _ = txdecode.NewDecoder(decodeOptions) - - counterMsg = &countertypes.MsgIncreaseCounter{ - Signer: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", - Count: 0, - } ) func TestNewBuilderProvider(t *testing.T) { diff --git a/client/v2/tx/config.go b/client/v2/tx/config.go index 4193d56b5619..a07b48669d16 100644 --- a/client/v2/tx/config.go +++ b/client/v2/tx/config.go @@ -1,12 +1,30 @@ package tx import ( - base "cosmossdk.io/api/cosmos/base/v1beta1" apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + "cosmossdk.io/core/address" + txdecode "cosmossdk.io/x/tx/decode" "cosmossdk.io/x/tx/signing" + "cosmossdk.io/x/tx/signing/aminojson" + "cosmossdk.io/x/tx/signing/direct" + "cosmossdk.io/x/tx/signing/directaux" + "cosmossdk.io/x/tx/signing/textual" + "errors" + "google.golang.org/protobuf/reflect/protoreflect" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/codec" +) + +var ( + _ TxConfig = txConfig{} + _ TxEncodingConfig = defaultEncodingConfig{} + _ TxSigningConfig = defaultTxSigningConfig{} + + defaultEnabledSignModes = []apitxsigning.SignMode{ + apitxsigning.SignMode_SIGN_MODE_DIRECT, + apitxsigning.SignMode_SIGN_MODE_DIRECT_AUX, + apitxsigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, + } ) // TxConfig defines an interface a client can utilize to generate an @@ -21,10 +39,10 @@ type TxConfig interface { // TxEncodingConfig defines an interface that contains transaction // encoders and decoders type TxEncodingConfig interface { - TxEncoder() TxApiEncoder - TxDecoder() TxApiDecoder - TxJSONEncoder() TxApiEncoder - TxJSONDecoder() TxApiDecoder + TxEncoder() txApiEncoder + TxDecoder() txApiDecoder + TxJSONEncoder() txApiEncoder + TxJSONDecoder() txApiDecoder } type TxSigningConfig interface { @@ -34,50 +52,176 @@ type TxSigningConfig interface { UnmarshalSignatureJSON([]byte) ([]Signature, error) } -type TxParameters struct { - timeoutHeight uint64 - chainID string - memo string - signMode apitxsigning.SignMode +type ConfigOptions struct { + AddressCodec address.Codec + Decoder *txdecode.Decoder + Cdc codec.BinaryCodec + + ValidatorAddressCodec address.Codec + FileResolver signing.ProtoFileResolver + TypeResolver signing.TypeResolver + CustomGetSigner map[protoreflect.FullName]signing.GetSignersFunc + MaxRecursionDepth int + + EnablesSignModes []apitxsigning.SignMode + CustomSignModes []signing.SignModeHandler + TextualCoinMetadataQueryFn textual.CoinMetadataQueryFn +} + +func (c *ConfigOptions) validate() error { + if c.AddressCodec == nil { + return errors.New("address codec cannot be nil") + } + if c.Decoder == nil { + return errors.New("decoder cannot be nil") + } + if c.Cdc == nil { + return errors.New("codec cannot be nil") + } + if c.ValidatorAddressCodec == nil { + return errors.New("validator address codec cannot be nil") + } + + // set default signModes + if len(c.EnablesSignModes) == 0 { + c.EnablesSignModes = defaultEnabledSignModes + } + return nil +} + +type txConfig struct { + TxBuilderProvider + TxEncodingConfig + TxSigningConfig +} - AccountConfig - GasConfig - FeeConfig - ExecutionOptions - ExtensionOptions +func NewTxConfig(options ConfigOptions) (TxConfig, error) { + err := options.validate() + if err != nil { + return nil, err + } + + signingCtx, err := newDefaultTxSigningConfig(options) + if err != nil { + return nil, err + } + + return &txConfig{ + TxBuilderProvider: NewBuilderProvider(options.AddressCodec, options.Decoder, options.Cdc), + TxEncodingConfig: defaultEncodingConfig{}, + TxSigningConfig: signingCtx, + }, nil } -// AccountConfig defines the 'account' related fields in a transaction. -type AccountConfig struct { - accountNumber uint64 - sequence uint64 - fromName string - fromAddress sdk.AccAddress +type defaultEncodingConfig struct{} + +func (t defaultEncodingConfig) TxEncoder() txApiEncoder { + return txEncoder } -// GasConfig defines the 'gas' related fields in a transaction. -type GasConfig struct { - gas uint64 - gasAdjustment float64 - gasPrices []*base.DecCoin +func (t defaultEncodingConfig) TxDecoder() txApiDecoder { + return txDecoder } -// FeeConfig defines the 'fee' related fields in a transaction. -type FeeConfig struct { - fees []*base.Coin - feeGranter sdk.AccAddress - feePayer sdk.AccAddress +func (t defaultEncodingConfig) TxJSONEncoder() txApiEncoder { + return txJsonEncoder } -// ExecutionOptions defines the transaction execution options ran by the client -type ExecutionOptions struct { - unordered bool - offline bool - generateOnly bool - simulateAndExecute bool - preprocessTxHook PreprocessTxFn +func (t defaultEncodingConfig) TxJSONDecoder() txApiDecoder { + return txJsonDecoder } -type ExtensionOptions struct { - ExtOptions []*codectypes.Any +type defaultTxSigningConfig struct { + signingCtx *signing.Context + handlerMap *signing.HandlerMap +} + +func newDefaultTxSigningConfig(opts ConfigOptions) (*defaultTxSigningConfig, error) { + signingCtx, err := newSigningContext(opts) + if err != nil { + return nil, err + } + + handlerMap, err := newHandlerMap(opts, signingCtx) + if err != nil { + return nil, err + } + + return &defaultTxSigningConfig{ + signingCtx: signingCtx, + handlerMap: handlerMap, + }, nil +} + +func (t defaultTxSigningConfig) SignModeHandler() *signing.HandlerMap { + return t.handlerMap +} + +func (t defaultTxSigningConfig) SigningContext() *signing.Context { + return t.signingCtx +} + +func (t defaultTxSigningConfig) MarshalSignatureJSON(signatures []Signature) ([]byte, error) { + //TODO implement me + panic("implement me") +} + +func (t defaultTxSigningConfig) UnmarshalSignatureJSON(bytes []byte) ([]Signature, error) { + //TODO implement me + panic("implement me") +} + +func newSigningContext(opts ConfigOptions) (*signing.Context, error) { + return signing.NewContext(signing.Options{ + FileResolver: opts.FileResolver, + TypeResolver: opts.TypeResolver, + AddressCodec: opts.AddressCodec, + ValidatorAddressCodec: opts.ValidatorAddressCodec, + CustomGetSigners: opts.CustomGetSigner, + MaxRecursionDepth: opts.MaxRecursionDepth, + }) +} + +func newHandlerMap(opts ConfigOptions, signingCtx *signing.Context) (*signing.HandlerMap, error) { + lenSignModes := len(opts.EnablesSignModes) + handlers := make([]signing.SignModeHandler, lenSignModes+len(opts.CustomSignModes)) + + for i, m := range opts.EnablesSignModes { + var err error + switch m { + case apitxsigning.SignMode_SIGN_MODE_DIRECT: + handlers[i] = &direct.SignModeHandler{} + case apitxsigning.SignMode_SIGN_MODE_TEXTUAL: + if opts.TextualCoinMetadataQueryFn == nil { + return nil, errors.New("cannot enable SIGN_MODE_TEXTUAL without a TextualCoinMetadataQueryFn") + } + handlers[i], err = textual.NewSignModeHandler(textual.SignModeOptions{ + CoinMetadataQuerier: opts.TextualCoinMetadataQueryFn, + FileResolver: signingCtx.FileResolver(), + TypeResolver: signingCtx.TypeResolver(), + }) + if err != nil { + return nil, err + } + case apitxsigning.SignMode_SIGN_MODE_DIRECT_AUX: + handlers[i], err = directaux.NewSignModeHandler(directaux.SignModeHandlerOptions{ + TypeResolver: signingCtx.TypeResolver(), + SignersContext: signingCtx, + }) + if err != nil { + return nil, err + } + case apitxsigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON: + handlers[i] = aminojson.NewSignModeHandler(aminojson.SignModeHandlerOptions{ + FileResolver: signingCtx.FileResolver(), + TypeResolver: opts.TypeResolver, + }) + } + } + for i, m := range opts.CustomSignModes { + handlers[i+lenSignModes] = m + } + + handler := signing.NewHandlerMap(handlers...) + return handler, nil } diff --git a/client/v2/tx/config_test.go b/client/v2/tx/config_test.go new file mode 100644 index 000000000000..fce5e5fd5905 --- /dev/null +++ b/client/v2/tx/config_test.go @@ -0,0 +1,135 @@ +package tx + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + "cosmossdk.io/x/tx/signing" + + "github.com/cosmos/cosmos-sdk/codec/address" +) + +type testModeHandler struct{} + +func (t testModeHandler) Mode() apitxsigning.SignMode { + return apitxsigning.SignMode_SIGN_MODE_DIRECT +} + +func (t testModeHandler) GetSignBytes(_ context.Context, _ signing.SignerData, _ signing.TxData) ([]byte, error) { + return []byte{}, nil +} + +func TestConfigOptions_validate(t *testing.T) { + tests := []struct { + name string + opts ConfigOptions + wantErr bool + }{ + { + name: "valid options", + opts: ConfigOptions{ + AddressCodec: address.NewBech32Codec("cosmos"), + Decoder: decoder, + Cdc: cdc, + ValidatorAddressCodec: address.NewBech32Codec("cosmosvaloper"), + }, + }, + { + name: "missing address codec", + opts: ConfigOptions{ + Decoder: decoder, + Cdc: cdc, + ValidatorAddressCodec: address.NewBech32Codec("cosmosvaloper"), + }, + wantErr: true, + }, + { + name: "missing decoder", + opts: ConfigOptions{ + AddressCodec: address.NewBech32Codec("cosmos"), + Cdc: cdc, + ValidatorAddressCodec: address.NewBech32Codec("cosmosvaloper"), + }, + wantErr: true, + }, + { + name: "missing codec", + opts: ConfigOptions{ + AddressCodec: address.NewBech32Codec("cosmos"), + Decoder: decoder, + ValidatorAddressCodec: address.NewBech32Codec("cosmosvaloper"), + }, + wantErr: true, + }, + { + name: "missing validator address codec", + opts: ConfigOptions{ + AddressCodec: address.NewBech32Codec("cosmos"), + Decoder: decoder, + Cdc: cdc, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.opts.validate(); (err != nil) != tt.wantErr { + t.Errorf("validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_newHandlerMap(t *testing.T) { + tests := []struct { + name string + opts ConfigOptions + }{ + { + name: "handler map with default sign modes", + opts: ConfigOptions{ + AddressCodec: address.NewBech32Codec("cosmos"), + Decoder: decoder, + Cdc: cdc, + ValidatorAddressCodec: address.NewBech32Codec("cosmosvaloper"), + }, + }, + { + name: "handler map with just one sign mode", + opts: ConfigOptions{ + AddressCodec: address.NewBech32Codec("cosmos"), + Decoder: decoder, + Cdc: cdc, + ValidatorAddressCodec: address.NewBech32Codec("cosmosvaloper"), + EnablesSignModes: []apitxsigning.SignMode{apitxsigning.SignMode_SIGN_MODE_DIRECT}, + }, + }, + { + name: "handler map with custom sign modes", + opts: ConfigOptions{ + AddressCodec: address.NewBech32Codec("cosmos"), + Decoder: decoder, + Cdc: cdc, + ValidatorAddressCodec: address.NewBech32Codec("cosmosvaloper"), + CustomSignModes: []signing.SignModeHandler{testModeHandler{}}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.opts.validate() + require.NoError(t, err) + + signingCtx, err := newSigningContext(tt.opts) + require.NoError(t, err) + + handlerMap, err := newHandlerMap(tt.opts, signingCtx) + require.NoError(t, err) + require.NotNil(t, handlerMap) + require.Equal(t, len(handlerMap.SupportedModes()), len(tt.opts.EnablesSignModes)+len(tt.opts.CustomSignModes)) + }) + } +} diff --git a/client/v2/tx/encoder.go b/client/v2/tx/encoder.go new file mode 100644 index 000000000000..1d9c63583b84 --- /dev/null +++ b/client/v2/tx/encoder.go @@ -0,0 +1,42 @@ +package tx + +import ( + apitx "cosmossdk.io/api/cosmos/tx/v1beta1" + "google.golang.org/protobuf/encoding/protojson" + protov2 "google.golang.org/protobuf/proto" +) + +var ( + marshalOption = protov2.MarshalOptions{Deterministic: true} + jsonMarshalOptions = protojson.MarshalOptions{ + Indent: "", + UseProtoNames: true, + UseEnumNumbers: false, + } +) + +// txApiDecoder unmarshals transaction bytes into API Tx type +type txApiDecoder func(txBytes []byte) (*apitx.Tx, error) + +// txApiEncoder marshals transaction to bytes +type txApiEncoder func(tx *apitx.Tx) ([]byte, error) + +func txDecoder(txBytes []byte) (*apitx.Tx, error) { + var tx apitx.Tx + //err := protov2.Unmarshal(txBytes, &tx) + //return &tx, err + return &tx, protov2.Unmarshal(txBytes, &tx) +} + +func txEncoder(tx *apitx.Tx) ([]byte, error) { + return marshalOption.Marshal(tx) +} + +func txJsonDecoder(txBytes []byte) (*apitx.Tx, error) { + var tx apitx.Tx + return &tx, protojson.Unmarshal(txBytes, &tx) +} + +func txJsonEncoder(tx *apitx.Tx) ([]byte, error) { + return jsonMarshalOptions.Marshal(tx) +} diff --git a/client/v2/tx/encoder_test.go b/client/v2/tx/encoder_test.go new file mode 100644 index 000000000000..3912420f0063 --- /dev/null +++ b/client/v2/tx/encoder_test.go @@ -0,0 +1,103 @@ +package tx + +import ( + apitx "cosmossdk.io/api/cosmos/tx/v1beta1" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/anypb" + "testing" +) + +func Test_txEncoder_txDecoder(t *testing.T) { + tests := []struct { + name string + tx *apitx.Tx + }{ + { + name: "decode tx", + tx: &apitx.Tx{ + Body: &apitx.TxBody{ + Messages: []*anypb.Any{{ + TypeUrl: "/test/decode", + Value: []byte("foo"), + }}, + Memo: "memo", + TimeoutHeight: 1, + Unordered: false, + ExtensionOptions: nil, + NonCriticalExtensionOptions: nil, + }, + AuthInfo: &apitx.AuthInfo{ + SignerInfos: []*apitx.SignerInfo{ + { + PublicKey: &anypb.Any{ + TypeUrl: "customKey", + Value: []byte("key"), + }, + Sequence: 1, + }, + }, + Fee: nil, + }, + Signatures: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + encodedTx, err := txEncoder(tt.tx) + require.NoError(t, err) + require.NotNil(t, encodedTx) + + isDeterministic, err := txEncoder(tt.tx) + require.NoError(t, err) + require.NotNil(t, encodedTx) + require.Equal(t, encodedTx, isDeterministic) + + decodedTx, err := txDecoder(encodedTx) + require.NoError(t, err) + require.NotNil(t, decodedTx) + }) + } +} + +func Test_txJsonEncoder_txJsonDecoder(t *testing.T) { + tests := []struct { + name string + tx *apitx.Tx + }{ + { + name: "json decode tx", + tx: &apitx.Tx{ + Body: &apitx.TxBody{ + Messages: []*anypb.Any{}, + Memo: "memo", + TimeoutHeight: 1, + Unordered: false, + ExtensionOptions: nil, + NonCriticalExtensionOptions: nil, + }, + AuthInfo: &apitx.AuthInfo{ + SignerInfos: []*apitx.SignerInfo{ + { + PublicKey: &anypb.Any{}, + Sequence: 1, + }, + }, + Fee: nil, + }, + Signatures: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + encodedTx, err := txJsonEncoder(tt.tx) + require.NoError(t, err) + require.NotNil(t, encodedTx) + + decodedTx, err := txJsonDecoder(encodedTx) + require.NoError(t, err) + require.NotNil(t, decodedTx) + }) + } +} diff --git a/client/v2/tx/signature.go b/client/v2/tx/signature.go new file mode 100644 index 000000000000..ae1a2d4ff454 --- /dev/null +++ b/client/v2/tx/signature.go @@ -0,0 +1,46 @@ +package tx + +import ( + apicrypto "cosmossdk.io/api/cosmos/crypto/multisig/v1beta1" + apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" +) + +type Signature struct { + // PubKey is the public key to use for verifying the signature + PubKey cryptotypes.PubKey + + // Data is the actual data of the signature which includes SignMode's and + // the signatures themselves for either single or multi-signatures. + Data SignatureData + + // Sequence is the sequence of this account. Only populated in + // SIGN_MODE_DIRECT. + Sequence uint64 +} + +type SignatureData interface { + isSignatureData() +} + +// SingleSignatureData represents the signature and SignMode of a single (non-multisig) signer +type SingleSignatureData struct { + // SignMode represents the SignMode of the signature + SignMode apitxsigning.SignMode + + // Signature is the raw signature. + Signature []byte +} + +// MultiSignatureData represents the nested SignatureData of a multisig signature +type MultiSignatureData struct { + // BitArray is a compact way of indicating which signers from the multisig key + // have signed + BitArray *apicrypto.CompactBitArray + + // Signatures is the nested SignatureData's for each signer + Signatures []SignatureData +} + +func (m *SingleSignatureData) isSignatureData() {} +func (m *MultiSignatureData) isSignatureData() {} diff --git a/client/v2/tx/types.go b/client/v2/tx/types.go index 0448d420fe20..249b7ed76b7f 100644 --- a/client/v2/tx/types.go +++ b/client/v2/tx/types.go @@ -3,22 +3,65 @@ package tx import ( "fmt" - apicrypto "cosmossdk.io/api/cosmos/crypto/multisig/v1beta1" + base "cosmossdk.io/api/cosmos/base/v1beta1" apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" apitx "cosmossdk.io/api/cosmos/tx/v1beta1" "cosmossdk.io/core/transaction" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" ) // PreprocessTxFn defines a hook by which chains can preprocess transactions before broadcasting type PreprocessTxFn func(chainID string, key uint, tx TxBuilder) error -// TxApiDecoder unmarshals transaction bytes into API Tx type -type TxApiDecoder func(txBytes []byte) (apitx.Tx, error) +type TxParameters struct { + timeoutHeight uint64 + chainID string + memo string + signMode apitxsigning.SignMode + + AccountConfig + GasConfig + FeeConfig + ExecutionOptions + ExtensionOptions +} + +// AccountConfig defines the 'account' related fields in a transaction. +type AccountConfig struct { + accountNumber uint64 + sequence uint64 + fromName string + fromAddress sdk.AccAddress +} + +// GasConfig defines the 'gas' related fields in a transaction. +type GasConfig struct { + gas uint64 + gasAdjustment float64 + gasPrices []*base.DecCoin +} + +// FeeConfig defines the 'fee' related fields in a transaction. +type FeeConfig struct { + fees []*base.Coin + feeGranter sdk.AccAddress + feePayer sdk.AccAddress +} + +// ExecutionOptions defines the transaction execution options ran by the client +type ExecutionOptions struct { + unordered bool + offline bool + generateOnly bool + simulateAndExecute bool + preprocessTxHook PreprocessTxFn +} -// TxApiEncoder marshals transaction to bytes -type TxApiEncoder func(tx *apitx.Tx) ([]byte, error) +type ExtensionOptions struct { + ExtOptions []*codectypes.Any +} // GasEstimateResponse defines a response definition for tx gas estimation. type GasEstimateResponse struct { @@ -42,42 +85,3 @@ func (tx TxWrapper) GetSignatures() ([]Signature, error) { //TODO implement me panic("implement me") } - -type Signature struct { - // PubKey is the public key to use for verifying the signature - PubKey cryptotypes.PubKey - - // Data is the actual data of the signature which includes SignMode's and - // the signatures themselves for either single or multi-signatures. - Data SignatureData - - // Sequence is the sequence of this account. Only populated in - // SIGN_MODE_DIRECT. - Sequence uint64 -} - -type SignatureData interface { - isSignatureData() -} - -// SingleSignatureData represents the signature and SignMode of a single (non-multisig) signer -type SingleSignatureData struct { - // SignMode represents the SignMode of the signature - SignMode apitxsigning.SignMode - - // Signature is the raw signature. - Signature []byte -} - -// MultiSignatureData represents the nested SignatureData of a multisig signature -type MultiSignatureData struct { - // BitArray is a compact way of indicating which signers from the multisig key - // have signed - BitArray *apicrypto.CompactBitArray - - // Signatures is the nested SignatureData's for each signer - Signatures []SignatureData -} - -func (m *SingleSignatureData) isSignatureData() {} -func (m *MultiSignatureData) isSignatureData() {} From 2ff6eddb45ac9c0eee78c9fa3f59461981eb55bb Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Tue, 11 Jun 2024 12:58:06 +0200 Subject: [PATCH 05/36] factory works --- client/v2/offchain/sign.go | 2 +- client/v2/tx/account.go | 94 +++++++++++++++++++++++-- client/v2/tx/aux_builder.go | 8 +-- client/v2/tx/builder.go | 122 +++++++++------------------------ client/v2/tx/builder_test.go | 57 --------------- client/v2/tx/config.go | 10 ++- client/v2/tx/encoder.go | 5 +- client/v2/tx/encoder_test.go | 4 +- client/v2/tx/factory.go | 82 +++++++++++++--------- client/v2/tx/factory_test.go | 39 +++++++++++ client/v2/tx/signature.go | 98 ++++++++++++++++++++++++++ client/v2/tx/signature_test.go | 95 +++++++++++++++++++++++++ client/v2/tx/tx.go | 114 +++++++++++++++++++++++++----- client/v2/tx/types.go | 119 +++++++++++++++++++++++++++++--- crypto/keyring/autocli.go | 19 ++++- simapp/simd/cmd/root.go | 2 +- simapp/simd/cmd/root_di.go | 2 +- 17 files changed, 644 insertions(+), 228 deletions(-) create mode 100644 client/v2/tx/factory_test.go create mode 100644 client/v2/tx/signature_test.go diff --git a/client/v2/offchain/sign.go b/client/v2/offchain/sign.go index fd01227ed13e..b36a50c00ca0 100644 --- a/client/v2/offchain/sign.go +++ b/client/v2/offchain/sign.go @@ -63,7 +63,7 @@ func Sign(ctx client.Context, rawBytes []byte, fromName, indent, encoding, outpu // sign signs a digest with provided key and SignMode. func sign(ctx client.Context, fromName, digest string) (*apitx.Tx, error) { - keybase, err := keyring.NewAutoCLIKeyring(ctx.Keyring) + keybase, err := keyring.NewAutoCLIKeyring(ctx.Keyring, ctx.AddressCodec) if err != nil { return nil, err } diff --git a/client/v2/tx/account.go b/client/v2/tx/account.go index 857813e1e81d..aa6d814ebdc2 100644 --- a/client/v2/tx/account.go +++ b/client/v2/tx/account.go @@ -1,10 +1,29 @@ package tx import ( + "context" + "fmt" + "google.golang.org/grpc" + "strconv" + + gogogrpc "github.com/cosmos/gogoproto/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + + "cosmossdk.io/core/address" + authtypes "cosmossdk.io/x/auth/types" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" + grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" ) +// TODO: move to internal + +var _ AccountRetriever = accountRetriever{} + // Account defines a read-only version of the auth module's AccountI. type Account interface { GetAddress() sdk.AccAddress @@ -17,8 +36,75 @@ type Account interface { // ensure an account exists and to be able to query for account fields necessary // for signing. type AccountRetriever interface { - GetAccount(addr sdk.AccAddress) (Account, error) - GetAccountWithHeight(addr sdk.AccAddress) (Account, int64, error) - EnsureExists(addr sdk.AccAddress) error - GetAccountNumberSequence(addr sdk.AccAddress) (accNum, accSeq uint64, err error) + GetAccount(context.Context, sdk.AccAddress) (Account, error) + GetAccountWithHeight(context.Context, sdk.AccAddress) (Account, int64, error) + EnsureExists(context.Context, sdk.AccAddress) error + GetAccountNumberSequence(context.Context, sdk.AccAddress) (accNum, accSeq uint64, err error) +} + +type accountRetriever struct { + ac address.Codec + conn gogogrpc.ClientConn + registry codectypes.InterfaceRegistry +} + +func newAccountRetriever(ac address.Codec, conn gogogrpc.ClientConn, registry codectypes.InterfaceRegistry) *accountRetriever { + return &accountRetriever{ + ac: ac, + conn: conn, + registry: registry, + } +} + +func (a accountRetriever) GetAccount(ctx context.Context, addr sdk.AccAddress) (Account, error) { + acc, _, err := a.GetAccountWithHeight(ctx, addr) + return acc, err +} + +func (a accountRetriever) GetAccountWithHeight(ctx context.Context, addr sdk.AccAddress) (Account, int64, error) { + var header metadata.MD + + qc := authtypes.NewQueryClient(a.conn) + + res, err := qc.Account(ctx, &authtypes.QueryAccountRequest{Address: addr.String()}, grpc.Header(&header)) + if err != nil { + return nil, 0, err + } + + blockHeight := header.Get(grpctypes.GRPCBlockHeightHeader) + if l := len(blockHeight); l != 1 { + return nil, 0, fmt.Errorf("unexpected '%s' header length; got %d, expected: %d", grpctypes.GRPCBlockHeightHeader, l, 1) + } + + nBlockHeight, err := strconv.Atoi(blockHeight[0]) + if err != nil { + return nil, 0, fmt.Errorf("failed to parse block height: %w", err) + } + + var acc Account + if err := a.registry.UnpackAny(res.Account, &acc); err != nil { + return nil, 0, err + } + + return acc, int64(nBlockHeight), nil + +} + +func (a accountRetriever) EnsureExists(ctx context.Context, addr sdk.AccAddress) error { + if _, err := a.GetAccount(ctx, addr); err != nil { + return err + } + return nil +} + +func (a accountRetriever) GetAccountNumberSequence(ctx context.Context, addr sdk.AccAddress) (accNum, accSeq uint64, err error) { + acc, err := a.GetAccount(ctx, addr) + if err != nil { + if status.Code(err) == codes.NotFound { + return 0, 0, nil + } + return 0, 0, err + } + + return acc.GetAccountNumber(), acc.GetSequence(), nil } diff --git a/client/v2/tx/aux_builder.go b/client/v2/tx/aux_builder.go index 5a587e400549..dac6bfaba2d9 100644 --- a/client/v2/tx/aux_builder.go +++ b/client/v2/tx/aux_builder.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/cosmos/gogoproto/proto" + gogoany "github.com/cosmos/gogoproto/types/any" "google.golang.org/protobuf/types/known/anypb" apisigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" @@ -17,7 +18,6 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -62,7 +62,7 @@ func (b *AuxTxBuilder) SetTimeoutHeight(height uint64) { } // SetMsgs sets an array of Msgs in the tx. -func (b *AuxTxBuilder) SetMsgs(msgs ...sdk.Msg) error { +func (b *AuxTxBuilder) SetMsgs(msgs ...transaction.Msg) error { anys := make([]*anypb.Any, len(msgs)) for i, msg := range msgs { legacyAny, err := codectypes.NewAnyWithValue(msg) @@ -143,7 +143,7 @@ func (b *AuxTxBuilder) SetSignature(sig []byte) { } // SetExtensionOptions sets the aux signer's extension options. -func (b *AuxTxBuilder) SetExtensionOptions(extOpts ...*codectypes.Any) { +func (b *AuxTxBuilder) SetExtensionOptions(extOpts ...*gogoany.Any) { b.checkEmptyFields() anyExtOpts := make([]*anypb.Any, len(extOpts)) @@ -158,7 +158,7 @@ func (b *AuxTxBuilder) SetExtensionOptions(extOpts ...*codectypes.Any) { } // SetNonCriticalExtensionOptions sets the aux signer's non-critical extension options. -func (b *AuxTxBuilder) SetNonCriticalExtensionOptions(extOpts ...*codectypes.Any) { +func (b *AuxTxBuilder) SetNonCriticalExtensionOptions(extOpts ...*gogoany.Any) { b.checkEmptyFields() anyNonCritExtOpts := make([]*anypb.Any, len(extOpts)) diff --git a/client/v2/tx/builder.go b/client/v2/tx/builder.go index 40c4a9749131..7835179ee558 100644 --- a/client/v2/tx/builder.go +++ b/client/v2/tx/builder.go @@ -1,9 +1,7 @@ package tx import ( - "errors" - "fmt" - + gogoany "github.com/cosmos/gogoproto/types/any" "google.golang.org/protobuf/types/known/anypb" base "cosmossdk.io/api/cosmos/base/v1beta1" @@ -15,8 +13,6 @@ import ( "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - gogoany "github.com/cosmos/gogoproto/types/any" ) var ( @@ -39,13 +35,12 @@ type TxBuilder interface { SetMsgs(...transaction.Msg) error SetMemo(string) SetFeeAmount([]*base.Coin) - SetFeePayer(string) error SetGasLimit(uint64) SetTimeoutHeight(uint64) + SetFeePayer(string) error SetFeeGranter(string) error SetUnordered(bool) SetSignatures(...Signature) error - SetAuxSignerData(*apitx.AuxSignerData) error } type TxBuilderProvider interface { @@ -140,30 +135,6 @@ func (b *txBuilder) GetTx() (*apitx.Tx, error) { }, nil } -func msgsV1toAnyV2(msgs []transaction.Msg) ([]*anypb.Any, error) { - anys := make([]*codectypes.Any, len(msgs)) - for i, msg := range msgs { - anyMsg, err := codectypes.NewAnyWithValue(msg) - if err != nil { - return nil, err - } - anys[i] = anyMsg - } - - return intoAnyV2(anys), nil -} - -func intoAnyV2(v1s []*codectypes.Any) []*anypb.Any { - v2s := make([]*anypb.Any, len(v1s)) - for i, v1 := range v1s { - v2s[i] = &anypb.Any{ - TypeUrl: v1.TypeUrl, - Value: v1.Value, - } - } - return v2s -} - func (b *txBuilder) getFee() (fee *apitx.Fee, err error) { granterStr := "" if b.granter != nil { @@ -244,7 +215,19 @@ func (b *txBuilder) SetFeeAmount(coins []*base.Coin) { b.fees = coins } +func (b *txBuilder) SetGasLimit(gasLimit uint64) { + b.gasLimit = gasLimit +} + +func (b *txBuilder) SetTimeoutHeight(timeoutHeight uint64) { + b.timeoutHeight = timeoutHeight +} + func (b *txBuilder) SetFeePayer(feePayer string) error { + if feePayer == "" { + return nil + } + addr, err := b.addressCodec.StringToBytes(feePayer) if err != nil { return err @@ -253,15 +236,11 @@ func (b *txBuilder) SetFeePayer(feePayer string) error { return nil } -func (b *txBuilder) SetGasLimit(gasLimit uint64) { - b.gasLimit = gasLimit -} - -func (b *txBuilder) SetTimeoutHeight(timeoutHeight uint64) { - b.timeoutHeight = timeoutHeight -} - func (b *txBuilder) SetFeeGranter(feeGranter string) error { + if feeGranter == "" { + return nil + } + addr, err := b.addressCodec.StringToBytes(feeGranter) if err != nil { return err @@ -313,61 +292,26 @@ func (b *txBuilder) SetSignatures(signatures ...Signature) error { return nil } -// TODO: check this -func (b *txBuilder) SetAuxSignerData(data *apitx.AuxSignerData) error { - /* - if data == nil { - return errors.New("aux signer data cannot be nil") - } - any, err := codectypes.NewAnyWithValue(data) +func msgsV1toAnyV2(msgs []transaction.Msg) ([]*anypb.Any, error) { + anys := make([]*codectypes.Any, len(msgs)) + for i, msg := range msgs { + anyMsg, err := codectypes.NewAnyWithValue(msg) if err != nil { - return err + return nil, err } - b.extensionOptions = append(b.extensionOptions, any) - return nil - */ - return errors.New("not supported") -} - -// SignatureDataToModeInfoAndSig converts a SignatureData to a ModeInfo and raw bytes signature -func SignatureDataToModeInfoAndSig(data SignatureData) (*apitx.ModeInfo, []byte) { - if data == nil { - return nil, nil + anys[i] = anyMsg } - switch data := data.(type) { - case *SingleSignatureData: - return &apitx.ModeInfo{ - Sum: &apitx.ModeInfo_Single_{ - Single: &apitx.ModeInfo_Single{Mode: data.SignMode}, - }, - }, data.Signature - case *MultiSignatureData: - n := len(data.Signatures) - modeInfos := make([]*apitx.ModeInfo, n) - sigs := make([][]byte, n) - - for i, d := range data.Signatures { - modeInfos[i], sigs[i] = SignatureDataToModeInfoAndSig(d) - } + return intoAnyV2(anys), nil +} - multisig := cryptotypes.MultiSignature{ - Signatures: sigs, - } - sig, err := multisig.Marshal() - if err != nil { - panic(err) +func intoAnyV2(v1s []*codectypes.Any) []*anypb.Any { + v2s := make([]*anypb.Any, len(v1s)) + for i, v1 := range v1s { + v2s[i] = &anypb.Any{ + TypeUrl: v1.TypeUrl, + Value: v1.Value, } - - return &apitx.ModeInfo{ - Sum: &apitx.ModeInfo_Multi_{ - Multi: &apitx.ModeInfo_Multi{ - Bitarray: data.BitArray, - ModeInfos: modeInfos, - }, - }, - }, sig - default: - panic(fmt.Sprintf("unexpected signature data type %T", data)) } + return v2s } diff --git a/client/v2/tx/builder_test.go b/client/v2/tx/builder_test.go index c1eed38002a7..f5a71cd374c7 100644 --- a/client/v2/tx/builder_test.go +++ b/client/v2/tx/builder_test.go @@ -588,60 +588,3 @@ func Test_txBuilder_SetSignatures(t *testing.T) { }) } } - -func TestSignatureDataToModeInfoAndSig(t *testing.T) { - tests := []struct { - name string - data SignatureData - mIResult *apitx.ModeInfo - sigResult []byte - }{ - { - name: "single signature", - data: &SingleSignatureData{ - SignMode: apisigning.SignMode_SIGN_MODE_DIRECT, - Signature: []byte("signature"), - }, - mIResult: &apitx.ModeInfo{ - Sum: &apitx.ModeInfo_Single_{ - Single: &apitx.ModeInfo_Single{Mode: apisigning.SignMode_SIGN_MODE_DIRECT}, - }, - }, - sigResult: []byte("signature"), - }, - { - name: "multi signature", - data: &MultiSignatureData{ - BitArray: nil, - Signatures: []SignatureData{ - &SingleSignatureData{ - SignMode: apisigning.SignMode_SIGN_MODE_DIRECT, - Signature: []byte("signature"), - }, - }, - }, - mIResult: &apitx.ModeInfo{ - Sum: &apitx.ModeInfo_Multi_{ - Multi: &apitx.ModeInfo_Multi{ - Bitarray: nil, - ModeInfos: []*apitx.ModeInfo{ - { - Sum: &apitx.ModeInfo_Single_{ - Single: &apitx.ModeInfo_Single{Mode: apisigning.SignMode_SIGN_MODE_DIRECT}, - }, - }, - }, - }, - }, - }, - sigResult: []byte("\n\tsignature"), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - modeInfo, signature := SignatureDataToModeInfoAndSig(tt.data) - require.Equal(t, tt.mIResult, modeInfo) - require.Equal(t, tt.sigResult, signature) - }) - } -} diff --git a/client/v2/tx/config.go b/client/v2/tx/config.go index a07b48669d16..679a2b0c0b6d 100644 --- a/client/v2/tx/config.go +++ b/client/v2/tx/config.go @@ -72,9 +72,6 @@ func (c *ConfigOptions) validate() error { if c.AddressCodec == nil { return errors.New("address codec cannot be nil") } - if c.Decoder == nil { - return errors.New("decoder cannot be nil") - } if c.Cdc == nil { return errors.New("codec cannot be nil") } @@ -106,6 +103,13 @@ func NewTxConfig(options ConfigOptions) (TxConfig, error) { return nil, err } + if options.Decoder == nil { + options.Decoder, err = txdecode.NewDecoder(txdecode.Options{SigningContext: signingCtx.SigningContext()}) + if err != nil { + return nil, err + } + } + return &txConfig{ TxBuilderProvider: NewBuilderProvider(options.AddressCodec, options.Decoder, options.Cdc), TxEncodingConfig: defaultEncodingConfig{}, diff --git a/client/v2/tx/encoder.go b/client/v2/tx/encoder.go index 1d9c63583b84..dd8d591e593c 100644 --- a/client/v2/tx/encoder.go +++ b/client/v2/tx/encoder.go @@ -1,9 +1,10 @@ package tx import ( - apitx "cosmossdk.io/api/cosmos/tx/v1beta1" "google.golang.org/protobuf/encoding/protojson" protov2 "google.golang.org/protobuf/proto" + + apitx "cosmossdk.io/api/cosmos/tx/v1beta1" ) var ( @@ -23,8 +24,6 @@ type txApiEncoder func(tx *apitx.Tx) ([]byte, error) func txDecoder(txBytes []byte) (*apitx.Tx, error) { var tx apitx.Tx - //err := protov2.Unmarshal(txBytes, &tx) - //return &tx, err return &tx, protov2.Unmarshal(txBytes, &tx) } diff --git a/client/v2/tx/encoder_test.go b/client/v2/tx/encoder_test.go index 3912420f0063..748886c6cb61 100644 --- a/client/v2/tx/encoder_test.go +++ b/client/v2/tx/encoder_test.go @@ -13,7 +13,7 @@ func Test_txEncoder_txDecoder(t *testing.T) { tx *apitx.Tx }{ { - name: "decode tx", + name: "encode and tx", tx: &apitx.Tx{ Body: &apitx.TxBody{ Messages: []*anypb.Any{{ @@ -66,7 +66,7 @@ func Test_txJsonEncoder_txJsonDecoder(t *testing.T) { tx *apitx.Tx }{ { - name: "json decode tx", + name: "json encode and decode tx", tx: &apitx.Tx{ Body: &apitx.TxBody{ Messages: []*anypb.Any{}, diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index 3dadd95fce2c..e731053698fa 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -20,6 +20,7 @@ import ( "cosmossdk.io/math" "cosmossdk.io/x/tx/signing" + "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" @@ -41,6 +42,7 @@ type FactoryI interface { // signing an application-specific transaction. type Factory struct { keybase keyring.Keyring + cdc codec.BinaryCodec accountRetriever AccountRetriever ac address.Codec conn gogogrpc.ClientConn @@ -49,7 +51,7 @@ type Factory struct { } // NewFactory returns a factory -func NewFactory(keybase keyring.Keyring, ac address.Codec, conn gogogrpc.ClientConn, parameters TxParameters) (Factory, error) { +func NewFactory(keybase keyring.Keyring, cdc codec.BinaryCodec, accRetriever AccountRetriever, txConfig TxConfig, ac address.Codec, conn gogogrpc.ClientConn, parameters TxParameters) (Factory, error) { //if clientCtx.Viper == nil { // clientCtx = clientCtx.WithViper("") //} @@ -87,14 +89,14 @@ func NewFactory(keybase keyring.Keyring, ac address.Codec, conn gogogrpc.ClientC //gasPricesStr := clientCtx.Viper.GetString(flags.FlagGasPrices) // //feesStr := clientCtx.Viper.GetString(flags.FlagFees) - f := Factory{ - keybase: keybase, - //accountRetriever: accRetriever, // TODO: pass as argument or call constructor - ac: ac, - conn: conn, - //txConfig: txConfig, // TODO: pass as argument or call constructor - txParams: parameters, + keybase: keybase, + cdc: cdc, + accountRetriever: accRetriever, + ac: ac, + conn: conn, + txConfig: txConfig, + txParams: parameters, } // Properties that need special parsing @@ -108,7 +110,7 @@ func NewFactory(keybase keyring.Keyring, ac address.Codec, conn gogogrpc.ClientC // A new Factory with the updated fields will be returned. // Note: When in offline mode, the Prepare does nothing and returns the original factory. func (f Factory) Prepare() (Factory, error) { - if f.txParams.ExecutionOptions.offline { + if f.txParams.ExecutionOptions.offline || f.txParams.ExecutionOptions.offChain { return f, nil } @@ -116,13 +118,13 @@ func (f Factory) Prepare() (Factory, error) { return f, errors.New("missing 'from address' field") } - if err := f.accountRetriever.EnsureExists(f.txParams.fromAddress); err != nil { + if err := f.accountRetriever.EnsureExists(context.Background(), f.txParams.fromAddress); err != nil { return f, err } if f.txParams.accountNumber == 0 || f.txParams.sequence == 0 { fc := f - num, seq, err := fc.accountRetriever.GetAccountNumberSequence(f.txParams.fromAddress) + num, seq, err := fc.accountRetriever.GetAccountNumberSequence(context.Background(), f.txParams.fromAddress) if err != nil { return f, err } @@ -141,6 +143,7 @@ func (f Factory) Prepare() (Factory, error) { return f, nil } +// TODO: move to internal/coins? var ( _ withAmount = &base.Coin{} _ withAmount = &base.DecCoin{} @@ -207,7 +210,7 @@ func (f Factory) BuildUnsignedTx(msgs ...transaction.Msg) (TxBuilder, error) { } } - if err := ValidateMemo(f.txParams.memo); err != nil { + if err := validateMemo(f.txParams.memo); err != nil { return nil, err } @@ -219,8 +222,14 @@ func (f Factory) BuildUnsignedTx(msgs ...transaction.Msg) (TxBuilder, error) { txBuilder.SetMemo(f.txParams.memo) txBuilder.SetFeeAmount(fees) txBuilder.SetGasLimit(f.txParams.gas) - txBuilder.SetFeeGranter(f.txParams.feeGranter.String()) - txBuilder.SetFeePayer(f.txParams.feePayer.String()) + err = txBuilder.SetFeeGranter(f.txParams.feeGranter) + if err != nil { + return nil, err + } + err = txBuilder.SetFeePayer(f.txParams.feePayer) + if err != nil { + return nil, err + } txBuilder.SetTimeoutHeight(f.txParams.timeoutHeight) if etx, ok := txBuilder.(ExtendedTxBuilder); ok { @@ -266,7 +275,7 @@ func (f Factory) PrintUnsignedTx(msgs ...transaction.Msg) (string, error) { if encoder == nil { return "", errors.New("cannot print unsigned tx: tx json encoder is nil") } - + tx, err := builder.GetTx() if err != nil { return "", err @@ -329,9 +338,8 @@ func (f Factory) Sign(ctx context.Context, name string, txBuilder TxBuilder, ove } var err error - signMode := f.txParams.signMode - if signMode == apitxsigning.SignMode_SIGN_MODE_UNSPECIFIED { - signMode = f.txConfig.SignModeHandler().DefaultMode() + if f.txParams.signMode == apitxsigning.SignMode_SIGN_MODE_UNSPECIFIED { + f.txParams.signMode = f.txConfig.SignModeHandler().DefaultMode() } pubKey, err := f.keybase.GetPubKey(name) @@ -349,18 +357,12 @@ func (f Factory) Sign(ctx context.Context, name string, txBuilder TxBuilder, ove AccountNumber: f.txParams.accountNumber, Sequence: f.txParams.sequence, PubKey: &anypb.Any{ - TypeUrl: pubKey.Type(), // TODO: check correctness + TypeUrl: codectypes.MsgTypeURL(pubKey), Value: pubKey.Bytes(), }, Address: addr, } - tx, err := txBuilder.GetTx() - if err != nil { - return err - } - txWrap := TxWrapper{Tx: tx} - // For SIGN_MODE_DIRECT, calling SetSignatures calls setSignerInfos on // TxBuilder under the hood, and SignerInfos is needed to be generated the // sign bytes. This is the reason for setting SetSignatures here, with a @@ -370,7 +372,7 @@ func (f Factory) Sign(ctx context.Context, name string, txBuilder TxBuilder, ove // also doesn't affect its generated sign bytes, so for code's simplicity // sake, we put it here. sigData := SingleSignatureData{ - SignMode: signMode, + SignMode: f.txParams.signMode, Signature: nil, } sig := Signature{ @@ -381,6 +383,15 @@ func (f Factory) Sign(ctx context.Context, name string, txBuilder TxBuilder, ove var prevSignatures []Signature if !overwriteSig { + tx, err := txBuilder.GetTx() + if err != nil { + return err + } + + txWrap := wrappedTx{ + tx: tx, + cdc: f.cdc, + } prevSignatures, err = txWrap.GetSignatures() if err != nil { return err @@ -398,6 +409,15 @@ func (f Factory) Sign(ctx context.Context, name string, txBuilder TxBuilder, ove return err } + tx, err := txBuilder.GetTx() + if err != nil { + return err + } + txWrap := wrappedTx{ + tx: tx, + cdc: f.cdc, + } + if err := checkMultipleSigners(txWrap); err != nil { return err } @@ -408,14 +428,14 @@ func (f Factory) Sign(ctx context.Context, name string, txBuilder TxBuilder, ove } // Sign those bytes - sigBytes, err := f.keybase.Sign(name, bytesToSign, signMode) + sigBytes, err := f.keybase.Sign(name, bytesToSign, f.txParams.signMode) if err != nil { return err } // Construct the SignatureV2 struct sigData = SingleSignatureData{ - SignMode: signMode, + SignMode: f.SignMode(), Signature: sigBytes, } sig = Signature{ @@ -460,7 +480,7 @@ func (f Factory) GetSignBytesAdapter(ctx context.Context, signerData signing.Sig return f.txConfig.SignModeHandler().GetSignBytes(ctx, f.SignMode(), txSignerData, *txData) } -func ValidateMemo(memo string) error { +func validateMemo(memo string) error { // Prevent simple inclusion of a valid mnemonic in the memo field if memo != "" && bip39.IsMnemonicValid(strings.ToLower(memo)) { return errors.New("cannot provide a valid mnemonic seed in the memo field") @@ -582,13 +602,13 @@ func (f Factory) WithTimeoutHeight(height uint64) Factory { } // WithFeeGranter returns a copy of the Factory with an updated fee granter. -func (f Factory) WithFeeGranter(fg sdk.AccAddress) Factory { +func (f Factory) WithFeeGranter(fg string) Factory { f.txParams.feeGranter = fg return f } // WithFeePayer returns a copy of the Factory with an updated fee granter. -func (f Factory) WithFeePayer(fp sdk.AccAddress) Factory { +func (f Factory) WithFeePayer(fp string) Factory { f.txParams.feePayer = fp return f } diff --git a/client/v2/tx/factory_test.go b/client/v2/tx/factory_test.go new file mode 100644 index 000000000000..e87a59a91458 --- /dev/null +++ b/client/v2/tx/factory_test.go @@ -0,0 +1,39 @@ +package tx + +import ( + "cosmossdk.io/client/v2/autocli/keyring" + "cosmossdk.io/core/address" + "github.com/cosmos/gogoproto/grpc" + "reflect" + "testing" +) + +func TestNewFactory(t *testing.T) { + type args struct { + keybase keyring.Keyring + txConfig TxConfig + ac address.Codec + conn grpc.ClientConn + parameters TxParameters + } + tests := []struct { + name string + args args + want Factory + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewFactory(tt.args.keybase, tt.args.txConfig, tt.args.ac, tt.args.conn, tt.args.parameters) + if (err != nil) != tt.wantErr { + t.Errorf("NewFactory() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewFactory() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/client/v2/tx/signature.go b/client/v2/tx/signature.go index ae1a2d4ff454..1c31d066421c 100644 --- a/client/v2/tx/signature.go +++ b/client/v2/tx/signature.go @@ -1,8 +1,13 @@ package tx import ( + "errors" + "fmt" + apicrypto "cosmossdk.io/api/cosmos/crypto/multisig/v1beta1" apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + apitx "cosmossdk.io/api/cosmos/tx/v1beta1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" ) @@ -44,3 +49,96 @@ type MultiSignatureData struct { func (m *SingleSignatureData) isSignatureData() {} func (m *MultiSignatureData) isSignatureData() {} + +// SignatureDataToModeInfoAndSig converts a SignatureData to a ModeInfo and raw bytes signature +func SignatureDataToModeInfoAndSig(data SignatureData) (*apitx.ModeInfo, []byte) { + if data == nil { + return nil, nil + } + + switch data := data.(type) { + case *SingleSignatureData: + return &apitx.ModeInfo{ + Sum: &apitx.ModeInfo_Single_{ + Single: &apitx.ModeInfo_Single{Mode: data.SignMode}, + }, + }, data.Signature + case *MultiSignatureData: + n := len(data.Signatures) + modeInfos := make([]*apitx.ModeInfo, n) + sigs := make([][]byte, n) + + for i, d := range data.Signatures { + modeInfos[i], sigs[i] = SignatureDataToModeInfoAndSig(d) + } + + multisig := cryptotypes.MultiSignature{ + Signatures: sigs, + } + sig, err := multisig.Marshal() + if err != nil { + panic(err) + } + + return &apitx.ModeInfo{ + Sum: &apitx.ModeInfo_Multi_{ + Multi: &apitx.ModeInfo_Multi{ + Bitarray: data.BitArray, + ModeInfos: modeInfos, + }, + }, + }, sig + default: + panic(fmt.Sprintf("unexpected signature data type %T", data)) + } +} + +func ModeInfoAndSigToSignatureData(modeInfo *apitx.ModeInfo, sig []byte) (SignatureData, error) { + switch mi := modeInfo.Sum.(type) { + case *apitx.ModeInfo_Single_: + return &SingleSignatureData{ + SignMode: mi.Single.Mode, + Signature: sig, + }, nil + + case *apitx.ModeInfo_Multi_: + multi := mi.Multi + + sigs, err := decodeMultiSignatures(sig) + if err != nil { + return nil, err + } + + sigsV2 := make([]SignatureData, len(sigs)) + for i, mi := range multi.ModeInfos { + sigsV2[i], err = ModeInfoAndSigToSignatureData(mi, sigs[i]) + if err != nil { + return nil, err + } + + return &MultiSignatureData{ + BitArray: &apicrypto.CompactBitArray{ + ExtraBitsStored: multi.Bitarray.GetExtraBitsStored(), + Elems: multi.Bitarray.GetElems(), + }, + Signatures: sigsV2, + }, nil + } + } + + return nil, fmt.Errorf("unsupported ModeInfo type %T", modeInfo) +} + +func decodeMultiSignatures(bz []byte) ([][]byte, error) { + multisig := cryptotypes.MultiSignature{} + + err := multisig.Unmarshal(bz) + if err != nil { + return nil, err + } + + if len(multisig.XXX_unrecognized) > 0 { + return nil, errors.New("rejecting unrecognized fields found in MultiSignature") + } + return multisig.Signatures, nil +} diff --git a/client/v2/tx/signature_test.go b/client/v2/tx/signature_test.go new file mode 100644 index 000000000000..bfc588f55e2b --- /dev/null +++ b/client/v2/tx/signature_test.go @@ -0,0 +1,95 @@ +package tx + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/require" + + apisigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + apitx "cosmossdk.io/api/cosmos/tx/v1beta1" +) + +func TestSignatureDataToModeInfoAndSig(t *testing.T) { + tests := []struct { + name string + data SignatureData + mIResult *apitx.ModeInfo + sigResult []byte + }{ + { + name: "single signature", + data: &SingleSignatureData{ + SignMode: apisigning.SignMode_SIGN_MODE_DIRECT, + Signature: []byte("signature"), + }, + mIResult: &apitx.ModeInfo{ + Sum: &apitx.ModeInfo_Single_{ + Single: &apitx.ModeInfo_Single{Mode: apisigning.SignMode_SIGN_MODE_DIRECT}, + }, + }, + sigResult: []byte("signature"), + }, + { + name: "multi signature", + data: &MultiSignatureData{ + BitArray: nil, + Signatures: []SignatureData{ + &SingleSignatureData{ + SignMode: apisigning.SignMode_SIGN_MODE_DIRECT, + Signature: []byte("signature"), + }, + }, + }, + mIResult: &apitx.ModeInfo{ + Sum: &apitx.ModeInfo_Multi_{ + Multi: &apitx.ModeInfo_Multi{ + Bitarray: nil, + ModeInfos: []*apitx.ModeInfo{ + { + Sum: &apitx.ModeInfo_Single_{ + Single: &apitx.ModeInfo_Single{Mode: apisigning.SignMode_SIGN_MODE_DIRECT}, + }, + }, + }, + }, + }, + }, + sigResult: []byte("\n\tsignature"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + modeInfo, signature := SignatureDataToModeInfoAndSig(tt.data) + require.Equal(t, tt.mIResult, modeInfo) + require.Equal(t, tt.sigResult, signature) + }) + } +} + +func TestModeInfoAndSigToSignatureData(t *testing.T) { + type args struct { + modeInfo *apitx.ModeInfo + sig []byte + } + tests := []struct { + name string + args args + want SignatureData + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ModeInfoAndSigToSignatureData(tt.args.modeInfo, tt.args.sig) + if (err != nil) != tt.wantErr { + t.Errorf("ModeInfoAndSigToSignatureData() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ModeInfoAndSigToSignatureData() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/client/v2/tx/tx.go b/client/v2/tx/tx.go index 0508591398ae..0cbf5577a766 100644 --- a/client/v2/tx/tx.go +++ b/client/v2/tx/tx.go @@ -12,9 +12,10 @@ import ( apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" apitx "cosmossdk.io/api/cosmos/tx/v1beta1" + keyring2 "cosmossdk.io/client/v2/autocli/keyring" "cosmossdk.io/core/transaction" - "github.com/cosmos/cosmos-sdk/client" + flags2 "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/input" "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" @@ -22,27 +23,88 @@ import ( "github.com/cosmos/cosmos-sdk/types/tx" ) +func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring) (params TxParameters, err error) { + timeout, _ := flags.GetUint64(flags2.FlagTimeoutHeight) + chainID, _ := flags.GetString(flags2.FlagChainID) + memo, _ := flags.GetString(flags2.FlagNote) + signMode, _ := flags.GetString(flags2.FlagSignMode) + accNumber, _ := flags.GetUint64(flags2.FlagAccountNumber) + sequence, _ := flags.GetUint64(flags2.FlagSequence) + fromName, _ := flags.GetString(flags2.FlagFrom) + gas, _ := flags.GetUint64(flags2.FlagGas) + gasAdjustment, _ := flags.GetFloat64(flags2.FlagGasAdjustment) + gasPrices, _ := flags.GetString(flags2.FlagGasPrices) + fmt.Println(gasPrices) + + feePayer, _ := flags.GetString(flags2.FlagFeePayer) + feeGrater, _ := flags.GetString(flags2.FlagFeeGranter) + + unordered, _ := flags.GetBool(flags2.FlagUnordered) + offline, _ := flags.GetBool(flags2.FlagOffline) + generateOnly, _ := flags.GetBool(flags2.FlagGenerateOnly) + + fees := flags2.FlagFees + fmt.Println(fees) + + acc, err := keybase.GetPubKey(fromName) + if err != nil { + return params, err + } + + return TxParameters{ + timeoutHeight: timeout, + chainID: chainID, + memo: memo, + signMode: getSignMode(signMode), + AccountConfig: AccountConfig{ + accountNumber: accNumber, + sequence: sequence, + fromName: fromName, + fromAddress: acc.Address().Bytes(), + }, + GasConfig: NewGasConfig(gas, gasAdjustment, nil), + FeeConfig: FeeConfig{ // TODO: needs special parsing + fees: nil, + feeGranter: feePayer, + feePayer: feeGrater, + }, + ExecutionOptions: ExecutionOptions{ + unordered: unordered, + offline: offline, + offChain: false, + generateOnly: generateOnly, + simulateAndExecute: false, // TODO: in context + preprocessTxHook: nil, // TODO: in context + }, + ExtensionOptions: ExtensionOptions{}, + }, nil +} + // GenerateOrBroadcastTxCLI will either generate and print an unsigned transaction // or sign it and broadcast it returning an error upon failure. // TODO: remove the client.Context func GenerateOrBroadcastTxCLI(ctx client.Context, flagSet *pflag.FlagSet, msgs ...transaction.Msg) error { - k, err := keyring.NewAutoCLIKeyring(ctx.Keyring) + k, err := keyring.NewAutoCLIKeyring(ctx.Keyring, ctx.AddressCodec) if err != nil { return err } - // TODO: fulfill with flagSet - params := TxParameters{ - timeoutHeight: 0, - chainID: "", - memo: "", - signMode: 0, - AccountConfig: AccountConfig{}, - GasConfig: GasConfig{}, - FeeConfig: FeeConfig{}, - ExecutionOptions: ExecutionOptions{}, - ExtensionOptions: ExtensionOptions{}, + + params, err := txParamsFromFlagSet(flagSet, k) + if err != nil { + return err + } + + txConfig, err := NewTxConfig(ConfigOptions{ + AddressCodec: ctx.AddressCodec, + Cdc: ctx.Codec, + ValidatorAddressCodec: ctx.ValidatorAddressCodec, + }) + if err != nil { + return err } - txf, err := NewFactory(k, ctx.AddressCodec, ctx, params) + + accRetriever := newAccountRetriever(ctx.AddressCodec, ctx, ctx.InterfaceRegistry) + txf, err := NewFactory(k, ctx.Codec, accRetriever, txConfig, ctx.AddressCodec, ctx, params) if err != nil { return err } @@ -92,7 +154,7 @@ func GenerateOrBroadcastTxWithFactory(clientCtx client.Context, txf Factory, msg // 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 { +func BroadcastTx(clientCtx client.Context, txf Factory, msgs ...transaction.Msg) error { txf, err := txf.Prepare() if err != nil { return err @@ -178,7 +240,7 @@ func BroadcastTx(clientCtx client.Context, txf Factory, msgs ...sdk.Msg) error { // 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, + clientCtx gogogrpc.ClientConn, txf Factory, msgs ...transaction.Msg, ) (*tx.SimulateResponse, uint64, error) { txBytes, err := txf.BuildSimTx(msgs...) if err != nil { @@ -197,7 +259,7 @@ func CalculateGas( } // makeAuxSignerData generates an AuxSignerData from the client inputs. -func makeAuxSignerData(clientCtx client.Context, f Factory, msgs ...sdk.Msg) (*apitx.AuxSignerData, error) { +func makeAuxSignerData(clientCtx client.Context, f Factory, msgs ...transaction.Msg) (*apitx.AuxSignerData, error) { b := NewAuxTxBuilder() fromAddress, name, _, err := client.GetFromFields(clientCtx, clientCtx.Keyring, clientCtx.From) if err != nil { @@ -209,7 +271,7 @@ func makeAuxSignerData(clientCtx client.Context, f Factory, msgs ...sdk.Msg) (*a b.SetAccountNumber(f.AccountNumber()) b.SetSequence(f.Sequence()) } else { - accNum, seq, err := f.accountRetriever.GetAccountNumberSequence(fromAddress) + accNum, seq, err := f.accountRetriever.GetAccountNumberSequence(context.Background(), fromAddress) if err != nil { return nil, err } @@ -254,7 +316,7 @@ func makeAuxSignerData(clientCtx client.Context, f Factory, msgs ...sdk.Msg) (*a // checkMultipleSigners checks that there can be maximum one DIRECT signer in // a tx. -func checkMultipleSigners(tx TxWrapper) error { +func checkMultipleSigners(tx Tx) error { directSigners := 0 sigsV2, err := tx.GetSignatures() if err != nil { @@ -290,3 +352,17 @@ func countDirectSigners(sigData SignatureData) int { panic("unreachable case") } } + +func getSignMode(mode string) apitxsigning.SignMode { + switch mode { + case "direct": + return apitxsigning.SignMode_SIGN_MODE_DIRECT + case "direct-aux": + return apitxsigning.SignMode_SIGN_MODE_DIRECT_AUX + case "amino-json": + return apitxsigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON + case "textual": + return apitxsigning.SignMode_SIGN_MODE_TEXTUAL + } + return apitxsigning.SignMode_SIGN_MODE_UNSPECIFIED +} diff --git a/client/v2/tx/types.go b/client/v2/tx/types.go index 249b7ed76b7f..7f435697fca7 100644 --- a/client/v2/tx/types.go +++ b/client/v2/tx/types.go @@ -1,14 +1,21 @@ package tx import ( + "cosmossdk.io/core/transaction" "fmt" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/gogoproto/proto" + "google.golang.org/protobuf/types/known/anypb" + "reflect" + "strings" + + gogoany "github.com/cosmos/gogoproto/types/any" base "cosmossdk.io/api/cosmos/base/v1beta1" apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" apitx "cosmossdk.io/api/cosmos/tx/v1beta1" - "cosmossdk.io/core/transaction" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -43,24 +50,36 @@ type GasConfig struct { gasPrices []*base.DecCoin } +func NewGasConfig(gas uint64, gasAdjustment float64, gasPrices []*base.DecCoin) GasConfig { + if gas == 0 { + gas = 200000 + } + return GasConfig{ + gas: gas, + gasAdjustment: gasAdjustment, + gasPrices: gasPrices, + } +} + // FeeConfig defines the 'fee' related fields in a transaction. type FeeConfig struct { fees []*base.Coin - feeGranter sdk.AccAddress - feePayer sdk.AccAddress + feeGranter string + feePayer string } // ExecutionOptions defines the transaction execution options ran by the client type ExecutionOptions struct { unordered bool offline bool + offChain bool generateOnly bool simulateAndExecute bool preprocessTxHook PreprocessTxFn } type ExtensionOptions struct { - ExtOptions []*codectypes.Any + ExtOptions []*gogoany.Any } // GasEstimateResponse defines a response definition for tx gas estimation. @@ -72,16 +91,94 @@ func (gr GasEstimateResponse) String() string { return fmt.Sprintf("gas estimate: %d", gr.GasEstimate) } -type TxWrapper struct { - Tx *apitx.Tx +type Tx interface { + GetMsgs() ([]transaction.Msg, error) + GetSigners() ([][]byte, error) + GetPubKeys() ([]cryptotypes.PubKey, error) + GetSignatures() ([]Signature, error) +} + +type wrappedTx struct { + tx *apitx.Tx + cdc codec.BinaryCodec } -func (tx TxWrapper) GetMsgs() ([]transaction.Msg, error) { - //TODO implement me +func (w wrappedTx) GetMsgs() ([]transaction.Msg, error) { panic("implement me") } -func (tx TxWrapper) GetSignatures() ([]Signature, error) { - //TODO implement me +func (w wrappedTx) GetSigners() ([][]byte, error) { panic("implement me") } + +func (w wrappedTx) GetPubKeys() ([]cryptotypes.PubKey, error) { + signerInfos := w.tx.AuthInfo.SignerInfos + pks := make([]cryptotypes.PubKey, len(signerInfos)) + + for i, si := range signerInfos { + // NOTE: it is okay to leave this nil if there is no PubKey in the SignerInfo. + // PubKey's can be left unset in SignerInfo. + if si.PublicKey == nil { + continue + } + maybePk, err := w.decodeAny(si.PublicKey) + if err != nil { + return nil, err + } + pk, ok := maybePk.(cryptotypes.PubKey) + if !ok { + return nil, fmt.Errorf("invalid public key type: %T", maybePk) + } + pks[i] = pk + } + + return pks, nil +} + +func (w wrappedTx) GetSignatures() ([]Signature, error) { + signerInfos := w.tx.AuthInfo.SignerInfos + sigs := w.tx.Signatures + + pubKeys, err := w.GetPubKeys() + if err != nil { + return nil, err + } + signatures := make([]Signature, len(sigs)) + + for i, si := range signerInfos { + if si.ModeInfo == nil || si.ModeInfo.Sum == nil { + signatures[i] = Signature{ + PubKey: pubKeys[i], + } + } else { + sigData, err := ModeInfoAndSigToSignatureData(si.ModeInfo, sigs[i]) + if err != nil { + return nil, err + } + signatures[i] = Signature{ + PubKey: pubKeys[i], + Data: sigData, + Sequence: si.GetSequence(), + } + } + } + + return signatures, nil +} + +func (w wrappedTx) decodeAny(anyPb *anypb.Any) (proto.Message, error) { + name := anyPb.GetTypeUrl() + if i := strings.LastIndexByte(name, '/'); i >= 0 { + name = name[i+len("/"):] + } + typ := proto.MessageType(name) + if typ == nil { + return nil, fmt.Errorf("unknown type: %s", name) + } + v1 := reflect.New(typ.Elem()).Interface().(proto.Message) + err := w.cdc.Unmarshal(anyPb.GetValue(), v1) + if err != nil { + return nil, err + } + return v1, nil +} diff --git a/crypto/keyring/autocli.go b/crypto/keyring/autocli.go index 23f56c012793..a9d78b378fd9 100644 --- a/crypto/keyring/autocli.go +++ b/crypto/keyring/autocli.go @@ -1,10 +1,14 @@ package keyring import ( + "cosmossdk.io/core/address" + "errors" + signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1" authsigning "cosmossdk.io/x/auth/signing" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) // autoCLIKeyring represents the keyring interface used by the AutoCLI. @@ -27,12 +31,13 @@ type autoCLIKeyring interface { } // NewAutoCLIKeyring wraps the SDK keyring and make it compatible with the AutoCLI keyring interfaces. -func NewAutoCLIKeyring(kr Keyring) (autoCLIKeyring, error) { - return &autoCLIKeyringAdapter{kr}, nil +func NewAutoCLIKeyring(kr Keyring, ac address.Codec) (autoCLIKeyring, error) { + return &autoCLIKeyringAdapter{kr, ac}, nil } type autoCLIKeyringAdapter struct { Keyring + ac address.Codec } func (a *autoCLIKeyringAdapter) List() ([]string, error) { @@ -67,6 +72,16 @@ func (a *autoCLIKeyringAdapter) LookupAddressByKeyName(name string) ([]byte, err func (a *autoCLIKeyringAdapter) GetPubKey(name string) (cryptotypes.PubKey, error) { record, err := a.Keyring.Key(name) if err != nil { + if errors.Is(err, sdkerrors.ErrKeyNotFound) { + addr, err := a.ac.StringToBytes(name) + if err != nil { + return nil, err + } + record, err = a.Keyring.KeyByAddress(addr) + if err == nil { + return record.GetPubKey() + } + } return nil, err } diff --git a/simapp/simd/cmd/root.go b/simapp/simd/cmd/root.go index 2b66d8cbe577..f7a1095bc81f 100644 --- a/simapp/simd/cmd/root.go +++ b/simapp/simd/cmd/root.go @@ -121,7 +121,7 @@ func NewRootCmd() *cobra.Command { } autoCliOpts := tempApp.AutoCliOpts() - autoCliOpts.Keyring, _ = keyring.NewAutoCLIKeyring(initClientCtx.Keyring) + autoCliOpts.Keyring, _ = keyring.NewAutoCLIKeyring(initClientCtx.Keyring, initClientCtx.AddressCodec) autoCliOpts.ClientCtx = initClientCtx if err := autoCliOpts.EnhanceRootCommand(rootCmd); err != nil { diff --git a/simapp/simd/cmd/root_di.go b/simapp/simd/cmd/root_di.go index adf8b6cef37c..d283723bd017 100644 --- a/simapp/simd/cmd/root_di.go +++ b/simapp/simd/cmd/root_di.go @@ -153,5 +153,5 @@ func ProvideKeyring(clientCtx client.Context, addressCodec address.Codec) (clien return nil, err } - return keyring.NewAutoCLIKeyring(kb) + return keyring.NewAutoCLIKeyring(kb, addressCodec) } From 5fec5afa3c819f3bba07b56ff88d48ad11c739d9 Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Wed, 12 Jun 2024 17:16:19 +0200 Subject: [PATCH 06/36] update: Decoder interface, remove factory withs, parsing gas and fees, --- client/v2/internal/coins/util.go | 31 +++++ client/v2/internal/coins/util_test.go | 83 +++++++++++ client/v2/internal/grpc/client.go | 22 +++ client/v2/tx/builder.go | 9 +- client/v2/tx/config.go | 2 +- client/v2/tx/encoder.go | 6 + client/v2/tx/factory.go | 192 +------------------------- client/v2/tx/tx.go | 141 ++++++++++++++----- client/v2/tx/types.go | 52 ++++++- 9 files changed, 305 insertions(+), 233 deletions(-) create mode 100644 client/v2/internal/coins/util.go create mode 100644 client/v2/internal/coins/util_test.go create mode 100644 client/v2/internal/grpc/client.go diff --git a/client/v2/internal/coins/util.go b/client/v2/internal/coins/util.go new file mode 100644 index 000000000000..afc511fe2ae7 --- /dev/null +++ b/client/v2/internal/coins/util.go @@ -0,0 +1,31 @@ +package coins + +import ( + "errors" + + base "cosmossdk.io/api/cosmos/base/v1beta1" + "cosmossdk.io/math" +) + +var ( + _ withAmount = &base.Coin{} + _ withAmount = &base.DecCoin{} +) + +type withAmount interface { + GetAmount() string +} + +// IsZero check if given coins are zero. +func IsZero[T withAmount](coins []T) (bool, error) { + for _, coin := range coins { + amount, ok := math.NewIntFromString(coin.GetAmount()) + if !ok { + return false, errors.New("invalid coin amount") + } + if !amount.IsZero() { + return false, nil + } + } + return true, nil +} diff --git a/client/v2/internal/coins/util_test.go b/client/v2/internal/coins/util_test.go new file mode 100644 index 000000000000..1ee7f5842920 --- /dev/null +++ b/client/v2/internal/coins/util_test.go @@ -0,0 +1,83 @@ +package coins + +import ( + "testing" + + "github.com/stretchr/testify/require" + + base "cosmossdk.io/api/cosmos/base/v1beta1" +) + +func TestCoinIsZero(t *testing.T) { + type testCase[T withAmount] struct { + name string + coins []T + isZero bool + } + tests := []testCase[*base.Coin]{ + { + name: "not zero coin", + coins: []*base.Coin{ + { + Denom: "stake", + Amount: "100", + }, + }, + isZero: false, + }, + { + name: "zero coin", + coins: []*base.Coin{ + { + Denom: "stake", + Amount: "0", + }, + }, + isZero: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := IsZero(tt.coins) + require.NoError(t, err) + require.Equal(t, got, tt.isZero) + }) + } +} + +func TestDecCoinIsZero(t *testing.T) { + type testCase[T withAmount] struct { + name string + coins []T + isZero bool + } + tests := []testCase[*base.DecCoin]{ + { + name: "not zero coin", + coins: []*base.DecCoin{ + { + Denom: "stake", + Amount: "100", + }, + }, + isZero: false, + }, + { + name: "zero coin", + coins: []*base.DecCoin{ + { + Denom: "stake", + Amount: "0", + }, + }, + isZero: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := IsZero(tt.coins) + require.NoError(t, err) + require.Equal(t, got, tt.isZero) + }) + } +} diff --git a/client/v2/internal/grpc/client.go b/client/v2/internal/grpc/client.go new file mode 100644 index 000000000000..db671e450b55 --- /dev/null +++ b/client/v2/internal/grpc/client.go @@ -0,0 +1,22 @@ +package grpc + +import ( + "context" + "fmt" + + gogogrpc "github.com/cosmos/gogoproto/grpc" + "google.golang.org/grpc" +) + +var _ gogogrpc.ClientConn = ClientConn{} + +type ClientConn struct{} + +func (c ClientConn) Invoke(ctx context.Context, method string, args, reply interface{}, opts ...grpc.CallOption) error { + //TODO implement me + panic("implement me") +} + +func (c ClientConn) NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) { + return nil, fmt.Errorf("streaming rpc not supported") +} diff --git a/client/v2/tx/builder.go b/client/v2/tx/builder.go index 7835179ee558..b4261a688785 100644 --- a/client/v2/tx/builder.go +++ b/client/v2/tx/builder.go @@ -8,7 +8,6 @@ import ( apitx "cosmossdk.io/api/cosmos/tx/v1beta1" "cosmossdk.io/core/address" "cosmossdk.io/core/transaction" - txdecode "cosmossdk.io/x/tx/decode" "cosmossdk.io/x/tx/signing" "github.com/cosmos/cosmos-sdk/codec" @@ -50,11 +49,11 @@ type TxBuilderProvider interface { type BuilderProvider struct { addressCodec address.Codec - decoder *txdecode.Decoder + decoder Decoder codec codec.BinaryCodec } -func NewBuilderProvider(addressCodec address.Codec, decoder *txdecode.Decoder, codec codec.BinaryCodec) *BuilderProvider { +func NewBuilderProvider(addressCodec address.Codec, decoder Decoder, codec codec.BinaryCodec) *BuilderProvider { return &BuilderProvider{ addressCodec: addressCodec, decoder: decoder, @@ -77,7 +76,7 @@ func (b BuilderProvider) WrapTxBuilder(tx *apitx.Tx) (TxBuilder, error) { type txBuilder struct { addressCodec address.Codec - decoder *txdecode.Decoder + decoder Decoder codec codec.BinaryCodec msgs []transaction.Msg @@ -95,7 +94,7 @@ type txBuilder struct { nonCriticalExtensionOptions []*anypb.Any } -func newTxBuilder(addressCodec address.Codec, decoder *txdecode.Decoder, codec codec.BinaryCodec) *txBuilder { +func newTxBuilder(addressCodec address.Codec, decoder Decoder, codec codec.BinaryCodec) *txBuilder { return &txBuilder{ addressCodec: addressCodec, decoder: decoder, diff --git a/client/v2/tx/config.go b/client/v2/tx/config.go index 679a2b0c0b6d..418f21ff59ae 100644 --- a/client/v2/tx/config.go +++ b/client/v2/tx/config.go @@ -54,7 +54,7 @@ type TxSigningConfig interface { type ConfigOptions struct { AddressCodec address.Codec - Decoder *txdecode.Decoder + Decoder Decoder Cdc codec.BinaryCodec ValidatorAddressCodec address.Codec diff --git a/client/v2/tx/encoder.go b/client/v2/tx/encoder.go index dd8d591e593c..92fc55b08a76 100644 --- a/client/v2/tx/encoder.go +++ b/client/v2/tx/encoder.go @@ -5,6 +5,7 @@ import ( protov2 "google.golang.org/protobuf/proto" apitx "cosmossdk.io/api/cosmos/tx/v1beta1" + txdecode "cosmossdk.io/x/tx/decode" ) var ( @@ -16,6 +17,11 @@ var ( } ) +// Decoder decodes bytes into x/tx DecodedTx +type Decoder interface { + Decode(txBytes []byte) (*txdecode.DecodedTx, error) +} + // txApiDecoder unmarshals transaction bytes into API Tx type type txApiDecoder func(txBytes []byte) (*apitx.Tx, error) diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index e731053698fa..5b334615efa7 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -15,6 +15,7 @@ import ( base "cosmossdk.io/api/cosmos/base/v1beta1" apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" "cosmossdk.io/client/v2/autocli/keyring" + "cosmossdk.io/client/v2/internal/coins" "cosmossdk.io/core/address" "cosmossdk.io/core/transaction" "cosmossdk.io/math" @@ -25,7 +26,6 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) // TODO: @@ -52,44 +52,7 @@ type Factory struct { // NewFactory returns a factory func NewFactory(keybase keyring.Keyring, cdc codec.BinaryCodec, accRetriever AccountRetriever, txConfig TxConfig, ac address.Codec, conn gogogrpc.ClientConn, parameters TxParameters) (Factory, error) { - //if clientCtx.Viper == nil { - // clientCtx = clientCtx.WithViper("") - //} - // - //if err := clientCtx.Viper.BindPFlags(flagSet); err != nil { - // return Factory{}, fmt.Errorf("failed to bind flags to viper: %w", err) - //} - // - //var accNum, accSeq uint64 - //if clientCtx.Offline { - // if flagSet.Changed(flags.FlagAccountNumber) && flagSet.Changed(flags.FlagSequence) { - // accNum = clientCtx.Viper.GetUint64(flags.FlagAccountNumber) - // accSeq = clientCtx.Viper.GetUint64(flags.FlagSequence) - // } else { - // return Factory{}, fmt.Errorf("account-number and sequence must be set in offline mode") - // } - //} - // - //if clientCtx.Offline && clientCtx.GenerateOnly { - // if clientCtx.ChainID != "" { - // return Factory{}, errors.New("chain ID cannot be used when offline and generate-only flags are set") - // } - //} else if clientCtx.ChainID == "" { - // return Factory{}, errors.New("chain ID required but not specified") - //} - // - //signMode := flags.ParseSignModeStr(clientCtx.SignModeStr) - //memo := clientCtx.Viper.GetString(flags.FlagNote) - //timeoutHeight := clientCtx.Viper.GetUint64(flags.FlagTimeoutHeight) - //unordered := clientCtx.Viper.GetBool(flags.FlagUnordered) - // - //gasAdj := clientCtx.Viper.GetFloat64(flags.FlagGasAdjustment) - //gasStr := clientCtx.Viper.GetString(flags.FlagGas) - //gasSetting, _ := flags.ParseGasSetting(gasStr) - //gasPricesStr := clientCtx.Viper.GetString(flags.FlagGasPrices) - // - //feesStr := clientCtx.Viper.GetString(flags.FlagFees) - f := Factory{ + return Factory{ keybase: keybase, cdc: cdc, accountRetriever: accRetriever, @@ -97,11 +60,7 @@ func NewFactory(keybase keyring.Keyring, cdc codec.BinaryCodec, accRetriever Acc conn: conn, txConfig: txConfig, txParams: parameters, - } - - // Properties that need special parsing - //f = f.WithFees(feesStr).WithGasPrices(gasPricesStr) - return f, nil + }, nil } // Prepare ensures the account defined by ctx.GetFromAddress() exists and @@ -143,29 +102,6 @@ func (f Factory) Prepare() (Factory, error) { return f, nil } -// TODO: move to internal/coins? -var ( - _ withAmount = &base.Coin{} - _ withAmount = &base.DecCoin{} -) - -type withAmount interface { - GetAmount() string -} - -func isZero[T withAmount](coins []T) (bool, error) { - for _, coin := range coins { - amount, ok := math.NewIntFromString(coin.GetAmount()) - if !ok { - return false, errors.New("invalid coin amount") - } - if !amount.IsZero() { - return false, nil - } - } - return true, nil -} - // 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 ...transaction.Msg) (TxBuilder, error) { @@ -179,12 +115,12 @@ func (f Factory) BuildUnsignedTx(msgs ...transaction.Msg) (TxBuilder, error) { fees := f.txParams.fees - isGasPriceZero, err := isZero(f.txParams.gasPrices) + isGasPriceZero, err := coins.IsZero(f.txParams.gasPrices) if err != nil { return nil, err } if !isGasPriceZero { - areFeesZero, err := isZero(fees) + areFeesZero, err := coins.IsZero(fees) if err != nil { return nil, err } @@ -489,142 +425,24 @@ func validateMemo(memo string) error { return nil } -// WithAccountRetriever returns a copy of the Factory with an updated AccountRetriever. -func (f Factory) WithAccountRetriever(ar AccountRetriever) Factory { - f.accountRetriever = ar - return f -} - -// WithChainID returns a copy of the Factory with an updated chainID. -func (f Factory) WithChainID(chainID string) Factory { - f.txParams.chainID = chainID - return f -} - // WithGas returns a copy of the Factory with an updated gas value. func (f Factory) WithGas(gas uint64) Factory { f.txParams.gas = gas return f } -// WithFees returns a copy of the Factory with an updated fee. -func (f Factory) WithFees(fees string) Factory { - parsedFees, err := sdk.ParseCoinsNormalized(fees) // TODO: do it here to avoid sdk dependency - if err != nil { - panic(err) - } - - finalFees := make([]*base.Coin, len(parsedFees)) - for i, coin := range parsedFees { - finalFees[i] = &base.Coin{ - Denom: coin.Denom, - Amount: coin.Amount.String(), - } - } - - f.txParams.fees = finalFees - return f -} - -// WithGasPrices returns a copy of the Factory with updated gas prices. -func (f Factory) WithGasPrices(gasPrices string) Factory { - parsedGasPrices, err := sdk.ParseDecCoins(gasPrices) // TODO: do it here to avoid sdk dependency - if err != nil { - panic(err) - } - - finalGasPrices := make([]*base.DecCoin, len(parsedGasPrices)) - for i, coin := range parsedGasPrices { - finalGasPrices[i] = &base.DecCoin{ - Denom: coin.Denom, - Amount: coin.Amount.String(), - } - } - - f.txParams.gasPrices = finalGasPrices - return f -} - -// WithKeybase returns a copy of the Factory with updated Keybase. -func (f Factory) WithKeybase(keybase keyring.Keyring) Factory { - f.keybase = keybase - return f -} - -// WithFromName returns a copy of the Factory with updated fromName -// fromName will be use for building a simulation tx. -func (f Factory) WithFromName(fromName string) Factory { - f.txParams.fromName = fromName - return f -} - // WithSequence returns a copy of the Factory with an updated sequence number. func (f Factory) WithSequence(sequence uint64) Factory { f.txParams.sequence = sequence return f } -// WithMemo returns a copy of the Factory with an updated memo. -func (f Factory) WithMemo(memo string) Factory { - f.txParams.memo = memo - return f -} - // WithAccountNumber returns a copy of the Factory with an updated account number. func (f Factory) WithAccountNumber(accnum uint64) Factory { f.txParams.accountNumber = accnum return f } -// WithGasAdjustment returns a copy of the Factory with an updated gas adjustment. -func (f Factory) WithGasAdjustment(gasAdj float64) Factory { - f.txParams.gasAdjustment = gasAdj - return f -} - -// WithSimulateAndExecute returns a copy of the Factory with an updated gas -// simulation value. -func (f Factory) WithSimulateAndExecute(sim bool) Factory { - f.txParams.simulateAndExecute = sim - return f -} - -// WithSignMode returns a copy of the Factory with an updated sign mode value. -func (f Factory) WithSignMode(mode apitxsigning.SignMode) Factory { - f.txParams.signMode = mode - return f -} - -// WithTimeoutHeight returns a copy of the Factory with an updated timeout height. -func (f Factory) WithTimeoutHeight(height uint64) Factory { - f.txParams.timeoutHeight = height - return f -} - -// WithFeeGranter returns a copy of the Factory with an updated fee granter. -func (f Factory) WithFeeGranter(fg string) Factory { - f.txParams.feeGranter = fg - return f -} - -// WithFeePayer returns a copy of the Factory with an updated fee granter. -func (f Factory) WithFeePayer(fp string) Factory { - f.txParams.feePayer = fp - return f -} - -// WithPreprocessTxHook returns a copy of the Factory with an updated preprocess tx function, -// allows for preprocessing of transaction data using the TxBuilder. -func (f Factory) WithPreprocessTxHook(preprocessFn PreprocessTxFn) Factory { - f.txParams.preprocessTxHook = preprocessFn - return f -} - -func (f Factory) WithExtensionOptions(extOpts ...*codectypes.Any) Factory { - f.txParams.ExtOptions = extOpts - return f -} - // PreprocessTx calls the preprocessing hook with the factory parameters and // returns the result. func (f Factory) PreprocessTx(keyname string, builder TxBuilder) error { diff --git a/client/v2/tx/tx.go b/client/v2/tx/tx.go index 0cbf5577a766..ec5477592280 100644 --- a/client/v2/tx/tx.go +++ b/client/v2/tx/tx.go @@ -14,6 +14,7 @@ import ( apitx "cosmossdk.io/api/cosmos/tx/v1beta1" keyring2 "cosmossdk.io/client/v2/autocli/keyring" "cosmossdk.io/core/transaction" + "github.com/cosmos/cosmos-sdk/client" flags2 "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/input" @@ -34,8 +35,8 @@ func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring) (params gas, _ := flags.GetUint64(flags2.FlagGas) gasAdjustment, _ := flags.GetFloat64(flags2.FlagGasAdjustment) gasPrices, _ := flags.GetString(flags2.FlagGasPrices) - fmt.Println(gasPrices) + fees, _ := flags.GetString(flags2.FlagFees) feePayer, _ := flags.GetString(flags2.FlagFeePayer) feeGrater, _ := flags.GetString(flags2.FlagFeeGranter) @@ -43,15 +44,21 @@ func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring) (params offline, _ := flags.GetBool(flags2.FlagOffline) generateOnly, _ := flags.GetBool(flags2.FlagGenerateOnly) - fees := flags2.FlagFees - fmt.Println(fees) - acc, err := keybase.GetPubKey(fromName) if err != nil { return params, err } - return TxParameters{ + gasConfig, err := NewGasConfig(gas, gasAdjustment, gasPrices) + if err != nil { + return params, err + } + feeConfig, err := NewFeeConfig(fees, feePayer, feeGrater) + if err != nil { + return params, err + } + + txParams := TxParameters{ timeoutHeight: timeout, chainID: chainID, memo: memo, @@ -62,12 +69,8 @@ func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring) (params fromName: fromName, fromAddress: acc.Address().Bytes(), }, - GasConfig: NewGasConfig(gas, gasAdjustment, nil), - FeeConfig: FeeConfig{ // TODO: needs special parsing - fees: nil, - feeGranter: feePayer, - feePayer: feeGrater, - }, + GasConfig: gasConfig, + FeeConfig: feeConfig, ExecutionOptions: ExecutionOptions{ unordered: unordered, offline: offline, @@ -76,22 +79,73 @@ func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring) (params simulateAndExecute: false, // TODO: in context preprocessTxHook: nil, // TODO: in context }, - ExtensionOptions: ExtensionOptions{}, - }, nil + ExtensionOptions: ExtensionOptions{}, // TODO + } + + return txParams, nil +} + +func validate(flags *pflag.FlagSet) error { + offline, _ := flags.GetBool(flags2.FlagOffline) + if offline { + if !flags.Changed(flags2.FlagAccountNumber) || !flags.Changed(flags2.FlagSequence) { + return errors.New("account-number and sequence must be set in offline mode") + } + } + + generateOnly, _ := flags.GetBool(flags2.FlagGenerateOnly) + chainID, _ := flags.GetString(flags2.FlagChainID) + if offline && generateOnly { + if chainID != "" { + return errors.New("chain ID cannot be used when offline and generate-only flags are set") + } + } + if chainID == "" { + return errors.New("chain ID required but not specified") + } + + return nil } // GenerateOrBroadcastTxCLI will either generate and print an unsigned transaction // or sign it and broadcast it returning an error upon failure. // TODO: remove the client.Context func GenerateOrBroadcastTxCLI(ctx client.Context, flagSet *pflag.FlagSet, msgs ...transaction.Msg) error { - k, err := keyring.NewAutoCLIKeyring(ctx.Keyring, ctx.AddressCodec) + if err := validate(flagSet); err != nil { + return err + } + + txf, err := newFactory(ctx, flagSet, msgs...) if err != nil { return err } + isAux, _ := flagSet.GetBool(flags2.FlagAux) + if isAux { + return generateAuxSignerData(ctx, txf, msgs...) + } + + // Only generate + genOnly, _ := flagSet.GetBool(flags2.FlagGenerateOnly) + if genOnly { + return generateOnly(ctx, txf, msgs...) + } + + // Simulate + // Broadcast + + return GenerateOrBroadcastTxWithFactory(ctx, txf, msgs...) +} + +func newFactory(ctx client.Context, flagSet *pflag.FlagSet, msgs ...transaction.Msg) (Factory, error) { + k, err := keyring.NewAutoCLIKeyring(ctx.Keyring, ctx.AddressCodec) + if err != nil { + return Factory{}, err + } + params, err := txParamsFromFlagSet(flagSet, k) if err != nil { - return err + return Factory{}, err } txConfig, err := NewTxConfig(ConfigOptions{ @@ -100,27 +154,25 @@ func GenerateOrBroadcastTxCLI(ctx client.Context, flagSet *pflag.FlagSet, msgs . ValidatorAddressCodec: ctx.ValidatorAddressCodec, }) if err != nil { - return err + return Factory{}, err } accRetriever := newAccountRetriever(ctx.AddressCodec, ctx, ctx.InterfaceRegistry) txf, err := NewFactory(k, ctx.Codec, accRetriever, txConfig, ctx.AddressCodec, ctx, params) if err != nil { - return err + return Factory{}, err } - return GenerateOrBroadcastTxWithFactory(ctx, txf, msgs...) + return txf, nil } -// GenerateOrBroadcastTxWithFactory will either generate and print an unsigned transaction -// or sign it and broadcast it returning an error upon failure. -func GenerateOrBroadcastTxWithFactory(clientCtx client.Context, txf Factory, msgs ...transaction.Msg) error { - // Validate all msgs before generating or broadcasting the tx. - // We were calling ValidateBasic separately in each CLI handler before. - // Right now, we're factorizing that call inside this function. - // ref: https://github.com/cosmos/cosmos-sdk/pull/9236#discussion_r623803504 +// validateMessages validates all msgs before generating or broadcasting the tx. +// We were calling ValidateBasic separately in each CLI handler before. +// Right now, we're factorizing that call inside this function. +// ref: https://github.com/cosmos/cosmos-sdk/pull/9236#discussion_r623803504 +func validateMessages(msgs ...transaction.Msg) error { for _, msg := range msgs { - m, ok := msg.(sdk.HasValidateBasic) + m, ok := msg.(sdk.HasValidateBasic) // TODO: sdk dependency if !ok { continue } @@ -130,14 +182,37 @@ func GenerateOrBroadcastTxWithFactory(clientCtx client.Context, txf Factory, msg } } - // If the --aux flag is set, we simply generate and print the AuxSignerData. - if clientCtx.IsAux { - auxSignerData, err := makeAuxSignerData(clientCtx, txf, msgs...) - if err != nil { - return err - } + return nil +} + +// generateAuxSignerData simply generates and prints the AuxSignerData. +func generateAuxSignerData(ctx client.Context, txf Factory, msgs ...transaction.Msg) error { + auxSignerData, err := makeAuxSignerData(ctx, txf, msgs...) + if err != nil { + return err + } + + return ctx.PrintString(auxSignerData.String()) +} + +func generateOnly(ctx client.Context, txf Factory, msgs ...transaction.Msg) error { + uTx, err := txf.PrintUnsignedTx(msgs...) + if err != nil { + return err + } + return ctx.PrintString(uTx) +} - return clientCtx.PrintString(auxSignerData.String()) // TODO: check this +// GenerateOrBroadcastTxWithFactory will either generate and print an unsigned transaction +// or sign it and broadcast it returning an error upon failure. +func GenerateOrBroadcastTxWithFactory(clientCtx client.Context, txf Factory, msgs ...transaction.Msg) error { + // Validate all msgs before generating or broadcasting the tx. + // We were calling ValidateBasic separately in each CLI handler before. + // Right now, we're factorizing that call inside this function. + // ref: https://github.com/cosmos/cosmos-sdk/pull/9236#discussion_r623803504 + err := validateMessages(msgs...) + if err != nil { + return err } if clientCtx.GenerateOnly { diff --git a/client/v2/tx/types.go b/client/v2/tx/types.go index 7f435697fca7..98012108151f 100644 --- a/client/v2/tx/types.go +++ b/client/v2/tx/types.go @@ -2,6 +2,7 @@ package tx import ( "cosmossdk.io/core/transaction" + "errors" "fmt" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/gogoproto/proto" @@ -19,6 +20,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +const defaultGas = 200000 + // PreprocessTxFn defines a hook by which chains can preprocess transactions before broadcasting type PreprocessTxFn func(chainID string, key uint, tx TxBuilder) error @@ -50,22 +53,57 @@ type GasConfig struct { gasPrices []*base.DecCoin } -func NewGasConfig(gas uint64, gasAdjustment float64, gasPrices []*base.DecCoin) GasConfig { +func NewGasConfig(gas uint64, gasAdjustment float64, gasPrices string) (GasConfig, error) { if gas == 0 { - gas = 200000 + gas = defaultGas + } + + parsedGasPrices, err := sdk.ParseDecCoins(gasPrices) // TODO: do it here to avoid sdk dependency + if err != nil { + return GasConfig{}, err + } + + finalGasPrices := make([]*base.DecCoin, len(parsedGasPrices)) + for i, coin := range parsedGasPrices { + finalGasPrices[i] = &base.DecCoin{ + Denom: coin.Denom, + Amount: coin.Amount.String(), + } } + return GasConfig{ gas: gas, gasAdjustment: gasAdjustment, - gasPrices: gasPrices, - } + gasPrices: finalGasPrices, + }, nil } // FeeConfig defines the 'fee' related fields in a transaction. type FeeConfig struct { fees []*base.Coin - feeGranter string feePayer string + feeGranter string +} + +func NewFeeConfig(fees, feePayer, feeGranter string) (FeeConfig, error) { + parsedFees, err := sdk.ParseCoinsNormalized(fees) // TODO: do it here to avoid sdk dependency + if err != nil { + return FeeConfig{}, err + } + + finalFees := make([]*base.Coin, len(parsedFees)) + for i, coin := range parsedFees { + finalFees[i] = &base.Coin{ + Denom: coin.Denom, + Amount: coin.Amount.String(), + } + } + + return FeeConfig{ + fees: finalFees, + feePayer: feePayer, + feeGranter: feeGranter, + }, nil } // ExecutionOptions defines the transaction execution options ran by the client @@ -104,11 +142,11 @@ type wrappedTx struct { } func (w wrappedTx) GetMsgs() ([]transaction.Msg, error) { - panic("implement me") + return nil, errors.New("not implemented") } func (w wrappedTx) GetSigners() ([][]byte, error) { - panic("implement me") + return nil, errors.New("not implemented") } func (w wrappedTx) GetPubKeys() ([]cryptotypes.PubKey, error) { From 6eb3c98fc3bd24bed4395fa3e6a24cf5029502eb Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Mon, 17 Jun 2024 12:13:20 +0200 Subject: [PATCH 07/36] update: factory --- client/v2/tx/factory.go | 121 +++++++++++++++++++++++----------------- client/v2/tx/tx.go | 118 +++++++++++++++++---------------------- 2 files changed, 120 insertions(+), 119 deletions(-) diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index 5b334615efa7..f3f9dd969999 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/cosmos/cosmos-sdk/types/tx" "math/big" "os" "strings" @@ -68,43 +69,41 @@ func NewFactory(keybase keyring.Keyring, cdc codec.BinaryCodec, accRetriever Acc // they will be queried for and set on the provided Factory. // A new Factory with the updated fields will be returned. // Note: When in offline mode, the Prepare does nothing and returns the original factory. -func (f Factory) Prepare() (Factory, error) { +func (f *Factory) Prepare() error { if f.txParams.ExecutionOptions.offline || f.txParams.ExecutionOptions.offChain { - return f, nil + return nil } if f.txParams.fromAddress.Empty() { - return f, errors.New("missing 'from address' field") + return errors.New("missing 'from address' field") } if err := f.accountRetriever.EnsureExists(context.Background(), f.txParams.fromAddress); err != nil { - return f, err + return err } if f.txParams.accountNumber == 0 || f.txParams.sequence == 0 { fc := f num, seq, err := fc.accountRetriever.GetAccountNumberSequence(context.Background(), f.txParams.fromAddress) if err != nil { - return f, err + return err } if f.txParams.accountNumber == 0 { - fc = fc.WithAccountNumber(num) + fc.WithAccountNumber(num) } if f.txParams.sequence == 0 { - fc = fc.WithSequence(seq) + fc.WithSequence(seq) } - - return fc, nil } - return f, nil + return nil } // 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 ...transaction.Msg) (TxBuilder, error) { +func (f *Factory) BuildUnsignedTx(msgs ...transaction.Msg) (TxBuilder, error) { if f.txParams.offline && f.txParams.generateOnly { if f.txParams.chainID != "" { return nil, errors.New("chain ID cannot be used when offline and generate-only flags are set") @@ -175,30 +174,51 @@ func (f Factory) BuildUnsignedTx(msgs ...transaction.Msg) (TxBuilder, error) { return txBuilder, nil } -// PrintUnsignedTx will generate an unsigned transaction and print it to the writer +func (f *Factory) CalculateGas(msgs ...transaction.Msg) error { + if f.txParams.offline { + return errors.New("cannot simulate in offline mode") + } + _, adjusted, err := f.Simulate(msgs...) + if err != nil { + return err + } + + f.WithGas(adjusted) + + return nil +} + +// Simulate simulates the execution of a transaction and returns the +// simulation response obtained by the query and the adjusted gas amount. +func (f *Factory) Simulate(msgs ...transaction.Msg) (*tx.SimulateResponse, uint64, error) { + txBytes, err := f.BuildSimTx(msgs...) + if err != nil { + return nil, 0, err + } + + txSvcClient := tx.NewServiceClient(f.conn) + simRes, err := txSvcClient.Simulate(context.Background(), &tx.SimulateRequest{ + TxBytes: txBytes, + }) + if err != nil { + return nil, 0, err + } + + return simRes, uint64(f.GasAdjustment() * float64(simRes.GasInfo.GasUsed)), nil +} + +// UnsignedTxString 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. // TODO: this should not be part of factory or at least just return the json encoded string. -func (f Factory) PrintUnsignedTx(msgs ...transaction.Msg) (string, error) { +func (f *Factory) UnsignedTxString(msgs ...transaction.Msg) (string, error) { if f.SimulateAndExecute() { - if f.txParams.offline { - return "", errors.New("cannot estimate gas in offline mode") - } - - // Prepare TxFactory with acc & seq numbers as CalculateGas requires - // account and sequence numbers to be set - preparedTxf, err := f.Prepare() - if err != nil { - return "", err - } - - _, adjusted, err := CalculateGas(f.conn, preparedTxf, msgs...) + err := f.CalculateGas(msgs...) if err != nil { return "", err } - f.WithGas(adjusted) _, _ = fmt.Fprintf(os.Stderr, "%s\n", GasEstimateResponse{GasEstimate: f.Gas()}) } @@ -228,7 +248,7 @@ func (f Factory) PrintUnsignedTx(msgs ...transaction.Msg) (string, error) { // 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 ...transaction.Msg) ([]byte, error) { +func (f *Factory) BuildSimTx(msgs ...transaction.Msg) ([]byte, error) { txb, err := f.BuildUnsignedTx(msgs...) if err != nil { return nil, err @@ -268,7 +288,7 @@ func (f Factory) BuildSimTx(msgs ...transaction.Msg) ([]byte, error) { // Signing a transaction with multiple signers in the DIRECT mode is not supported and will // return an error. // An error is returned upon failure. -func (f Factory) Sign(ctx context.Context, name string, txBuilder TxBuilder, overwriteSig bool) error { +func (f *Factory) Sign(ctx context.Context, name string, txBuilder TxBuilder, overwriteSig bool) error { if f.keybase == nil { return errors.New("keybase must be set prior to signing a transaction") } @@ -397,7 +417,7 @@ func (f Factory) Sign(ctx context.Context, name string, txBuilder TxBuilder, ove } // GetSignBytesAdapter returns the sign bytes for a given transaction and sign mode. -func (f Factory) GetSignBytesAdapter(ctx context.Context, signerData signing.SignerData, builder TxBuilder) ([]byte, error) { +func (f *Factory) GetSignBytesAdapter(ctx context.Context, signerData signing.SignerData, builder TxBuilder) ([]byte, error) { // TODO txSignerData := signing.SignerData{ ChainID: signerData.ChainID, @@ -426,26 +446,23 @@ func validateMemo(memo string) error { } // WithGas returns a copy of the Factory with an updated gas value. -func (f Factory) WithGas(gas uint64) Factory { +func (f *Factory) WithGas(gas uint64) { f.txParams.gas = gas - return f } // WithSequence returns a copy of the Factory with an updated sequence number. -func (f Factory) WithSequence(sequence uint64) Factory { +func (f *Factory) WithSequence(sequence uint64) { f.txParams.sequence = sequence - return f } // WithAccountNumber returns a copy of the Factory with an updated account number. -func (f Factory) WithAccountNumber(accnum uint64) Factory { +func (f *Factory) WithAccountNumber(accnum uint64) { f.txParams.accountNumber = accnum - return f } // PreprocessTx calls the preprocessing hook with the factory parameters and // returns the result. -func (f Factory) PreprocessTx(keyname string, builder TxBuilder) error { +func (f *Factory) PreprocessTx(keyname string, builder TxBuilder) error { if f.txParams.preprocessTxHook == nil { // Allow pass-through return nil @@ -458,27 +475,27 @@ func (f Factory) PreprocessTx(keyname string, builder TxBuilder) error { return f.txParams.preprocessTxHook(f.txParams.chainID, keyType, builder) } -func (f Factory) AccountNumber() uint64 { return f.txParams.accountNumber } -func (f Factory) Sequence() uint64 { return f.txParams.sequence } -func (f Factory) Gas() uint64 { return f.txParams.gas } -func (f Factory) GasAdjustment() float64 { return f.txParams.gasAdjustment } -func (f Factory) Keybase() keyring.Keyring { return f.keybase } -func (f Factory) ChainID() string { return f.txParams.chainID } -func (f Factory) Memo() string { return f.txParams.memo } -func (f Factory) Fees() []*base.Coin { return f.txParams.fees } -func (f Factory) GasPrices() []*base.DecCoin { return f.txParams.gasPrices } -func (f Factory) AccountRetriever() AccountRetriever { return f.accountRetriever } -func (f Factory) TimeoutHeight() uint64 { return f.txParams.timeoutHeight } -func (f Factory) FromName() string { return f.txParams.fromName } -func (f Factory) SimulateAndExecute() bool { return f.txParams.simulateAndExecute } -func (f Factory) SignMode() apitxsigning.SignMode { return f.txParams.signMode } +func (f *Factory) AccountNumber() uint64 { return f.txParams.accountNumber } +func (f *Factory) Sequence() uint64 { return f.txParams.sequence } +func (f *Factory) Gas() uint64 { return f.txParams.gas } +func (f *Factory) GasAdjustment() float64 { return f.txParams.gasAdjustment } +func (f *Factory) Keybase() keyring.Keyring { return f.keybase } +func (f *Factory) ChainID() string { return f.txParams.chainID } +func (f *Factory) Memo() string { return f.txParams.memo } +func (f *Factory) Fees() []*base.Coin { return f.txParams.fees } +func (f *Factory) GasPrices() []*base.DecCoin { return f.txParams.gasPrices } +func (f *Factory) AccountRetriever() AccountRetriever { return f.accountRetriever } +func (f *Factory) TimeoutHeight() uint64 { return f.txParams.timeoutHeight } +func (f *Factory) FromName() string { return f.txParams.fromName } +func (f *Factory) SimulateAndExecute() bool { return f.txParams.simulateAndExecute } +func (f *Factory) SignMode() apitxsigning.SignMode { return f.txParams.signMode } // getSimPK gets the public key to use for building a simulation tx. // Note, we should only check for keys in the keybase if we are in simulate and execute mode, // e.g. when using --gas=auto. // When using --dry-run, we are is simulation mode only and should not check the keybase. // Ref: https://github.com/cosmos/cosmos-sdk/issues/11283 -func (f Factory) getSimPK() (cryptotypes.PubKey, error) { +func (f *Factory) getSimPK() (cryptotypes.PubKey, error) { var ( err error pk cryptotypes.PubKey = &secp256k1.PubKey{} @@ -496,7 +513,7 @@ func (f Factory) getSimPK() (cryptotypes.PubKey, error) { // getSimSignatureData based on the pubKey type gets the correct SignatureData type // to use for building a simulation tx. -func (f Factory) getSimSignatureData(pk cryptotypes.PubKey) SignatureData { +func (f *Factory) getSimSignatureData(pk cryptotypes.PubKey) SignatureData { multisigPubKey, ok := pk.(*multisig.LegacyAminoPubKey) if !ok { return &SingleSignatureData{SignMode: f.txParams.signMode} diff --git a/client/v2/tx/tx.go b/client/v2/tx/tx.go index ec5477592280..2e797d86eabc 100644 --- a/client/v2/tx/tx.go +++ b/client/v2/tx/tx.go @@ -7,7 +7,7 @@ import ( "fmt" "os" - gogogrpc "github.com/cosmos/gogoproto/grpc" + "github.com/cosmos/gogoproto/proto" "github.com/spf13/pflag" apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" @@ -21,7 +21,6 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/types/tx" ) func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring) (params TxParameters, err error) { @@ -29,10 +28,13 @@ func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring) (params chainID, _ := flags.GetString(flags2.FlagChainID) memo, _ := flags.GetString(flags2.FlagNote) signMode, _ := flags.GetString(flags2.FlagSignMode) + accNumber, _ := flags.GetUint64(flags2.FlagAccountNumber) sequence, _ := flags.GetUint64(flags2.FlagSequence) fromName, _ := flags.GetString(flags2.FlagFrom) - gas, _ := flags.GetUint64(flags2.FlagGas) + + gas, _ := flags.GetString(flags2.FlagGas) + gasSetting, _ := flags2.ParseGasSetting(gas) gasAdjustment, _ := flags.GetFloat64(flags2.FlagGasAdjustment) gasPrices, _ := flags.GetString(flags2.FlagGasPrices) @@ -44,12 +46,17 @@ func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring) (params offline, _ := flags.GetBool(flags2.FlagOffline) generateOnly, _ := flags.GetBool(flags2.FlagGenerateOnly) - acc, err := keybase.GetPubKey(fromName) - if err != nil { - return params, err + var fromAddr []byte + dryRun, _ := flags.GetBool(flags2.FlagDryRun) + if !dryRun { + acc, err := keybase.GetPubKey(fromName) + if err != nil { + return params, err + } + fromAddr = acc.Address().Bytes() } - gasConfig, err := NewGasConfig(gas, gasAdjustment, gasPrices) + gasConfig, err := NewGasConfig(gasSetting.Gas, gasAdjustment, gasPrices) if err != nil { return params, err } @@ -67,7 +74,7 @@ func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring) (params accountNumber: accNumber, sequence: sequence, fromName: fromName, - fromAddress: acc.Address().Bytes(), + fromAddress: fromAddr, }, GasConfig: gasConfig, FeeConfig: feeConfig, @@ -76,8 +83,8 @@ func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring) (params offline: offline, offChain: false, generateOnly: generateOnly, - simulateAndExecute: false, // TODO: in context - preprocessTxHook: nil, // TODO: in context + simulateAndExecute: gasSetting.Simulate, + preprocessTxHook: nil, // TODO: in context }, ExtensionOptions: ExtensionOptions{}, // TODO } @@ -115,7 +122,12 @@ func GenerateOrBroadcastTxCLI(ctx client.Context, flagSet *pflag.FlagSet, msgs . return err } - txf, err := newFactory(ctx, flagSet, msgs...) + err := validateMessages(msgs...) + if err != nil { + return err + } + + txf, err := newFactory(ctx, flagSet) if err != nil { return err } @@ -125,19 +137,26 @@ func GenerateOrBroadcastTxCLI(ctx client.Context, flagSet *pflag.FlagSet, msgs . return generateAuxSignerData(ctx, txf, msgs...) } - // Only generate genOnly, _ := flagSet.GetBool(flags2.FlagGenerateOnly) if genOnly { return generateOnly(ctx, txf, msgs...) } // Simulate - // Broadcast + dryRun, _ := flagSet.GetBool(flags2.FlagDryRun) + if dryRun { + simulation, _, err := txf.Simulate(msgs...) + if err != nil { + return err + } + return ctx.PrintProto(simulation) + } - return GenerateOrBroadcastTxWithFactory(ctx, txf, msgs...) + // Broadcast + return BroadcastTx(ctx, txf, msgs...) } -func newFactory(ctx client.Context, flagSet *pflag.FlagSet, msgs ...transaction.Msg) (Factory, error) { +func newFactory(ctx client.Context, flagSet *pflag.FlagSet) (Factory, error) { k, err := keyring.NewAutoCLIKeyring(ctx.Keyring, ctx.AddressCodec) if err != nil { return Factory{}, err @@ -196,61 +215,47 @@ func generateAuxSignerData(ctx client.Context, txf Factory, msgs ...transaction. } func generateOnly(ctx client.Context, txf Factory, msgs ...transaction.Msg) error { - uTx, err := txf.PrintUnsignedTx(msgs...) + err := txf.Prepare() if err != nil { return err } + + uTx, err := txf.UnsignedTxString(msgs...) + if err != nil { + return err + } + return ctx.PrintString(uTx) } -// GenerateOrBroadcastTxWithFactory will either generate and print an unsigned transaction -// or sign it and broadcast it returning an error upon failure. -func GenerateOrBroadcastTxWithFactory(clientCtx client.Context, txf Factory, msgs ...transaction.Msg) error { - // Validate all msgs before generating or broadcasting the tx. - // We were calling ValidateBasic separately in each CLI handler before. - // Right now, we're factorizing that call inside this function. - // ref: https://github.com/cosmos/cosmos-sdk/pull/9236#discussion_r623803504 - err := validateMessages(msgs...) +func SimulateTx(ctx client.Context, flagSet *pflag.FlagSet, msgs ...transaction.Msg) (proto.Message, error) { + txf, err := newFactory(ctx, flagSet) if err != nil { - return err + return nil, err } - if clientCtx.GenerateOnly { - uTx, err := txf.PrintUnsignedTx(msgs...) - if err != nil { - return err - } - return clientCtx.PrintString(uTx) + simulation, _, err := txf.Simulate(msgs...) + if err != nil { + return nil, err } - return BroadcastTx(clientCtx, txf, msgs...) + return simulation, nil } // 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 ...transaction.Msg) error { - txf, err := txf.Prepare() + err := txf.Prepare() if err != nil { return err } - if txf.SimulateAndExecute() || clientCtx.Simulate { - if clientCtx.Offline { - return errors.New("cannot estimate gas in offline mode") - } - - _, adjusted, err := CalculateGas(clientCtx, txf, msgs...) + if txf.SimulateAndExecute() { + err = txf.CalculateGas(msgs...) if err != nil { return err } - - txf = txf.WithGas(adjusted) - _, _ = fmt.Fprintf(os.Stderr, "%s\n", GasEstimateResponse{GasEstimate: txf.Gas()}) - } - - if clientCtx.Simulate { - return nil } builder, err := txf.BuildUnsignedTx(msgs...) @@ -312,27 +317,6 @@ func BroadcastTx(clientCtx client.Context, txf Factory, msgs ...transaction.Msg) return clientCtx.PrintProto(res) } -// 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 ...transaction.Msg, -) (*tx.SimulateResponse, uint64, error) { - txBytes, err := txf.BuildSimTx(msgs...) - if err != nil { - return nil, 0, err - } - - txSvcClient := tx.NewServiceClient(clientCtx) - simRes, err := txSvcClient.Simulate(context.Background(), &tx.SimulateRequest{ - TxBytes: txBytes, - }) - if err != nil { - return nil, 0, err - } - - return simRes, uint64(txf.GasAdjustment() * float64(simRes.GasInfo.GasUsed)), nil -} - // makeAuxSignerData generates an AuxSignerData from the client inputs. func makeAuxSignerData(clientCtx client.Context, f Factory, msgs ...transaction.Msg) (*apitx.AuxSignerData, error) { b := NewAuxTxBuilder() From f3071a48403f6386b4b7951c5e59315f5529a233 Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Tue, 18 Jun 2024 16:53:17 +0200 Subject: [PATCH 08/36] add: factory tests --- client/v2/tx/account.go | 2 +- client/v2/tx/aux_builder_test.go | 3 +- client/v2/tx/builder_test.go | 16 +- client/v2/tx/common_test.go | 114 ++++++++ client/v2/tx/config.go | 4 +- client/v2/tx/config_test.go | 36 ++- client/v2/tx/factory.go | 30 +- client/v2/tx/factory_test.go | 451 +++++++++++++++++++++++++++++-- client/v2/tx/tx.go | 11 +- 9 files changed, 596 insertions(+), 71 deletions(-) create mode 100644 client/v2/tx/common_test.go diff --git a/client/v2/tx/account.go b/client/v2/tx/account.go index aa6d814ebdc2..cebe1069fa0d 100644 --- a/client/v2/tx/account.go +++ b/client/v2/tx/account.go @@ -3,10 +3,10 @@ package tx import ( "context" "fmt" - "google.golang.org/grpc" "strconv" gogogrpc "github.com/cosmos/gogoproto/grpc" + "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" diff --git a/client/v2/tx/aux_builder_test.go b/client/v2/tx/aux_builder_test.go index 203c02b61f7d..7480677288c4 100644 --- a/client/v2/tx/aux_builder_test.go +++ b/client/v2/tx/aux_builder_test.go @@ -253,7 +253,8 @@ func checkCorrectData(t *testing.T, cdc codec.Codec, auxSignerData *apitx.AuxSig require.Equal(t, memo, body.Memo) require.Equal(t, chainID, auxSignerData.SignDoc.ChainId) require.Equal(t, msgAny, body.GetMessages()[0]) - require.Equal(t, pkAny, auxSignerData.SignDoc.PublicKey) + require.Equal(t, pkAny.TypeUrl, auxSignerData.SignDoc.PublicKey.TypeUrl) + require.Equal(t, pkAny.Value, auxSignerData.SignDoc.PublicKey.Value) require.Equal(t, signMode, auxSignerData.Mode) require.Equal(t, rawSig, auxSignerData.Sig) } diff --git a/client/v2/tx/builder_test.go b/client/v2/tx/builder_test.go index f5a71cd374c7..1446d32b013a 100644 --- a/client/v2/tx/builder_test.go +++ b/client/v2/tx/builder_test.go @@ -4,6 +4,7 @@ import ( "reflect" "testing" + "github.com/stretchr/testify/require" "google.golang.org/protobuf/types/known/anypb" base "cosmossdk.io/api/cosmos/base/v1beta1" @@ -12,11 +13,8 @@ import ( "cosmossdk.io/core/address" "cosmossdk.io/core/transaction" txdecode "cosmossdk.io/x/tx/decode" - "cosmossdk.io/x/tx/signing" - "github.com/stretchr/testify/require" "github.com/cosmos/cosmos-sdk/codec" - addrcodec "github.com/cosmos/cosmos-sdk/codec/address" codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" @@ -24,18 +22,6 @@ import ( countertypes "github.com/cosmos/cosmos-sdk/testutil/x/counter/types" ) -var ( - cdc = codec.NewProtoCodec(codectypes.NewInterfaceRegistry()) - ac = addrcodec.NewBech32Codec("cosmos") - signingOptions = signing.Options{ - AddressCodec: ac, - ValidatorAddressCodec: addrcodec.NewBech32Codec("cosmosval"), - } - signingContext, _ = signing.NewContext(signingOptions) - decodeOptions = txdecode.Options{SigningContext: signingContext} - decoder, _ = txdecode.NewDecoder(decodeOptions) -) - func TestNewBuilderProvider(t *testing.T) { type args struct { addressCodec address.Codec diff --git a/client/v2/tx/common_test.go b/client/v2/tx/common_test.go new file mode 100644 index 000000000000..86de3ed72b06 --- /dev/null +++ b/client/v2/tx/common_test.go @@ -0,0 +1,114 @@ +package tx + +import ( + "context" + codec2 "github.com/cosmos/cosmos-sdk/crypto/codec" + + "google.golang.org/grpc" + + "cosmossdk.io/client/v2/autocli/keyring" + txdecode "cosmossdk.io/x/tx/decode" + "cosmossdk.io/x/tx/signing" + + "github.com/cosmos/cosmos-sdk/codec" + addrcodec "github.com/cosmos/cosmos-sdk/codec/address" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/hd" + cryptoKeyring "github.com/cosmos/cosmos-sdk/crypto/keyring" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx" +) + +var ( + cdc = codec.NewProtoCodec(codectypes.NewInterfaceRegistry()) + ac = addrcodec.NewBech32Codec("cosmos") + valCodec = addrcodec.NewBech32Codec("cosmosval") + signingOptions = signing.Options{ + AddressCodec: ac, + ValidatorAddressCodec: valCodec, + } + signingContext, _ = signing.NewContext(signingOptions) + decodeOptions = txdecode.Options{SigningContext: signingContext, ProtoCodec: cdc} + decoder, _ = txdecode.NewDecoder(decodeOptions) + + k = cryptoKeyring.NewInMemory(cdc) + keybase, _ = cryptoKeyring.NewAutoCLIKeyring(k, ac) + txConf, _ = NewTxConfig(ConfigOptions{ + AddressCodec: ac, + Cdc: cdc, + ValidatorAddressCodec: valCodec, + }) +) + +func setKeyring() keyring.Keyring { + registry := codectypes.NewInterfaceRegistry() + codec2.RegisterInterfaces(registry) + cdc := codec.NewProtoCodec(registry) + k := cryptoKeyring.NewInMemory(cdc) + _, err := k.NewAccount("alice", "equip will roof matter pink blind book anxiety banner elbow sun young", "", "m/44'/118'/0'/0/0", hd.Secp256k1) + if err != nil { + panic(err) + } + keybase, err := cryptoKeyring.NewAutoCLIKeyring(k, ac) + if err != nil { + panic(err) + } + return keybase +} + +type mockAccount struct { + addr sdk.AccAddress +} + +func (m mockAccount) GetAddress() sdk.AccAddress { + return m.addr +} + +func (m mockAccount) GetPubKey() cryptotypes.PubKey { + return nil +} + +func (m mockAccount) GetAccountNumber() uint64 { + return 1 +} + +func (m mockAccount) GetSequence() uint64 { + return 0 +} + +type mockAccountRetriever struct{} + +func (m mockAccountRetriever) GetAccount(_ context.Context, address sdk.AccAddress) (Account, error) { + return mockAccount{addr: address}, nil +} + +func (m mockAccountRetriever) GetAccountWithHeight(_ context.Context, address sdk.AccAddress) (Account, int64, error) { + return mockAccount{addr: address}, 0, nil +} + +func (m mockAccountRetriever) EnsureExists(_ context.Context, address sdk.AccAddress) error { + return nil +} + +func (m mockAccountRetriever) GetAccountNumberSequence(_ context.Context, address sdk.AccAddress) (accNum, accSeq uint64, err error) { + return accNum, accSeq, nil +} + +type mockClientConn struct{} + +func (m mockClientConn) Invoke(_ context.Context, _ string, args, reply interface{}, opts ...grpc.CallOption) error { + simResponse := tx.SimulateResponse{ + GasInfo: &sdk.GasInfo{ // TODO: sdk dependency + GasWanted: 10000, + GasUsed: 7500, + }, + Result: nil, + } + *reply.(*tx.SimulateResponse) = simResponse + return nil +} + +func (m mockClientConn) NewStream(_ context.Context, _ *grpc.StreamDesc, _ string, _ ...grpc.CallOption) (grpc.ClientStream, error) { + return nil, nil +} diff --git a/client/v2/tx/config.go b/client/v2/tx/config.go index 418f21ff59ae..8f00fa74ab2c 100644 --- a/client/v2/tx/config.go +++ b/client/v2/tx/config.go @@ -104,7 +104,9 @@ func NewTxConfig(options ConfigOptions) (TxConfig, error) { } if options.Decoder == nil { - options.Decoder, err = txdecode.NewDecoder(txdecode.Options{SigningContext: signingCtx.SigningContext()}) + options.Decoder, err = txdecode.NewDecoder(txdecode.Options{ + SigningContext: signingCtx.SigningContext(), + ProtoCodec: options.Cdc}) if err != nil { return nil, err } diff --git a/client/v2/tx/config_test.go b/client/v2/tx/config_test.go index fce5e5fd5905..41871e6c48a3 100644 --- a/client/v2/tx/config_test.go +++ b/client/v2/tx/config_test.go @@ -12,13 +12,13 @@ import ( "github.com/cosmos/cosmos-sdk/codec/address" ) -type testModeHandler struct{} +type mockModeHandler struct{} -func (t testModeHandler) Mode() apitxsigning.SignMode { +func (t mockModeHandler) Mode() apitxsigning.SignMode { return apitxsigning.SignMode_SIGN_MODE_DIRECT } -func (t testModeHandler) GetSignBytes(_ context.Context, _ signing.SignerData, _ signing.TxData) ([]byte, error) { +func (t mockModeHandler) GetSignBytes(_ context.Context, _ signing.SignerData, _ signing.TxData) ([]byte, error) { return []byte{}, nil } @@ -53,7 +53,6 @@ func TestConfigOptions_validate(t *testing.T) { Cdc: cdc, ValidatorAddressCodec: address.NewBech32Codec("cosmosvaloper"), }, - wantErr: true, }, { name: "missing codec", @@ -114,7 +113,7 @@ func Test_newHandlerMap(t *testing.T) { Decoder: decoder, Cdc: cdc, ValidatorAddressCodec: address.NewBech32Codec("cosmosvaloper"), - CustomSignModes: []signing.SignModeHandler{testModeHandler{}}, + CustomSignModes: []signing.SignModeHandler{mockModeHandler{}}, }, }, } @@ -133,3 +132,30 @@ func Test_newHandlerMap(t *testing.T) { }) } } + +func TestNewTxConfig(t *testing.T) { + tests := []struct { + name string + options ConfigOptions + wantErr bool + }{ + { + name: "valid options", + options: ConfigOptions{ + AddressCodec: ac, + Cdc: cdc, + ValidatorAddressCodec: valCodec, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewTxConfig(tt.options) + if (err != nil) != tt.wantErr { + t.Errorf("NewTxConfig() error = %v, wantErr %v", err, tt.wantErr) + return + } + require.NotNil(t, got) + }) + } +} diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index f3f9dd969999..d96767dd7e4f 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -174,7 +174,7 @@ func (f *Factory) BuildUnsignedTx(msgs ...transaction.Msg) (TxBuilder, error) { return txBuilder, nil } -func (f *Factory) CalculateGas(msgs ...transaction.Msg) error { +func (f *Factory) calculateGas(msgs ...transaction.Msg) error { if f.txParams.offline { return errors.New("cannot simulate in offline mode") } @@ -211,10 +211,9 @@ func (f *Factory) Simulate(msgs ...transaction.Msg) (*tx.SimulateResponse, uint6 // 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. -// TODO: this should not be part of factory or at least just return the json encoded string. func (f *Factory) UnsignedTxString(msgs ...transaction.Msg) (string, error) { if f.SimulateAndExecute() { - err := f.CalculateGas(msgs...) + err := f.calculateGas(msgs...) if err != nil { return "", err } @@ -288,7 +287,7 @@ func (f *Factory) BuildSimTx(msgs ...transaction.Msg) ([]byte, error) { // Signing a transaction with multiple signers in the DIRECT mode is not supported and will // return an error. // An error is returned upon failure. -func (f *Factory) Sign(ctx context.Context, name string, txBuilder TxBuilder, overwriteSig bool) error { +func (f *Factory) Sign(ctx context.Context, txBuilder TxBuilder, overwriteSig bool) error { if f.keybase == nil { return errors.New("keybase must be set prior to signing a transaction") } @@ -298,7 +297,7 @@ func (f *Factory) Sign(ctx context.Context, name string, txBuilder TxBuilder, ov f.txParams.signMode = f.txConfig.SignModeHandler().DefaultMode() } - pubKey, err := f.keybase.GetPubKey(name) + pubKey, err := f.keybase.GetPubKey(f.txParams.fromName) if err != nil { return err } @@ -378,13 +377,13 @@ func (f *Factory) Sign(ctx context.Context, name string, txBuilder TxBuilder, ov return err } - bytesToSign, err := f.GetSignBytesAdapter(ctx, signerData, txBuilder) + bytesToSign, err := f.getSignBytesAdapter(ctx, signerData, txBuilder) if err != nil { return err } // Sign those bytes - sigBytes, err := f.keybase.Sign(name, bytesToSign, f.txParams.signMode) + sigBytes, err := f.keybase.Sign(f.txParams.fromName, bytesToSign, f.txParams.signMode) if err != nil { return err } @@ -413,27 +412,18 @@ func (f *Factory) Sign(ctx context.Context, name string, txBuilder TxBuilder, ov // Run optional preprocessing if specified. By default, this is unset // and will return nil. - return f.PreprocessTx(name, txBuilder) + return f.PreprocessTx(f.txParams.fromName, txBuilder) } -// GetSignBytesAdapter returns the sign bytes for a given transaction and sign mode. -func (f *Factory) GetSignBytesAdapter(ctx context.Context, signerData signing.SignerData, builder TxBuilder) ([]byte, error) { - // TODO - txSignerData := signing.SignerData{ - ChainID: signerData.ChainID, - AccountNumber: signerData.AccountNumber, - Sequence: signerData.Sequence, - Address: signerData.Address, - PubKey: signerData.PubKey, - } - +// getSignBytesAdapter returns the sign bytes for a given transaction and sign mode. +func (f *Factory) getSignBytesAdapter(ctx context.Context, signerData signing.SignerData, builder TxBuilder) ([]byte, error) { txData, err := builder.GetSigningTxData() if err != nil { return nil, err } // Generate the bytes to be signed. - return f.txConfig.SignModeHandler().GetSignBytes(ctx, f.SignMode(), txSignerData, *txData) + return f.txConfig.SignModeHandler().GetSignBytes(ctx, f.SignMode(), signerData, *txData) } func validateMemo(memo string) error { diff --git a/client/v2/tx/factory_test.go b/client/v2/tx/factory_test.go index e87a59a91458..186308ad97e5 100644 --- a/client/v2/tx/factory_test.go +++ b/client/v2/tx/factory_test.go @@ -1,39 +1,448 @@ package tx import ( - "cosmossdk.io/client/v2/autocli/keyring" - "cosmossdk.io/core/address" - "github.com/cosmos/gogoproto/grpc" - "reflect" + "context" + apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + "cosmossdk.io/x/tx/signing" + "fmt" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "google.golang.org/protobuf/types/known/anypb" "testing" + + base "cosmossdk.io/api/cosmos/base/v1beta1" + "cosmossdk.io/core/transaction" + countertypes "github.com/cosmos/cosmos-sdk/testutil/x/counter/types" + "github.com/stretchr/testify/require" ) -func TestNewFactory(t *testing.T) { - type args struct { - keybase keyring.Keyring - txConfig TxConfig - ac address.Codec - conn grpc.ClientConn - parameters TxParameters +func TestFactory_Prepare(t *testing.T) { + tests := []struct { + name string + txParams TxParameters + error bool + }{ + { + name: "no error", + txParams: TxParameters{ + AccountConfig: AccountConfig{ + fromAddress: []byte("hello"), + }, + }, + }, + { + name: "without account", + txParams: TxParameters{}, + error: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := NewFactory(keybase, cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, tt.txParams) + require.NoError(t, err) + require.NotNil(t, f) + if err := f.Prepare(); (err != nil) != tt.error { + t.Errorf("Prepare() error = %v, wantErr %v", err, tt.error) + } + }) + } +} + +func TestFactory_BuildUnsignedTx(t *testing.T) { + tests := []struct { + name string + txParams TxParameters + msgs []transaction.Msg + error bool + }{ + { + name: "no error", + txParams: TxParameters{ + chainID: "demo", + }, + msgs: []transaction.Msg{ + &countertypes.MsgIncreaseCounter{ + Signer: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + Count: 0, + }, + }, + }, + { + name: "chainId not provided", + txParams: TxParameters{}, + msgs: []transaction.Msg{}, + error: true, + }, + { + name: "offline and generateOnly with chainIde provided", + txParams: TxParameters{ + chainID: "demo", + ExecutionOptions: ExecutionOptions{ + offline: true, + generateOnly: true, + }, + }, + msgs: []transaction.Msg{}, + error: true, + }, + { + name: "fees and gas price provided", + txParams: TxParameters{ + chainID: "demo", + GasConfig: GasConfig{ + gasPrices: []*base.DecCoin{ + { + Amount: "1000", + Denom: "stake", + }, + }, + }, + FeeConfig: FeeConfig{ + fees: []*base.Coin{ + { + Amount: "1000", + Denom: "stake", + }, + }, + }, + }, + msgs: []transaction.Msg{}, + error: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := NewFactory(keybase, cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, tt.txParams) + require.NoError(t, err) + require.NotNil(t, f) + got, err := f.BuildUnsignedTx(tt.msgs...) + if tt.error { + require.Error(t, err) + } else { + require.NoError(t, err) + require.NotNil(t, got) + builder, ok := got.(*txBuilder) + require.True(t, ok) + require.Nil(t, builder.signatures) + require.Nil(t, builder.signerInfos) + } + }) + } +} + +func TestFactory_calculateGas(t *testing.T) { + tests := []struct { + name string + txParams TxParameters + msgs []transaction.Msg + error bool + }{ + { + name: "no error", + txParams: TxParameters{ + chainID: "demo", + GasConfig: GasConfig{ + gasAdjustment: 1, + }, + }, + msgs: []transaction.Msg{ + &countertypes.MsgIncreaseCounter{ + Signer: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + Count: 0, + }, + }, + }, + { + name: "offline mode", + txParams: TxParameters{ + chainID: "demo", + ExecutionOptions: ExecutionOptions{ + offline: true, + }, + }, + msgs: []transaction.Msg{ + &countertypes.MsgIncreaseCounter{ + Signer: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + Count: 0, + }, + }, + error: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := NewFactory(keybase, cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, tt.txParams) + require.NoError(t, err) + require.NotNil(t, f) + err = f.calculateGas(tt.msgs...) + if tt.error { + require.Error(t, err) + } else { + require.NoError(t, err) + require.NotZero(t, f.txParams.GasConfig) + } + }) + } +} + +func TestFactory_Simulate(t *testing.T) { + tests := []struct { + name string + txParams TxParameters + msgs []transaction.Msg + error bool + }{ + { + name: "no error", + txParams: TxParameters{ + chainID: "demo", + GasConfig: GasConfig{ + gasAdjustment: 1, + }, + }, + msgs: []transaction.Msg{ + &countertypes.MsgIncreaseCounter{ + Signer: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + Count: 0, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := NewFactory(keybase, cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, tt.txParams) + require.NoError(t, err) + require.NotNil(t, f) + got, got1, err := f.Simulate(tt.msgs...) + if tt.error { + require.Error(t, err) + } else { + require.NoError(t, err) + require.NotNil(t, got) + require.NotZero(t, got1) + } + }) + } +} + +func TestFactory_BuildSimTx(t *testing.T) { + tests := []struct { + name string + txParams TxParameters + msgs []transaction.Msg + want []byte + error bool + }{ + { + name: "no error", + txParams: TxParameters{ + chainID: "demo", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := NewFactory(keybase, cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, tt.txParams) + require.NoError(t, err) + require.NotNil(t, f) + got, err := f.BuildSimTx(tt.msgs...) + if tt.error { + require.Error(t, err) + } else { + require.NoError(t, err) + require.NotNil(t, got) + } + }) + } +} + +func TestFactory_Sign(t *testing.T) { + tests := []struct { + name string + txParams TxParameters + wantErr bool + }{ + { + name: "no error", + txParams: TxParameters{ + chainID: "demo", + AccountConfig: AccountConfig{ + fromName: "alice", + }, + }, + }, } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := NewFactory(setKeyring(), cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, tt.txParams) + require.NoError(t, err) + require.NotNil(t, f) + + builder, err := f.BuildUnsignedTx([]transaction.Msg{ + &countertypes.MsgIncreaseCounter{ + Signer: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + Count: 0, + }, + }...) + require.NoError(t, err) + require.NotNil(t, builder) + + builderTx, ok := builder.(*txBuilder) + require.True(t, ok) + require.Nil(t, builderTx.signatures) + require.Nil(t, builderTx.signerInfos) + + err = f.Sign(context.Background(), builder, true) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.NotNil(t, builderTx.signatures) + require.NotNil(t, builderTx.signerInfos) + } + }) + } +} + +func TestFactory_getSignBytesAdapter(t *testing.T) { + tests := []struct { + name string + txParams TxParameters + error bool + }{ + { + name: "no error", + txParams: TxParameters{ + chainID: "demo", + signMode: apitxsigning.SignMode_SIGN_MODE_DIRECT, + }, + }, + { + name: "signMode not specified", + txParams: TxParameters{ + chainID: "demo", + }, + error: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := NewFactory(setKeyring(), cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, tt.txParams) + require.NoError(t, err) + require.NotNil(t, f) + + txb, err := f.BuildUnsignedTx([]transaction.Msg{ + &countertypes.MsgIncreaseCounter{ + Signer: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + Count: 0, + }, + }...) + + pk, err := f.keybase.GetPubKey("alice") + require.NoError(t, err) + require.NotNil(t, pk) + + addr, err := f.ac.BytesToString(pk.Address()) + require.NoError(t, err) + require.NotNil(t, addr) + + signerData := signing.SignerData{ + Address: addr, + ChainID: f.txParams.chainID, + AccountNumber: 0, + Sequence: 0, + PubKey: &anypb.Any{ + TypeUrl: codectypes.MsgTypeURL(pk), + Value: pk.Bytes(), + }, + } + + got, err := f.getSignBytesAdapter(context.Background(), signerData, txb) + fmt.Println(got) + if tt.error { + require.Error(t, err) + } else { + require.NoError(t, err) + require.NotNil(t, got) + } + }) + } +} + +func Test_validateMemo(t *testing.T) { tests := []struct { name string - args args - want Factory + memo string wantErr bool }{ - // TODO: Add test cases. + { + name: "empty memo", + memo: "", + }, + { + name: "valid memo", + memo: "11245", + }, + { + name: "invalid Memo", + memo: "echo echo echo echo echo echo echo echo echo echo echo echo echo echo echo", + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := NewFactory(tt.args.keybase, tt.args.txConfig, tt.args.ac, tt.args.conn, tt.args.parameters) - if (err != nil) != tt.wantErr { - t.Errorf("NewFactory() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewFactory() got = %v, want %v", got, tt.want) + if err := validateMemo(tt.memo); (err != nil) != tt.wantErr { + t.Errorf("validateMemo() error = %v, wantErr %v", err, tt.wantErr) } }) } } + +func TestFactory_WithFunctions(t *testing.T) { + tests := []struct { + name string + txParams TxParameters + withFunc func(*Factory) + checkFunc func(*Factory) bool + }{ + { + name: "with gas", + txParams: TxParameters{}, + withFunc: func(f *Factory) { + f.WithGas(1000) + }, + checkFunc: func(f *Factory) bool { + return f.txParams.GasConfig.gas == 1000 + }, + }, + { + name: "with sequence", + txParams: TxParameters{}, + withFunc: func(f *Factory) { + f.WithSequence(10) + }, + checkFunc: func(f *Factory) bool { + return f.txParams.AccountConfig.sequence == 10 + }, + }, + { + name: "with account number", + txParams: TxParameters{}, + withFunc: func(f *Factory) { + f.WithAccountNumber(123) + }, + checkFunc: func(f *Factory) bool { + return f.txParams.AccountConfig.accountNumber == 123 + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := NewFactory(setKeyring(), cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, tt.txParams) + require.NoError(t, err) + require.NotNil(t, f) + + tt.withFunc(&f) + require.True(t, tt.checkFunc(&f)) + }) + } +} diff --git a/client/v2/tx/tx.go b/client/v2/tx/tx.go index 2e797d86eabc..0e561ea7f749 100644 --- a/client/v2/tx/tx.go +++ b/client/v2/tx/tx.go @@ -228,6 +228,7 @@ func generateOnly(ctx client.Context, txf Factory, msgs ...transaction.Msg) erro return ctx.PrintString(uTx) } +// SimulateTx simulates a tx and returns the simulation response obtained by the query. func SimulateTx(ctx client.Context, flagSet *pflag.FlagSet, msgs ...transaction.Msg) (proto.Message, error) { txf, err := newFactory(ctx, flagSet) if err != nil { @@ -235,11 +236,7 @@ func SimulateTx(ctx client.Context, flagSet *pflag.FlagSet, msgs ...transaction. } simulation, _, err := txf.Simulate(msgs...) - if err != nil { - return nil, err - } - - return simulation, nil + return simulation, err } // BroadcastTx attempts to generate, sign and broadcast a transaction with the @@ -252,7 +249,7 @@ func BroadcastTx(clientCtx client.Context, txf Factory, msgs ...transaction.Msg) } if txf.SimulateAndExecute() { - err = txf.CalculateGas(msgs...) + err = txf.calculateGas(msgs...) if err != nil { return err } @@ -294,7 +291,7 @@ func BroadcastTx(clientCtx client.Context, txf Factory, msgs ...transaction.Msg) } } - if err = txf.Sign(clientCtx.CmdContext, clientCtx.FromName, builder, true); err != nil { + if err = txf.Sign(clientCtx.CmdContext, builder, true); err != nil { return err } From b9bcaa618215f89a1b4ce33a3fa16d154d58cb95 Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Wed, 19 Jun 2024 11:56:00 +0200 Subject: [PATCH 09/36] del: most sdk dependency --- client/v2/internal/coins/util.go | 35 ++++++++++++++++++++++++++ client/v2/tx/account.go | 35 ++++++++++++++++---------- client/v2/tx/aux_builder_test.go | 6 ++--- client/v2/tx/builder.go | 2 +- client/v2/tx/common_test.go | 25 ++++++++++--------- client/v2/tx/config.go | 6 +++-- client/v2/tx/factory.go | 10 ++++---- client/v2/tx/factory_test.go | 12 +++++---- client/v2/tx/tx.go | 3 +-- client/v2/tx/types.go | 43 +++++++++++++------------------- 10 files changed, 108 insertions(+), 69 deletions(-) diff --git a/client/v2/internal/coins/util.go b/client/v2/internal/coins/util.go index afc511fe2ae7..1495386713f6 100644 --- a/client/v2/internal/coins/util.go +++ b/client/v2/internal/coins/util.go @@ -5,6 +5,8 @@ import ( base "cosmossdk.io/api/cosmos/base/v1beta1" "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" ) var ( @@ -29,3 +31,36 @@ func IsZero[T withAmount](coins []T) (bool, error) { } return true, nil } + +func ParseDecCoins(coins string) ([]*base.DecCoin, error) { + parsedGasPrices, err := sdk.ParseDecCoins(coins) // TODO: do it here to avoid sdk dependency + if err != nil { + return nil, err + } + + finalGasPrices := make([]*base.DecCoin, len(parsedGasPrices)) + for i, coin := range parsedGasPrices { + finalGasPrices[i] = &base.DecCoin{ + Denom: coin.Denom, + Amount: coin.Amount.String(), + } + } + return finalGasPrices, nil +} + +func ParseCoinsNormalized(coins string) ([]*base.Coin, error) { + parsedFees, err := sdk.ParseCoinsNormalized(coins) // TODO: do it here to avoid sdk dependency + if err != nil { + return nil, err + } + + finalFees := make([]*base.Coin, len(parsedFees)) + for i, coin := range parsedFees { + finalFees[i] = &base.Coin{ + Denom: coin.Denom, + Amount: coin.Amount.String(), + } + } + + return finalFees, nil +} diff --git a/client/v2/tx/account.go b/client/v2/tx/account.go index cebe1069fa0d..8115bf024d3f 100644 --- a/client/v2/tx/account.go +++ b/client/v2/tx/account.go @@ -3,6 +3,7 @@ package tx import ( "context" "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" "strconv" gogogrpc "github.com/cosmos/gogoproto/grpc" @@ -16,12 +17,15 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - sdk "github.com/cosmos/cosmos-sdk/types" - grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" ) // TODO: move to internal +const ( + // GRPCBlockHeightHeader is the gRPC header for block height. + GRPCBlockHeightHeader = "x-cosmos-block-height" +) + var _ AccountRetriever = accountRetriever{} // Account defines a read-only version of the auth module's AccountI. @@ -36,10 +40,10 @@ type Account interface { // ensure an account exists and to be able to query for account fields necessary // for signing. type AccountRetriever interface { - GetAccount(context.Context, sdk.AccAddress) (Account, error) - GetAccountWithHeight(context.Context, sdk.AccAddress) (Account, int64, error) - EnsureExists(context.Context, sdk.AccAddress) error - GetAccountNumberSequence(context.Context, sdk.AccAddress) (accNum, accSeq uint64, err error) + GetAccount(context.Context, []byte) (Account, error) + GetAccountWithHeight(context.Context, []byte) (Account, int64, error) + EnsureExists(context.Context, []byte) error + GetAccountNumberSequence(context.Context, []byte) (accNum, accSeq uint64, err error) } type accountRetriever struct { @@ -56,24 +60,29 @@ func newAccountRetriever(ac address.Codec, conn gogogrpc.ClientConn, registry co } } -func (a accountRetriever) GetAccount(ctx context.Context, addr sdk.AccAddress) (Account, error) { +func (a accountRetriever) GetAccount(ctx context.Context, addr []byte) (Account, error) { acc, _, err := a.GetAccountWithHeight(ctx, addr) return acc, err } -func (a accountRetriever) GetAccountWithHeight(ctx context.Context, addr sdk.AccAddress) (Account, int64, error) { +func (a accountRetriever) GetAccountWithHeight(ctx context.Context, addr []byte) (Account, int64, error) { var header metadata.MD qc := authtypes.NewQueryClient(a.conn) - res, err := qc.Account(ctx, &authtypes.QueryAccountRequest{Address: addr.String()}, grpc.Header(&header)) + addrStr, err := a.ac.BytesToString(addr) + if err != nil { + return nil, 0, err + } + + res, err := qc.Account(ctx, &authtypes.QueryAccountRequest{Address: addrStr}, grpc.Header(&header)) if err != nil { return nil, 0, err } - blockHeight := header.Get(grpctypes.GRPCBlockHeightHeader) + blockHeight := header.Get(GRPCBlockHeightHeader) if l := len(blockHeight); l != 1 { - return nil, 0, fmt.Errorf("unexpected '%s' header length; got %d, expected: %d", grpctypes.GRPCBlockHeightHeader, l, 1) + return nil, 0, fmt.Errorf("unexpected '%s' header length; got %d, expected: %d", GRPCBlockHeightHeader, l, 1) } nBlockHeight, err := strconv.Atoi(blockHeight[0]) @@ -90,14 +99,14 @@ func (a accountRetriever) GetAccountWithHeight(ctx context.Context, addr sdk.Acc } -func (a accountRetriever) EnsureExists(ctx context.Context, addr sdk.AccAddress) error { +func (a accountRetriever) EnsureExists(ctx context.Context, addr []byte) error { if _, err := a.GetAccount(ctx, addr); err != nil { return err } return nil } -func (a accountRetriever) GetAccountNumberSequence(ctx context.Context, addr sdk.AccAddress) (accNum, accSeq uint64, err error) { +func (a accountRetriever) GetAccountNumberSequence(ctx context.Context, addr []byte) (accNum, accSeq uint64, err error) { acc, err := a.GetAccount(ctx, addr) if err != nil { if status.Code(err) == codes.NotFound { diff --git a/client/v2/tx/aux_builder_test.go b/client/v2/tx/aux_builder_test.go index 7480677288c4..e22cdcb8f58b 100644 --- a/client/v2/tx/aux_builder_test.go +++ b/client/v2/tx/aux_builder_test.go @@ -19,7 +19,6 @@ import ( "github.com/cosmos/cosmos-sdk/testutil/x/counter" countertypes "github.com/cosmos/cosmos-sdk/testutil/x/counter/types" moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" - typestx "github.com/cosmos/cosmos-sdk/types/tx" ) const ( @@ -243,7 +242,7 @@ func checkCorrectData(t *testing.T, cdc codec.Codec, auxSignerData *apitx.AuxSig msgAny, err := codectypes.NewAnyWithValue(msg1) require.NoError(t, err) - var body typestx.TxBody + var body apitx.TxBody err = cdc.Unmarshal(auxSignerData.SignDoc.BodyBytes, &body) require.NoError(t, err) @@ -252,7 +251,8 @@ func checkCorrectData(t *testing.T, cdc codec.Codec, auxSignerData *apitx.AuxSig require.Equal(t, timeoutHeight, body.TimeoutHeight) require.Equal(t, memo, body.Memo) require.Equal(t, chainID, auxSignerData.SignDoc.ChainId) - require.Equal(t, msgAny, body.GetMessages()[0]) + require.Equal(t, msgAny.TypeUrl, body.GetMessages()[0].TypeUrl) + require.Equal(t, msgAny.Value, body.GetMessages()[0].Value) require.Equal(t, pkAny.TypeUrl, auxSignerData.SignDoc.PublicKey.TypeUrl) require.Equal(t, pkAny.Value, auxSignerData.SignDoc.PublicKey.Value) require.Equal(t, signMode, auxSignerData.Mode) diff --git a/client/v2/tx/builder.go b/client/v2/tx/builder.go index b4261a688785..bd84637a65a4 100644 --- a/client/v2/tx/builder.go +++ b/client/v2/tx/builder.go @@ -9,8 +9,8 @@ import ( "cosmossdk.io/core/address" "cosmossdk.io/core/transaction" "cosmossdk.io/x/tx/signing" - "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" ) diff --git a/client/v2/tx/common_test.go b/client/v2/tx/common_test.go index 86de3ed72b06..667e24cd4e18 100644 --- a/client/v2/tx/common_test.go +++ b/client/v2/tx/common_test.go @@ -2,7 +2,9 @@ package tx import ( "context" - codec2 "github.com/cosmos/cosmos-sdk/crypto/codec" + abciv1beta1 "cosmossdk.io/api/cosmos/base/abci/v1beta1" + apitx "cosmossdk.io/api/cosmos/tx/v1beta1" + "github.com/cosmos/cosmos-sdk/types" "google.golang.org/grpc" @@ -13,11 +15,10 @@ import ( "github.com/cosmos/cosmos-sdk/codec" addrcodec "github.com/cosmos/cosmos-sdk/codec/address" codectypes "github.com/cosmos/cosmos-sdk/codec/types" + codec2 "github.com/cosmos/cosmos-sdk/crypto/codec" "github.com/cosmos/cosmos-sdk/crypto/hd" cryptoKeyring "github.com/cosmos/cosmos-sdk/crypto/keyring" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/tx" ) var ( @@ -58,10 +59,10 @@ func setKeyring() keyring.Keyring { } type mockAccount struct { - addr sdk.AccAddress + addr []byte } -func (m mockAccount) GetAddress() sdk.AccAddress { +func (m mockAccount) GetAddress() types.AccAddress { return m.addr } @@ -79,33 +80,33 @@ func (m mockAccount) GetSequence() uint64 { type mockAccountRetriever struct{} -func (m mockAccountRetriever) GetAccount(_ context.Context, address sdk.AccAddress) (Account, error) { +func (m mockAccountRetriever) GetAccount(_ context.Context, address []byte) (Account, error) { return mockAccount{addr: address}, nil } -func (m mockAccountRetriever) GetAccountWithHeight(_ context.Context, address sdk.AccAddress) (Account, int64, error) { +func (m mockAccountRetriever) GetAccountWithHeight(_ context.Context, address []byte) (Account, int64, error) { return mockAccount{addr: address}, 0, nil } -func (m mockAccountRetriever) EnsureExists(_ context.Context, address sdk.AccAddress) error { +func (m mockAccountRetriever) EnsureExists(_ context.Context, address []byte) error { return nil } -func (m mockAccountRetriever) GetAccountNumberSequence(_ context.Context, address sdk.AccAddress) (accNum, accSeq uint64, err error) { +func (m mockAccountRetriever) GetAccountNumberSequence(_ context.Context, address []byte) (accNum, accSeq uint64, err error) { return accNum, accSeq, nil } type mockClientConn struct{} func (m mockClientConn) Invoke(_ context.Context, _ string, args, reply interface{}, opts ...grpc.CallOption) error { - simResponse := tx.SimulateResponse{ - GasInfo: &sdk.GasInfo{ // TODO: sdk dependency + simResponse := apitx.SimulateResponse{ + GasInfo: &abciv1beta1.GasInfo{ // TODO: sdk dependency GasWanted: 10000, GasUsed: 7500, }, Result: nil, } - *reply.(*tx.SimulateResponse) = simResponse + *reply.(*apitx.SimulateResponse) = simResponse return nil } diff --git a/client/v2/tx/config.go b/client/v2/tx/config.go index 8f00fa74ab2c..52fd855fa2bf 100644 --- a/client/v2/tx/config.go +++ b/client/v2/tx/config.go @@ -1,6 +1,10 @@ package tx import ( + "errors" + + "google.golang.org/protobuf/reflect/protoreflect" + apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" "cosmossdk.io/core/address" txdecode "cosmossdk.io/x/tx/decode" @@ -9,8 +13,6 @@ import ( "cosmossdk.io/x/tx/signing/direct" "cosmossdk.io/x/tx/signing/directaux" "cosmossdk.io/x/tx/signing/textual" - "errors" - "google.golang.org/protobuf/reflect/protoreflect" "github.com/cosmos/cosmos-sdk/codec" ) diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index d96767dd7e4f..5849da5fe661 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "github.com/cosmos/cosmos-sdk/types/tx" "math/big" "os" "strings" @@ -15,6 +14,7 @@ import ( base "cosmossdk.io/api/cosmos/base/v1beta1" apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + apitx "cosmossdk.io/api/cosmos/tx/v1beta1" "cosmossdk.io/client/v2/autocli/keyring" "cosmossdk.io/client/v2/internal/coins" "cosmossdk.io/core/address" @@ -74,7 +74,7 @@ func (f *Factory) Prepare() error { return nil } - if f.txParams.fromAddress.Empty() { + if len(f.txParams.fromAddress) == 0 { return errors.New("missing 'from address' field") } @@ -190,14 +190,14 @@ func (f *Factory) calculateGas(msgs ...transaction.Msg) error { // Simulate simulates the execution of a transaction and returns the // simulation response obtained by the query and the adjusted gas amount. -func (f *Factory) Simulate(msgs ...transaction.Msg) (*tx.SimulateResponse, uint64, error) { +func (f *Factory) Simulate(msgs ...transaction.Msg) (*apitx.SimulateResponse, uint64, error) { txBytes, err := f.BuildSimTx(msgs...) if err != nil { return nil, 0, err } - txSvcClient := tx.NewServiceClient(f.conn) - simRes, err := txSvcClient.Simulate(context.Background(), &tx.SimulateRequest{ + txSvcClient := apitx.NewServiceClient(f.conn) + simRes, err := txSvcClient.Simulate(context.Background(), &apitx.SimulateRequest{ TxBytes: txBytes, }) if err != nil { diff --git a/client/v2/tx/factory_test.go b/client/v2/tx/factory_test.go index 186308ad97e5..519f56e4089e 100644 --- a/client/v2/tx/factory_test.go +++ b/client/v2/tx/factory_test.go @@ -2,17 +2,19 @@ package tx import ( "context" - apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" - "cosmossdk.io/x/tx/signing" "fmt" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "google.golang.org/protobuf/types/known/anypb" "testing" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/anypb" + base "cosmossdk.io/api/cosmos/base/v1beta1" + apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" "cosmossdk.io/core/transaction" + "cosmossdk.io/x/tx/signing" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" countertypes "github.com/cosmos/cosmos-sdk/testutil/x/counter/types" - "github.com/stretchr/testify/require" ) func TestFactory_Prepare(t *testing.T) { diff --git a/client/v2/tx/tx.go b/client/v2/tx/tx.go index 0e561ea7f749..a77bdbb55bd4 100644 --- a/client/v2/tx/tx.go +++ b/client/v2/tx/tx.go @@ -19,7 +19,6 @@ import ( flags2 "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/input" "github.com/cosmos/cosmos-sdk/crypto/keyring" - sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -191,7 +190,7 @@ func newFactory(ctx client.Context, flagSet *pflag.FlagSet) (Factory, error) { // ref: https://github.com/cosmos/cosmos-sdk/pull/9236#discussion_r623803504 func validateMessages(msgs ...transaction.Msg) error { for _, msg := range msgs { - m, ok := msg.(sdk.HasValidateBasic) // TODO: sdk dependency + m, ok := msg.(HasValidateBasic) if !ok { continue } diff --git a/client/v2/tx/types.go b/client/v2/tx/types.go index 98012108151f..cec27c8c659c 100644 --- a/client/v2/tx/types.go +++ b/client/v2/tx/types.go @@ -1,23 +1,23 @@ package tx import ( - "cosmossdk.io/core/transaction" "errors" "fmt" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/gogoproto/proto" - "google.golang.org/protobuf/types/known/anypb" "reflect" "strings" + "github.com/cosmos/gogoproto/proto" gogoany "github.com/cosmos/gogoproto/types/any" + "google.golang.org/protobuf/types/known/anypb" base "cosmossdk.io/api/cosmos/base/v1beta1" apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" apitx "cosmossdk.io/api/cosmos/tx/v1beta1" + "cosmossdk.io/client/v2/internal/coins" + "cosmossdk.io/core/transaction" + "github.com/cosmos/cosmos-sdk/codec" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) const defaultGas = 200000 @@ -25,6 +25,13 @@ const defaultGas = 200000 // PreprocessTxFn defines a hook by which chains can preprocess transactions before broadcasting type PreprocessTxFn func(chainID string, key uint, tx TxBuilder) error +// HasValidateBasic is a copy of types.HasValidateBasic to avoid sdk import. +type HasValidateBasic interface { + // ValidateBasic does a simple validation check that + // doesn't require access to any other information. + ValidateBasic() error +} + type TxParameters struct { timeoutHeight uint64 chainID string @@ -43,7 +50,7 @@ type AccountConfig struct { accountNumber uint64 sequence uint64 fromName string - fromAddress sdk.AccAddress + fromAddress []byte } // GasConfig defines the 'gas' related fields in a transaction. @@ -58,23 +65,15 @@ func NewGasConfig(gas uint64, gasAdjustment float64, gasPrices string) (GasConfi gas = defaultGas } - parsedGasPrices, err := sdk.ParseDecCoins(gasPrices) // TODO: do it here to avoid sdk dependency + parsedGasPrices, err := coins.ParseDecCoins(gasPrices) if err != nil { return GasConfig{}, err } - finalGasPrices := make([]*base.DecCoin, len(parsedGasPrices)) - for i, coin := range parsedGasPrices { - finalGasPrices[i] = &base.DecCoin{ - Denom: coin.Denom, - Amount: coin.Amount.String(), - } - } - return GasConfig{ gas: gas, gasAdjustment: gasAdjustment, - gasPrices: finalGasPrices, + gasPrices: parsedGasPrices, }, nil } @@ -86,21 +85,13 @@ type FeeConfig struct { } func NewFeeConfig(fees, feePayer, feeGranter string) (FeeConfig, error) { - parsedFees, err := sdk.ParseCoinsNormalized(fees) // TODO: do it here to avoid sdk dependency + parsedFees, err := coins.ParseCoinsNormalized(fees) // TODO: do it here to avoid sdk dependency if err != nil { return FeeConfig{}, err } - finalFees := make([]*base.Coin, len(parsedFees)) - for i, coin := range parsedFees { - finalFees[i] = &base.Coin{ - Denom: coin.Denom, - Amount: coin.Amount.String(), - } - } - return FeeConfig{ - fees: finalFees, + fees: parsedFees, feePayer: feePayer, feeGranter: feeGranter, }, nil From 1270a8750b3e798d95a7335c6f0fc69d4f033be4 Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Wed, 19 Jun 2024 14:06:19 +0200 Subject: [PATCH 10/36] fix: dry-run and sign --- client/v2/autocli/keyring/interface.go | 3 ++ client/v2/autocli/keyring/no_keyring.go | 4 ++ client/v2/tx/factory.go | 19 ++----- client/v2/tx/factory_test.go | 2 +- client/v2/tx/tx.go | 66 ++++++++++++++++--------- client/v2/tx/types.go | 3 +- crypto/keyring/autocli.go | 43 +++++++++++----- 7 files changed, 87 insertions(+), 53 deletions(-) diff --git a/client/v2/autocli/keyring/interface.go b/client/v2/autocli/keyring/interface.go index 6ec62cdf4f18..7f2fee1b3e3d 100644 --- a/client/v2/autocli/keyring/interface.go +++ b/client/v2/autocli/keyring/interface.go @@ -23,4 +23,7 @@ type Keyring interface { // KeyType returns the type of the key. KeyType(name string) (uint, error) + + // KeyInfo given a key name or address returns key name, key address and key type. + KeyInfo(nameOrAddr string) (string, string, uint, error) } diff --git a/client/v2/autocli/keyring/no_keyring.go b/client/v2/autocli/keyring/no_keyring.go index 01666a19f8b8..7f0be9c7593e 100644 --- a/client/v2/autocli/keyring/no_keyring.go +++ b/client/v2/autocli/keyring/no_keyring.go @@ -33,3 +33,7 @@ func (k NoKeyring) Sign(name string, msg []byte, signMode signingv1beta1.SignMod func (k NoKeyring) KeyType(name string) (uint, error) { return 0, errNoKeyring } + +func (k NoKeyring) KeyInfo(name string) (string, string, uint, error) { + return "", "", 0, errNoKeyring +} diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index 5849da5fe661..c9d4a4390cf8 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "math/big" - "os" "strings" "github.com/cosmos/go-bip39" @@ -29,16 +28,6 @@ import ( cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" ) -// TODO: -// FactoryI -type FactoryI interface { - BuildAuxSignerData() - BuildUnsignedTx(msg ...transaction.Msg) (TxBuilder, error) // TODO: should return a TxBuilder or a tx already? - SignUnsignedTx() - BuildSignedTx() - BuildSimulationTx() -} - // Factory defines a client transaction factory that facilitates generating and // signing an application-specific transaction. type Factory struct { @@ -74,17 +63,17 @@ func (f *Factory) Prepare() error { return nil } - if len(f.txParams.fromAddress) == 0 { + if len(f.txParams.address) == 0 { return errors.New("missing 'from address' field") } - if err := f.accountRetriever.EnsureExists(context.Background(), f.txParams.fromAddress); err != nil { + if err := f.accountRetriever.EnsureExists(context.Background(), f.txParams.address); err != nil { return err } if f.txParams.accountNumber == 0 || f.txParams.sequence == 0 { fc := f - num, seq, err := fc.accountRetriever.GetAccountNumberSequence(context.Background(), f.txParams.fromAddress) + num, seq, err := fc.accountRetriever.GetAccountNumberSequence(context.Background(), f.txParams.address) if err != nil { return err } @@ -217,8 +206,6 @@ func (f *Factory) UnsignedTxString(msgs ...transaction.Msg) (string, error) { if err != nil { return "", err } - - _, _ = fmt.Fprintf(os.Stderr, "%s\n", GasEstimateResponse{GasEstimate: f.Gas()}) } builder, err := f.BuildUnsignedTx(msgs...) diff --git a/client/v2/tx/factory_test.go b/client/v2/tx/factory_test.go index 519f56e4089e..f93b7bbf9d27 100644 --- a/client/v2/tx/factory_test.go +++ b/client/v2/tx/factory_test.go @@ -27,7 +27,7 @@ func TestFactory_Prepare(t *testing.T) { name: "no error", txParams: TxParameters{ AccountConfig: AccountConfig{ - fromAddress: []byte("hello"), + address: []byte("hello"), }, }, }, diff --git a/client/v2/tx/tx.go b/client/v2/tx/tx.go index a77bdbb55bd4..075da397fb83 100644 --- a/client/v2/tx/tx.go +++ b/client/v2/tx/tx.go @@ -7,6 +7,8 @@ import ( "fmt" "os" + "cosmossdk.io/core/address" + "github.com/cosmos/gogoproto/proto" "github.com/spf13/pflag" @@ -22,7 +24,7 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) -func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring) (params TxParameters, err error) { +func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring, ac address.Codec) (params TxParameters, err error) { timeout, _ := flags.GetUint64(flags2.FlagTimeoutHeight) chainID, _ := flags.GetString(flags2.FlagChainID) memo, _ := flags.GetString(flags2.FlagNote) @@ -30,7 +32,26 @@ func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring) (params accNumber, _ := flags.GetUint64(flags2.FlagAccountNumber) sequence, _ := flags.GetUint64(flags2.FlagSequence) - fromName, _ := flags.GetString(flags2.FlagFrom) + from, _ := flags.GetString(flags2.FlagFrom) + + var fromName, fromAddress string + var addr []byte + isDryRun, _ := flags.GetBool(flags2.FlagDryRun) + if !isDryRun { + fromName, fromAddress, _, err = keybase.KeyInfo(from) + if err != nil { + return params, err + } + addr, err = ac.StringToBytes(fromAddress) + if err != nil { + return params, err + } + } else { + addr, err = ac.StringToBytes(from) + if err != nil { + return params, err + } + } gas, _ := flags.GetString(flags2.FlagGas) gasSetting, _ := flags2.ParseGasSetting(gas) @@ -45,16 +66,6 @@ func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring) (params offline, _ := flags.GetBool(flags2.FlagOffline) generateOnly, _ := flags.GetBool(flags2.FlagGenerateOnly) - var fromAddr []byte - dryRun, _ := flags.GetBool(flags2.FlagDryRun) - if !dryRun { - acc, err := keybase.GetPubKey(fromName) - if err != nil { - return params, err - } - fromAddr = acc.Address().Bytes() - } - gasConfig, err := NewGasConfig(gasSetting.Gas, gasAdjustment, gasPrices) if err != nil { return params, err @@ -73,7 +84,8 @@ func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring) (params accountNumber: accNumber, sequence: sequence, fromName: fromName, - fromAddress: fromAddr, + fromAddress: fromAddress, + address: addr, }, GasConfig: gasConfig, FeeConfig: feeConfig, @@ -141,17 +153,11 @@ func GenerateOrBroadcastTxCLI(ctx client.Context, flagSet *pflag.FlagSet, msgs . return generateOnly(ctx, txf, msgs...) } - // Simulate - dryRun, _ := flagSet.GetBool(flags2.FlagDryRun) - if dryRun { - simulation, _, err := txf.Simulate(msgs...) - if err != nil { - return err - } - return ctx.PrintProto(simulation) + isDryRun, _ := flagSet.GetBool(flags2.FlagDryRun) + if isDryRun { + return dryRun(ctx, txf, msgs...) } - // Broadcast return BroadcastTx(ctx, txf, msgs...) } @@ -161,7 +167,7 @@ func newFactory(ctx client.Context, flagSet *pflag.FlagSet) (Factory, error) { return Factory{}, err } - params, err := txParamsFromFlagSet(flagSet, k) + params, err := txParamsFromFlagSet(flagSet, k, ctx.AddressCodec) if err != nil { return Factory{}, err } @@ -227,6 +233,20 @@ func generateOnly(ctx client.Context, txf Factory, msgs ...transaction.Msg) erro return ctx.PrintString(uTx) } +func dryRun(ctx client.Context, txf Factory, msgs ...transaction.Msg) error { + err := txf.Prepare() + if err != nil { + return err + } + + _, gas, err := txf.Simulate(msgs...) + if err != nil { + return err + } + _, err = fmt.Fprintf(os.Stderr, "%s\n", GasEstimateResponse{GasEstimate: gas}) + return err +} + // SimulateTx simulates a tx and returns the simulation response obtained by the query. func SimulateTx(ctx client.Context, flagSet *pflag.FlagSet, msgs ...transaction.Msg) (proto.Message, error) { txf, err := newFactory(ctx, flagSet) diff --git a/client/v2/tx/types.go b/client/v2/tx/types.go index cec27c8c659c..b9e86e4ba151 100644 --- a/client/v2/tx/types.go +++ b/client/v2/tx/types.go @@ -50,7 +50,8 @@ type AccountConfig struct { accountNumber uint64 sequence uint64 fromName string - fromAddress []byte + fromAddress string + address []byte } // GasConfig defines the 'gas' related fields in a transaction. diff --git a/crypto/keyring/autocli.go b/crypto/keyring/autocli.go index a9d78b378fd9..7867d6e8e800 100644 --- a/crypto/keyring/autocli.go +++ b/crypto/keyring/autocli.go @@ -2,13 +2,11 @@ package keyring import ( "cosmossdk.io/core/address" - "errors" signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1" authsigning "cosmossdk.io/x/auth/signing" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) // autoCLIKeyring represents the keyring interface used by the AutoCLI. @@ -28,6 +26,9 @@ type autoCLIKeyring interface { // KeyType returns the type of the key. KeyType(name string) (uint, error) + + // KeyInfo given a key name or address returns key name, key address and key type. + KeyInfo(name string) (string, string, uint, error) } // NewAutoCLIKeyring wraps the SDK keyring and make it compatible with the AutoCLI keyring interfaces. @@ -72,16 +73,6 @@ func (a *autoCLIKeyringAdapter) LookupAddressByKeyName(name string) ([]byte, err func (a *autoCLIKeyringAdapter) GetPubKey(name string) (cryptotypes.PubKey, error) { record, err := a.Keyring.Key(name) if err != nil { - if errors.Is(err, sdkerrors.ErrKeyNotFound) { - addr, err := a.ac.StringToBytes(name) - if err != nil { - return nil, err - } - record, err = a.Keyring.KeyByAddress(addr) - if err == nil { - return record.GetPubKey() - } - } return nil, err } @@ -111,3 +102,31 @@ func (a *autoCLIKeyringAdapter) KeyType(name string) (uint, error) { return uint(record.GetType()), nil } + +func (a *autoCLIKeyringAdapter) KeyInfo(nameOrAddr string) (string, string, uint, error) { + addr, err := a.ac.StringToBytes(nameOrAddr) + if err != nil { + // If conversion fails, it's likely a name, not an address + record, err := a.Keyring.Key(nameOrAddr) + if err != nil { + return "", "", 0, err + } + addr, err = record.GetAddress() + if err != nil { + return "", "", 0, err + } + addrStr, err := a.ac.BytesToString(addr) + if err != nil { + return "", "", 0, err + } + return record.Name, addrStr, uint(record.GetType()), nil + } + + // If conversion succeeds, it's an address, get the key info by address + record, err := a.Keyring.KeyByAddress(addr) + if err != nil { + return "", "", 0, err + } + + return record.Name, nameOrAddr, uint(record.GetType()), nil +} From 2e4895f4d306b01454ca39d2d1a30a96697d12f3 Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Wed, 19 Jun 2024 14:11:10 +0200 Subject: [PATCH 11/36] update: simplify accountconfig setter --- client/v2/tx/tx.go | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/client/v2/tx/tx.go b/client/v2/tx/tx.go index 075da397fb83..c5cdce79648e 100644 --- a/client/v2/tx/tx.go +++ b/client/v2/tx/tx.go @@ -37,21 +37,17 @@ func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring, ac addr var fromName, fromAddress string var addr []byte isDryRun, _ := flags.GetBool(flags2.FlagDryRun) - if !isDryRun { - fromName, fromAddress, _, err = keybase.KeyInfo(from) - if err != nil { - return params, err - } - addr, err = ac.StringToBytes(fromAddress) - if err != nil { - return params, err - } - } else { + if isDryRun { addr, err = ac.StringToBytes(from) - if err != nil { - return params, err + } else { + fromName, fromAddress, _, err = keybase.KeyInfo(from) + if err == nil { + addr, err = ac.StringToBytes(fromAddress) } } + if err != nil { + return params, err + } gas, _ := flags.GetString(flags2.FlagGas) gasSetting, _ := flags2.ParseGasSetting(gas) From 51ffeb2ef58de02b8250b3b3a21f8ae459309f8b Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Thu, 20 Jun 2024 11:16:36 +0200 Subject: [PATCH 12/36] fix: dry-run offline --- client/v2/autocli/common_test.go | 2 +- client/v2/tx/account.go | 3 +- client/v2/tx/aux_builder.go | 4 +-- client/v2/tx/builder.go | 4 +-- client/v2/tx/builder_test.go | 2 +- client/v2/tx/common_test.go | 8 ++--- client/v2/tx/factory.go | 36 ++++++++++++++++------ client/v2/tx/signature_test.go | 22 ++++++++++++-- client/v2/tx/tx.go | 52 ++++++++++---------------------- client/v2/tx/types.go | 2 +- 10 files changed, 72 insertions(+), 63 deletions(-) diff --git a/client/v2/autocli/common_test.go b/client/v2/autocli/common_test.go index 9e8613be07ca..7d99d6d17400 100644 --- a/client/v2/autocli/common_test.go +++ b/client/v2/autocli/common_test.go @@ -56,7 +56,7 @@ func initFixture(t *testing.T) *fixture { kr, err := sdkkeyring.New(sdk.KeyringServiceName(), sdkkeyring.BackendMemory, home, nil, encodingConfig.Codec) assert.NilError(t, err) - akr, err := sdkkeyring.NewAutoCLIKeyring(kr) + akr, err := sdkkeyring.NewAutoCLIKeyring(kr, testutil.CodecOptions{}.GetAddressCodec()) assert.NilError(t, err) interfaceRegistry := encodingConfig.Codec.InterfaceRegistry() diff --git a/client/v2/tx/account.go b/client/v2/tx/account.go index 8115bf024d3f..f95b232a34cf 100644 --- a/client/v2/tx/account.go +++ b/client/v2/tx/account.go @@ -3,7 +3,6 @@ package tx import ( "context" "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" "strconv" gogogrpc "github.com/cosmos/gogoproto/grpc" @@ -17,6 +16,7 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" ) // TODO: move to internal @@ -96,7 +96,6 @@ func (a accountRetriever) GetAccountWithHeight(ctx context.Context, addr []byte) } return acc, int64(nBlockHeight), nil - } func (a accountRetriever) EnsureExists(ctx context.Context, addr []byte) error { diff --git a/client/v2/tx/aux_builder.go b/client/v2/tx/aux_builder.go index dac6bfaba2d9..7f33e53d895d 100644 --- a/client/v2/tx/aux_builder.go +++ b/client/v2/tx/aux_builder.go @@ -172,7 +172,6 @@ func (b *AuxTxBuilder) SetNonCriticalExtensionOptions(extOpts ...*gogoany.Any) { b.auxSignerData.SignDoc.BodyBytes = nil } -// TODO: better place func validateSignDoc(sd *apitx.SignDocDirectAux) error { if len(sd.BodyBytes) == 0 { return errors.New("body bytes is empty") @@ -183,7 +182,6 @@ func validateSignDoc(sd *apitx.SignDocDirectAux) error { return nil } -// TODO: better place func validateAuxSignerData(a *apitx.AuxSignerData) error { if a.Address == "" { return errors.New("address cannot be empty: invalid request") @@ -223,7 +221,7 @@ func (b *AuxTxBuilder) GetSignBytes() ([]byte, error) { } sd.BodyBytes = bodyBz - if err = validateSignDoc(sd); err != nil { // TODO + if err = validateSignDoc(sd); err != nil { return nil, err } diff --git a/client/v2/tx/builder.go b/client/v2/tx/builder.go index bd84637a65a4..b3a8ead219da 100644 --- a/client/v2/tx/builder.go +++ b/client/v2/tx/builder.go @@ -20,7 +20,7 @@ var ( ) type ExtendedTxBuilder interface { - SetExtensionOptions(...*gogoany.Any) // TODO: sdk.Any? + SetExtensionOptions(...*gogoany.Any) } // TxBuilder defines an interface which an application-defined concrete transaction @@ -151,8 +151,6 @@ func (b *txBuilder) getFee() (fee *apitx.Fee, err error) { } } - // TODO: what if not fee payer nor granted are empty? - fee = &apitx.Fee{ Amount: b.fees, GasLimit: b.gasLimit, diff --git a/client/v2/tx/builder_test.go b/client/v2/tx/builder_test.go index 1446d32b013a..fc6889f6b9e5 100644 --- a/client/v2/tx/builder_test.go +++ b/client/v2/tx/builder_test.go @@ -516,7 +516,7 @@ func Test_txBuilder_SetSignatures(t *testing.T) { }, }, { - name: "set empty single signature", + name: "set single signature", signatures: func() []Signature { return []Signature{{ PubKey: secp256k1.GenPrivKey().PubKey(), diff --git a/client/v2/tx/common_test.go b/client/v2/tx/common_test.go index 667e24cd4e18..9c4ef259f3e6 100644 --- a/client/v2/tx/common_test.go +++ b/client/v2/tx/common_test.go @@ -88,19 +88,19 @@ func (m mockAccountRetriever) GetAccountWithHeight(_ context.Context, address [] return mockAccount{addr: address}, 0, nil } -func (m mockAccountRetriever) EnsureExists(_ context.Context, address []byte) error { +func (m mockAccountRetriever) EnsureExists(_ context.Context, _ []byte) error { return nil } -func (m mockAccountRetriever) GetAccountNumberSequence(_ context.Context, address []byte) (accNum, accSeq uint64, err error) { +func (m mockAccountRetriever) GetAccountNumberSequence(_ context.Context, _ []byte) (accNum, accSeq uint64, err error) { return accNum, accSeq, nil } type mockClientConn struct{} -func (m mockClientConn) Invoke(_ context.Context, _ string, args, reply interface{}, opts ...grpc.CallOption) error { +func (m mockClientConn) Invoke(_ context.Context, _ string, _, reply interface{}, _ ...grpc.CallOption) error { simResponse := apitx.SimulateResponse{ - GasInfo: &abciv1beta1.GasInfo{ // TODO: sdk dependency + GasInfo: &abciv1beta1.GasInfo{ GasWanted: 10000, GasUsed: 7500, }, diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index c9d4a4390cf8..9faa747329be 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -413,15 +413,6 @@ func (f *Factory) getSignBytesAdapter(ctx context.Context, signerData signing.Si return f.txConfig.SignModeHandler().GetSignBytes(ctx, f.SignMode(), signerData, *txData) } -func validateMemo(memo string) error { - // Prevent simple inclusion of a valid mnemonic in the memo field - if memo != "" && bip39.IsMnemonicValid(strings.ToLower(memo)) { - return errors.New("cannot provide a valid mnemonic seed in the memo field") - } - - return nil -} - // WithGas returns a copy of the Factory with an updated gas value. func (f *Factory) WithGas(gas uint64) { f.txParams.gas = gas @@ -507,3 +498,30 @@ func (f *Factory) getSimSignatureData(pk cryptotypes.PubKey) SignatureData { Signatures: multiSignatureData, } } + +// checkMultipleSigners checks that there can be maximum one DIRECT signer in +// a tx. +func checkMultipleSigners(tx Tx) error { + directSigners := 0 + sigsV2, err := tx.GetSignatures() + if err != nil { + return err + } + for _, sig := range sigsV2 { + directSigners += countDirectSigners(sig.Data) + if directSigners > 1 { + return errors.New("txs signed with CLI can have maximum 1 DIRECT signer") + } + } + + return nil +} + +func validateMemo(memo string) error { + // Prevent simple inclusion of a valid mnemonic in the memo field + if memo != "" && bip39.IsMnemonicValid(strings.ToLower(memo)) { + return errors.New("cannot provide a valid mnemonic seed in the memo field") + } + + return nil +} diff --git a/client/v2/tx/signature_test.go b/client/v2/tx/signature_test.go index bfc588f55e2b..2bd1cf2a03f5 100644 --- a/client/v2/tx/signature_test.go +++ b/client/v2/tx/signature_test.go @@ -69,7 +69,7 @@ func TestSignatureDataToModeInfoAndSig(t *testing.T) { func TestModeInfoAndSigToSignatureData(t *testing.T) { type args struct { - modeInfo *apitx.ModeInfo + modeInfo func() *apitx.ModeInfo sig []byte } tests := []struct { @@ -78,11 +78,27 @@ func TestModeInfoAndSigToSignatureData(t *testing.T) { want SignatureData wantErr bool }{ - // TODO: Add test cases. + { + name: "to SingleSignatureData", + args: args{ + modeInfo: func() *apitx.ModeInfo { + return &apitx.ModeInfo{ + Sum: &apitx.ModeInfo_Single_{ + Single: &apitx.ModeInfo_Single{Mode: apisigning.SignMode_SIGN_MODE_DIRECT}, + }, + } + }, + sig: []byte("signature"), + }, + want: &SingleSignatureData{ + SignMode: apisigning.SignMode_SIGN_MODE_DIRECT, + Signature: []byte("signature"), + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := ModeInfoAndSigToSignatureData(tt.args.modeInfo, tt.args.sig) + got, err := ModeInfoAndSigToSignatureData(tt.args.modeInfo(), tt.args.sig) if (err != nil) != tt.wantErr { t.Errorf("ModeInfoAndSigToSignatureData() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/client/v2/tx/tx.go b/client/v2/tx/tx.go index c5cdce79648e..c1f2b81690ef 100644 --- a/client/v2/tx/tx.go +++ b/client/v2/tx/tx.go @@ -7,21 +7,19 @@ import ( "fmt" "os" - "cosmossdk.io/core/address" - "github.com/cosmos/gogoproto/proto" "github.com/spf13/pflag" apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" apitx "cosmossdk.io/api/cosmos/tx/v1beta1" keyring2 "cosmossdk.io/client/v2/autocli/keyring" + "cosmossdk.io/core/address" "cosmossdk.io/core/transaction" "github.com/cosmos/cosmos-sdk/client" flags2 "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/input" "github.com/cosmos/cosmos-sdk/crypto/keyring" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring, ac address.Codec) (params TxParameters, err error) { @@ -123,14 +121,12 @@ func validate(flags *pflag.FlagSet) error { // GenerateOrBroadcastTxCLI will either generate and print an unsigned transaction // or sign it and broadcast it returning an error upon failure. -// TODO: remove the client.Context func GenerateOrBroadcastTxCLI(ctx client.Context, flagSet *pflag.FlagSet, msgs ...transaction.Msg) error { if err := validate(flagSet); err != nil { return err } - err := validateMessages(msgs...) - if err != nil { + if err := validateMessages(msgs...); err != nil { return err } @@ -151,7 +147,7 @@ func GenerateOrBroadcastTxCLI(ctx client.Context, flagSet *pflag.FlagSet, msgs . isDryRun, _ := flagSet.GetBool(flags2.FlagDryRun) if isDryRun { - return dryRun(ctx, txf, msgs...) + return dryRun(txf, msgs...) } return BroadcastTx(ctx, txf, msgs...) @@ -172,6 +168,7 @@ func newFactory(ctx client.Context, flagSet *pflag.FlagSet) (Factory, error) { AddressCodec: ctx.AddressCodec, Cdc: ctx.Codec, ValidatorAddressCodec: ctx.ValidatorAddressCodec, + // EnablesSignModes: ctx.TxConfig.SignModeHandler().SupportedModes(), }) if err != nil { return Factory{}, err @@ -212,7 +209,7 @@ func generateAuxSignerData(ctx client.Context, txf Factory, msgs ...transaction. return err } - return ctx.PrintString(auxSignerData.String()) + return ctx.PrintProto(auxSignerData) } func generateOnly(ctx client.Context, txf Factory, msgs ...transaction.Msg) error { @@ -229,7 +226,11 @@ func generateOnly(ctx client.Context, txf Factory, msgs ...transaction.Msg) erro return ctx.PrintString(uTx) } -func dryRun(ctx client.Context, txf Factory, msgs ...transaction.Msg) error { +func dryRun(txf Factory, msgs ...transaction.Msg) error { + if txf.txParams.offline { + return errors.New("dry-run: cannot use offline mode") + } + err := txf.Prepare() if err != nil { return err @@ -239,6 +240,7 @@ func dryRun(ctx client.Context, txf Factory, msgs ...transaction.Msg) error { if err != nil { return err } + _, err = fmt.Fprintf(os.Stderr, "%s\n", GasEstimateResponse{GasEstimate: gas}) return err } @@ -332,17 +334,13 @@ func BroadcastTx(clientCtx client.Context, txf Factory, msgs ...transaction.Msg) // makeAuxSignerData generates an AuxSignerData from the client inputs. func makeAuxSignerData(clientCtx client.Context, f Factory, msgs ...transaction.Msg) (*apitx.AuxSignerData, error) { b := NewAuxTxBuilder() - fromAddress, name, _, err := client.GetFromFields(clientCtx, clientCtx.Keyring, clientCtx.From) - if err != nil { - return nil, err - } - b.SetAddress(fromAddress.String()) + b.SetAddress(f.txParams.fromAddress) if f.txParams.offline { b.SetAccountNumber(f.AccountNumber()) b.SetSequence(f.Sequence()) } else { - accNum, seq, err := f.accountRetriever.GetAccountNumberSequence(context.Background(), fromAddress) + accNum, seq, err := f.accountRetriever.GetAccountNumberSequence(context.Background(), f.txParams.address) if err != nil { return nil, err } @@ -350,7 +348,7 @@ func makeAuxSignerData(clientCtx client.Context, f Factory, msgs ...transaction. b.SetSequence(seq) } - err = b.SetMsgs(msgs...) + err := b.SetMsgs(msgs...) if err != nil { return nil, err } @@ -360,7 +358,7 @@ func makeAuxSignerData(clientCtx client.Context, f Factory, msgs ...transaction. return nil, err } - pubKey, err := f.keybase.GetPubKey(name) + pubKey, err := f.keybase.GetPubKey(f.txParams.fromName) if err != nil { return nil, err } @@ -376,7 +374,7 @@ func makeAuxSignerData(clientCtx client.Context, f Factory, msgs ...transaction. return nil, err } - sig, err := f.keybase.Sign(name, signBz, f.SignMode()) + sig, err := f.keybase.Sign(f.txParams.fromName, signBz, f.SignMode()) if err != nil { return nil, err } @@ -385,24 +383,6 @@ func makeAuxSignerData(clientCtx client.Context, f Factory, msgs ...transaction. return b.GetAuxSignerData() } -// checkMultipleSigners checks that there can be maximum one DIRECT signer in -// a tx. -func checkMultipleSigners(tx Tx) error { - directSigners := 0 - sigsV2, err := tx.GetSignatures() - if err != nil { - return err - } - for _, sig := range sigsV2 { - directSigners += countDirectSigners(sig.Data) - if directSigners > 1 { - return sdkerrors.ErrNotSupported.Wrap("txs signed with CLI can have maximum 1 DIRECT signer") - } - } - - return nil -} - // countDirectSigners counts the number of DIRECT signers in a signature data. func countDirectSigners(sigData SignatureData) int { switch data := sigData.(type) { diff --git a/client/v2/tx/types.go b/client/v2/tx/types.go index b9e86e4ba151..affa9a5ec0e6 100644 --- a/client/v2/tx/types.go +++ b/client/v2/tx/types.go @@ -86,7 +86,7 @@ type FeeConfig struct { } func NewFeeConfig(fees, feePayer, feeGranter string) (FeeConfig, error) { - parsedFees, err := coins.ParseCoinsNormalized(fees) // TODO: do it here to avoid sdk dependency + parsedFees, err := coins.ParseCoinsNormalized(fees) if err != nil { return FeeConfig{}, err } From 189e6b3aaa7e4e8c294a621acd5f8ddcc5884afe Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Thu, 20 Jun 2024 11:52:04 +0200 Subject: [PATCH 13/36] add: godoc --- client/v2/tx/account.go | 24 +++++------ client/v2/tx/aux_builder.go | 3 ++ client/v2/tx/builder.go | 27 ++++++++++++ client/v2/tx/config.go | 44 ++++++++++++++++--- client/v2/tx/encoder.go | 15 +++++-- client/v2/tx/factory.go | 57 ++++++++++++++++++------- client/v2/tx/signature.go | 61 ++++++++++----------------- client/v2/tx/tx.go | 12 ++++++ client/v2/tx/types.go | 84 ++++++++++++++++++++++++------------- 9 files changed, 222 insertions(+), 105 deletions(-) diff --git a/client/v2/tx/account.go b/client/v2/tx/account.go index f95b232a34cf..21665a8fc625 100644 --- a/client/v2/tx/account.go +++ b/client/v2/tx/account.go @@ -19,16 +19,12 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// TODO: move to internal - -const ( - // GRPCBlockHeightHeader is the gRPC header for block height. - GRPCBlockHeightHeader = "x-cosmos-block-height" -) +// GRPCBlockHeightHeader represents the gRPC header for block height. +const GRPCBlockHeightHeader = "x-cosmos-block-height" var _ AccountRetriever = accountRetriever{} -// Account defines a read-only version of the auth module's AccountI. +// Account provides a read-only abstraction over the auth module's AccountI. type Account interface { GetAddress() sdk.AccAddress GetPubKey() cryptotypes.PubKey // can return nil. @@ -36,9 +32,7 @@ type Account interface { GetSequence() uint64 } -// AccountRetriever defines the interfaces required by transactions to -// ensure an account exists and to be able to query for account fields necessary -// for signing. +// AccountRetriever defines methods required to retrieve account details necessary for transaction signing. type AccountRetriever interface { GetAccount(context.Context, []byte) (Account, error) GetAccountWithHeight(context.Context, []byte) (Account, int64, error) @@ -52,6 +46,7 @@ type accountRetriever struct { registry codectypes.InterfaceRegistry } +// newAccountRetriever creates a new instance of accountRetriever. func newAccountRetriever(ac address.Codec, conn gogogrpc.ClientConn, registry codectypes.InterfaceRegistry) *accountRetriever { return &accountRetriever{ ac: ac, @@ -60,14 +55,15 @@ func newAccountRetriever(ac address.Codec, conn gogogrpc.ClientConn, registry co } } +// GetAccount retrieves an account using its address. func (a accountRetriever) GetAccount(ctx context.Context, addr []byte) (Account, error) { acc, _, err := a.GetAccountWithHeight(ctx, addr) return acc, err } +// GetAccountWithHeight retrieves an account and its associated block height using the account's address. func (a accountRetriever) GetAccountWithHeight(ctx context.Context, addr []byte) (Account, int64, error) { var header metadata.MD - qc := authtypes.NewQueryClient(a.conn) addrStr, err := a.ac.BytesToString(addr) @@ -81,8 +77,8 @@ func (a accountRetriever) GetAccountWithHeight(ctx context.Context, addr []byte) } blockHeight := header.Get(GRPCBlockHeightHeader) - if l := len(blockHeight); l != 1 { - return nil, 0, fmt.Errorf("unexpected '%s' header length; got %d, expected: %d", GRPCBlockHeightHeader, l, 1) + if len(blockHeight) != 1 { + return nil, 0, fmt.Errorf("unexpected '%s' header length; got %d, expected 1", GRPCBlockHeightHeader, len(blockHeight)) } nBlockHeight, err := strconv.Atoi(blockHeight[0]) @@ -98,6 +94,7 @@ func (a accountRetriever) GetAccountWithHeight(ctx context.Context, addr []byte) return acc, int64(nBlockHeight), nil } +// EnsureExists checks if an account exists using its address. func (a accountRetriever) EnsureExists(ctx context.Context, addr []byte) error { if _, err := a.GetAccount(ctx, addr); err != nil { return err @@ -105,6 +102,7 @@ func (a accountRetriever) EnsureExists(ctx context.Context, addr []byte) error { return nil } +// GetAccountNumberSequence retrieves the account number and sequence for an account using its address. func (a accountRetriever) GetAccountNumberSequence(ctx context.Context, addr []byte) (accNum, accSeq uint64, err error) { acc, err := a.GetAccount(ctx, addr) if err != nil { diff --git a/client/v2/tx/aux_builder.go b/client/v2/tx/aux_builder.go index 7f33e53d895d..fcb7b4c72448 100644 --- a/client/v2/tx/aux_builder.go +++ b/client/v2/tx/aux_builder.go @@ -172,6 +172,7 @@ func (b *AuxTxBuilder) SetNonCriticalExtensionOptions(extOpts ...*gogoany.Any) { b.auxSignerData.SignDoc.BodyBytes = nil } +// validateSignDoc validates that SignDocDirectAux is correctly set. func validateSignDoc(sd *apitx.SignDocDirectAux) error { if len(sd.BodyBytes) == 0 { return errors.New("body bytes is empty") @@ -182,6 +183,7 @@ func validateSignDoc(sd *apitx.SignDocDirectAux) error { return nil } +// validateAuxSignerData validates AuxSignerData is correctly set. func validateAuxSignerData(a *apitx.AuxSignerData) error { if a.Address == "" { return errors.New("address cannot be empty: invalid request") @@ -291,6 +293,7 @@ func (b *AuxTxBuilder) GetAuxSignerData() (*apitx.AuxSignerData, error) { return b.auxSignerData, nil } +// checkEmptyFields checks that body and auxSignerData are not empty and initializes them if so. func (b *AuxTxBuilder) checkEmptyFields() { if b.body == nil { b.body = &apitx.TxBody{} diff --git a/client/v2/tx/builder.go b/client/v2/tx/builder.go index b3a8ead219da..e345498c6c0d 100644 --- a/client/v2/tx/builder.go +++ b/client/v2/tx/builder.go @@ -42,17 +42,20 @@ type TxBuilder interface { SetSignatures(...Signature) error } +// TxBuilderProvider provides a TxBuilder. type TxBuilderProvider interface { NewTxBuilder() TxBuilder WrapTxBuilder(*apitx.Tx) (TxBuilder, error) } +// BuilderProvider implements TxBuilderProvider. type BuilderProvider struct { addressCodec address.Codec decoder Decoder codec codec.BinaryCodec } +// NewBuilderProvider BuilderProvider constructor. func NewBuilderProvider(addressCodec address.Codec, decoder Decoder, codec codec.BinaryCodec) *BuilderProvider { return &BuilderProvider{ addressCodec: addressCodec, @@ -61,6 +64,7 @@ func NewBuilderProvider(addressCodec address.Codec, decoder Decoder, codec codec } } +// NewTxBuilder TxBuilder constructor. func (b BuilderProvider) NewTxBuilder() TxBuilder { return newTxBuilder(b.addressCodec, b.decoder, b.codec) } @@ -102,6 +106,7 @@ func newTxBuilder(addressCodec address.Codec, decoder Decoder, codec codec.Binar } } +// GetTx converts txBuilder messages to V2 and returns a Tx. func (b *txBuilder) GetTx() (*apitx.Tx, error) { msgs, err := msgsV1toAnyV2(b.msgs) if err != nil { @@ -134,6 +139,9 @@ func (b *txBuilder) GetTx() (*apitx.Tx, error) { }, nil } +// getFee computes the transaction fee information for the txBuilder. +// It returns a pointer to an apitx.Fee struct containing the fee amount, gas limit, payer, and granter information. +// If the granter or payer addresses are set, it converts them from bytes to string using the addressCodec. func (b *txBuilder) getFee() (fee *apitx.Fee, err error) { granterStr := "" if b.granter != nil { @@ -161,6 +169,7 @@ func (b *txBuilder) getFee() (fee *apitx.Fee, err error) { return fee, nil } +// GetSigningTxData returns a TxData with the txBuilder info. func (b *txBuilder) GetSigningTxData() (*signing.TxData, error) { tx, err := b.GetTx() if err != nil { @@ -199,27 +208,33 @@ func (b *txBuilder) GetSigningTxData() (*signing.TxData, error) { }, nil } +// SetMsgs sets the messages for the transaction. func (b *txBuilder) SetMsgs(msgs ...transaction.Msg) error { b.msgs = msgs return nil } +// SetMemo sets the memo for the transaction. func (b *txBuilder) SetMemo(memo string) { b.memo = memo } +// SetFeeAmount sets the fee amount for the transaction. func (b *txBuilder) SetFeeAmount(coins []*base.Coin) { b.fees = coins } +// SetGasLimit sets the gas limit for the transaction. func (b *txBuilder) SetGasLimit(gasLimit uint64) { b.gasLimit = gasLimit } +// SetTimeoutHeight sets the timeout height for the transaction. func (b *txBuilder) SetTimeoutHeight(timeoutHeight uint64) { b.timeoutHeight = timeoutHeight } +// SetFeePayer sets the fee payer for the transaction. func (b *txBuilder) SetFeePayer(feePayer string) error { if feePayer == "" { return nil @@ -233,6 +248,10 @@ func (b *txBuilder) SetFeePayer(feePayer string) error { return nil } +// SetFeeGranter sets the fee granter's address in the transaction builder. +// If the feeGranter string is empty, the function returns nil without setting an address. +// It converts the feeGranter string to bytes using the address codec and sets it as the granter address. +// Returns an error if the conversion fails. func (b *txBuilder) SetFeeGranter(feeGranter string) error { if feeGranter == "" { return nil @@ -247,10 +266,14 @@ func (b *txBuilder) SetFeeGranter(feeGranter string) error { return nil } +// SetUnordered sets the unordered flag of the transaction builder. func (b *txBuilder) SetUnordered(unordered bool) { b.unordered = unordered } +// SetSignatures sets the signatures for the transaction builder. +// It takes a variable number of Signature arguments and processes each one to extract the mode information and raw signature. +// It also converts the public key to the appropriate format and sets the signer information. func (b *txBuilder) SetSignatures(signatures ...Signature) error { n := len(signatures) signerInfos := make([]*apitx.SignerInfo, n) @@ -289,6 +312,9 @@ func (b *txBuilder) SetSignatures(signatures ...Signature) error { return nil } +// msgsV1toAnyV2 converts a slice of transaction.Msg (v1) to a slice of anypb.Any (v2). +// It first converts each transaction.Msg into a codectypes.Any and then converts +// these into anypb.Any. func msgsV1toAnyV2(msgs []transaction.Msg) ([]*anypb.Any, error) { anys := make([]*codectypes.Any, len(msgs)) for i, msg := range msgs { @@ -302,6 +328,7 @@ func msgsV1toAnyV2(msgs []transaction.Msg) ([]*anypb.Any, error) { return intoAnyV2(anys), nil } +// intoAnyV2 converts a slice of codectypes.Any (v1) to a slice of anypb.Any (v2). func intoAnyV2(v1s []*codectypes.Any) []*anypb.Any { v2s := make([]*anypb.Any, len(v1s)) for i, v1 := range v1s { diff --git a/client/v2/tx/config.go b/client/v2/tx/config.go index 52fd855fa2bf..f01a6eaab617 100644 --- a/client/v2/tx/config.go +++ b/client/v2/tx/config.go @@ -29,31 +29,40 @@ var ( } ) -// TxConfig defines an interface a client can utilize to generate an -// application-defined concrete transaction type. The type returned must -// implement TxBuilder. +// TxConfig is an interface that a client can use to generate a concrete transaction type +// defined by the application. The returned type must implement the TxBuilder interface. type TxConfig interface { TxEncodingConfig TxSigningConfig TxBuilderProvider } -// TxEncodingConfig defines an interface that contains transaction -// encoders and decoders +// TxEncodingConfig defines the interface for transaction encoding and decoding. +// It provides methods for both binary and JSON encoding/decoding. type TxEncodingConfig interface { + // TxEncoder returns an encoder for binary transaction encoding. TxEncoder() txApiEncoder + // TxDecoder returns a decoder for binary transaction decoding. TxDecoder() txApiDecoder + // TxJSONEncoder returns an encoder for JSON transaction encoding. TxJSONEncoder() txApiEncoder + // TxJSONDecoder returns a decoder for JSON transaction decoding. TxJSONDecoder() txApiDecoder } +// TxSigningConfig defines the interface for transaction signing configurations. type TxSigningConfig interface { + // SignModeHandler returns a reference to the HandlerMap which manages the different signing modes. SignModeHandler() *signing.HandlerMap + // SigningContext returns a reference to the Context which holds additional data required during signing. SigningContext() *signing.Context + // MarshalSignatureJSON takes a slice of Signature objects and returns their JSON encoding. MarshalSignatureJSON([]Signature) ([]byte, error) + // UnmarshalSignatureJSON takes a JSON byte slice and returns a slice of Signature objects. UnmarshalSignatureJSON([]byte) ([]Signature, error) } +// ConfigOptions defines the configuration options for transaction processing. type ConfigOptions struct { AddressCodec address.Codec Decoder Decoder @@ -70,6 +79,8 @@ type ConfigOptions struct { TextualCoinMetadataQueryFn textual.CoinMetadataQueryFn } +// validate checks the ConfigOptions for required fields and sets default values where necessary. +// It returns an error if any required field is missing. func (c *ConfigOptions) validate() error { if c.AddressCodec == nil { return errors.New("address codec cannot be nil") @@ -81,19 +92,22 @@ func (c *ConfigOptions) validate() error { return errors.New("validator address codec cannot be nil") } - // set default signModes + // set default signModes if none are provided if len(c.EnablesSignModes) == 0 { c.EnablesSignModes = defaultEnabledSignModes } return nil } +// txConfig is a struct that embeds TxBuilderProvider, TxEncodingConfig, and TxSigningConfig interfaces. type txConfig struct { TxBuilderProvider TxEncodingConfig TxSigningConfig } +// NewTxConfig creates a new TxConfig instance using the provided ConfigOptions. +// It validates the options, initializes the signing context, and sets up the decoder if not provided. func NewTxConfig(options ConfigOptions) (TxConfig, error) { err := options.validate() if err != nil { @@ -121,29 +135,37 @@ func NewTxConfig(options ConfigOptions) (TxConfig, error) { }, nil } +// defaultEncodingConfig is an empty struct that implements the TxEncodingConfig interface. type defaultEncodingConfig struct{} +// TxEncoder returns the default transaction encoder. func (t defaultEncodingConfig) TxEncoder() txApiEncoder { return txEncoder } +// TxDecoder returns the default transaction decoder. func (t defaultEncodingConfig) TxDecoder() txApiDecoder { return txDecoder } +// TxJSONEncoder returns the default JSON transaction encoder. func (t defaultEncodingConfig) TxJSONEncoder() txApiEncoder { return txJsonEncoder } +// TxJSONDecoder returns the default JSON transaction decoder. func (t defaultEncodingConfig) TxJSONDecoder() txApiDecoder { return txJsonDecoder } +// defaultTxSigningConfig is a struct that holds the signing context and handler map. type defaultTxSigningConfig struct { signingCtx *signing.Context handlerMap *signing.HandlerMap } +// newDefaultTxSigningConfig creates a new defaultTxSigningConfig instance using the provided ConfigOptions. +// It initializes the signing context and handler map. func newDefaultTxSigningConfig(opts ConfigOptions) (*defaultTxSigningConfig, error) { signingCtx, err := newSigningContext(opts) if err != nil { @@ -161,24 +183,32 @@ func newDefaultTxSigningConfig(opts ConfigOptions) (*defaultTxSigningConfig, err }, nil } +// SignModeHandler returns the handler map that manages the different signing modes. func (t defaultTxSigningConfig) SignModeHandler() *signing.HandlerMap { return t.handlerMap } +// SigningContext returns the signing context that holds additional data required during signing. func (t defaultTxSigningConfig) SigningContext() *signing.Context { return t.signingCtx } +// MarshalSignatureJSON takes a slice of Signature objects and returns their JSON encoding. +// This method is not yet implemented and will panic if called. func (t defaultTxSigningConfig) MarshalSignatureJSON(signatures []Signature) ([]byte, error) { //TODO implement me panic("implement me") } +// UnmarshalSignatureJSON takes a JSON byte slice and returns a slice of Signature objects. +// This method is not yet implemented and will panic if called. func (t defaultTxSigningConfig) UnmarshalSignatureJSON(bytes []byte) ([]Signature, error) { //TODO implement me panic("implement me") } +// newSigningContext creates a new signing context using the provided ConfigOptions. +// Returns a signing.Context instance or an error if initialization fails. func newSigningContext(opts ConfigOptions) (*signing.Context, error) { return signing.NewContext(signing.Options{ FileResolver: opts.FileResolver, @@ -190,6 +220,8 @@ func newSigningContext(opts ConfigOptions) (*signing.Context, error) { }) } +// newHandlerMap constructs a new HandlerMap based on the provided ConfigOptions and signing context. +// It initializes handlers for each enabled and custom sign mode specified in the options. func newHandlerMap(opts ConfigOptions, signingCtx *signing.Context) (*signing.HandlerMap, error) { lenSignModes := len(opts.EnablesSignModes) handlers := make([]signing.SignModeHandler, lenSignModes+len(opts.CustomSignModes)) diff --git a/client/v2/tx/encoder.go b/client/v2/tx/encoder.go index 92fc55b08a76..4067c73f44da 100644 --- a/client/v2/tx/encoder.go +++ b/client/v2/tx/encoder.go @@ -9,7 +9,10 @@ import ( ) var ( - marshalOption = protov2.MarshalOptions{Deterministic: true} + // marshalOption configures protobuf marshaling to be deterministic. + marshalOption = protov2.MarshalOptions{Deterministic: true} + + // jsonMarshalOptions configures JSON marshaling for protobuf messages. jsonMarshalOptions = protojson.MarshalOptions{ Indent: "", UseProtoNames: true, @@ -17,31 +20,35 @@ var ( } ) -// Decoder decodes bytes into x/tx DecodedTx +// Decoder defines the interface for decoding transaction bytes into a DecodedTx. type Decoder interface { Decode(txBytes []byte) (*txdecode.DecodedTx, error) } -// txApiDecoder unmarshals transaction bytes into API Tx type +// txApiDecoder is a function type that unmarshals transaction bytes into an API Tx type. type txApiDecoder func(txBytes []byte) (*apitx.Tx, error) -// txApiEncoder marshals transaction to bytes +// txApiEncoder is a function type that marshals a transaction into bytes. type txApiEncoder func(tx *apitx.Tx) ([]byte, error) +// txDecoder decodes transaction bytes into an apitx.Tx structure. func txDecoder(txBytes []byte) (*apitx.Tx, error) { var tx apitx.Tx return &tx, protov2.Unmarshal(txBytes, &tx) } +// txEncoder encodes an apitx.Tx into bytes using protobuf marshaling options. func txEncoder(tx *apitx.Tx) ([]byte, error) { return marshalOption.Marshal(tx) } +// txJsonDecoder decodes transaction bytes into an apitx.Tx structure using JSON format. func txJsonDecoder(txBytes []byte) (*apitx.Tx, error) { var tx apitx.Tx return &tx, protojson.Unmarshal(txBytes, &tx) } +// txJsonEncoder encodes an apitx.Tx into bytes using JSON marshaling options. func txJsonEncoder(tx *apitx.Tx) ([]byte, error) { return jsonMarshalOptions.Marshal(tx) } diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index 9faa747329be..3686993d5acd 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -40,7 +40,7 @@ type Factory struct { txParams TxParameters } -// NewFactory returns a factory +// NewFactory returns a new instance of Factory. func NewFactory(keybase keyring.Keyring, cdc codec.BinaryCodec, accRetriever AccountRetriever, txConfig TxConfig, ac address.Codec, conn gogogrpc.ClientConn, parameters TxParameters) (Factory, error) { return Factory{ keybase: keybase, @@ -163,6 +163,7 @@ func (f *Factory) BuildUnsignedTx(msgs ...transaction.Msg) (TxBuilder, error) { return txBuilder, nil } +// calculateGas calculates the gas required for the given messages. func (f *Factory) calculateGas(msgs ...transaction.Msg) error { if f.txParams.offline { return errors.New("cannot simulate in offline mode") @@ -443,20 +444,47 @@ func (f *Factory) PreprocessTx(keyname string, builder TxBuilder) error { return f.txParams.preprocessTxHook(f.txParams.chainID, keyType, builder) } -func (f *Factory) AccountNumber() uint64 { return f.txParams.accountNumber } -func (f *Factory) Sequence() uint64 { return f.txParams.sequence } -func (f *Factory) Gas() uint64 { return f.txParams.gas } -func (f *Factory) GasAdjustment() float64 { return f.txParams.gasAdjustment } -func (f *Factory) Keybase() keyring.Keyring { return f.keybase } -func (f *Factory) ChainID() string { return f.txParams.chainID } -func (f *Factory) Memo() string { return f.txParams.memo } -func (f *Factory) Fees() []*base.Coin { return f.txParams.fees } -func (f *Factory) GasPrices() []*base.DecCoin { return f.txParams.gasPrices } +// AccountNumber returns the account number. +func (f *Factory) AccountNumber() uint64 { return f.txParams.accountNumber } + +// Sequence returns the sequence number. +func (f *Factory) Sequence() uint64 { return f.txParams.sequence } + +// Gas returns the gas value. +func (f *Factory) Gas() uint64 { return f.txParams.gas } + +// GasAdjustment returns the gas adjustment value. +func (f *Factory) GasAdjustment() float64 { return f.txParams.gasAdjustment } + +// Keybase returns the keyring. +func (f *Factory) Keybase() keyring.Keyring { return f.keybase } + +// ChainID returns the chain ID. +func (f *Factory) ChainID() string { return f.txParams.chainID } + +// Memo returns the memo. +func (f *Factory) Memo() string { return f.txParams.memo } + +// Fees returns the fees. +func (f *Factory) Fees() []*base.Coin { return f.txParams.fees } + +// GasPrices returns the gas prices. +func (f *Factory) GasPrices() []*base.DecCoin { return f.txParams.gasPrices } + +// AccountRetriever returns the account retriever. func (f *Factory) AccountRetriever() AccountRetriever { return f.accountRetriever } -func (f *Factory) TimeoutHeight() uint64 { return f.txParams.timeoutHeight } -func (f *Factory) FromName() string { return f.txParams.fromName } -func (f *Factory) SimulateAndExecute() bool { return f.txParams.simulateAndExecute } -func (f *Factory) SignMode() apitxsigning.SignMode { return f.txParams.signMode } + +// TimeoutHeight returns the timeout height. +func (f *Factory) TimeoutHeight() uint64 { return f.txParams.timeoutHeight } + +// FromName returns the from name. +func (f *Factory) FromName() string { return f.txParams.fromName } + +// SimulateAndExecute returns whether to simulate and execute. +func (f *Factory) SimulateAndExecute() bool { return f.txParams.simulateAndExecute } + +// SignMode returns the sign mode. +func (f *Factory) SignMode() apitxsigning.SignMode { return f.txParams.signMode } // getSimPK gets the public key to use for building a simulation tx. // Note, we should only check for keys in the keybase if we are in simulate and execute mode, @@ -517,6 +545,7 @@ func checkMultipleSigners(tx Tx) error { return nil } +// validateMemo validates the memo field. func validateMemo(memo string) error { // Prevent simple inclusion of a valid mnemonic in the memo field if memo != "" && bip39.IsMnemonicValid(strings.ToLower(memo)) { diff --git a/client/v2/tx/signature.go b/client/v2/tx/signature.go index 1c31d066421c..ac9a03d253e1 100644 --- a/client/v2/tx/signature.go +++ b/client/v2/tx/signature.go @@ -11,46 +11,34 @@ import ( cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" ) +// Signature holds the necessary components to verify transaction signatures. type Signature struct { - // PubKey is the public key to use for verifying the signature - PubKey cryptotypes.PubKey - - // Data is the actual data of the signature which includes SignMode's and - // the signatures themselves for either single or multi-signatures. - Data SignatureData - - // Sequence is the sequence of this account. Only populated in - // SIGN_MODE_DIRECT. - Sequence uint64 + PubKey cryptotypes.PubKey // Public key for signature verification. + Data SignatureData // Signature data containing the actual signatures. + Sequence uint64 // Account sequence, relevant for SIGN_MODE_DIRECT. } +// SignatureData defines an interface for different signature data types. type SignatureData interface { isSignatureData() } -// SingleSignatureData represents the signature and SignMode of a single (non-multisig) signer +// SingleSignatureData stores a single signer's signature and its mode. type SingleSignatureData struct { - // SignMode represents the SignMode of the signature - SignMode apitxsigning.SignMode - - // Signature is the raw signature. - Signature []byte + SignMode apitxsigning.SignMode // Mode of the signature. + Signature []byte // Actual binary signature. } -// MultiSignatureData represents the nested SignatureData of a multisig signature +// MultiSignatureData encapsulates signatures from a multisig transaction. type MultiSignatureData struct { - // BitArray is a compact way of indicating which signers from the multisig key - // have signed - BitArray *apicrypto.CompactBitArray - - // Signatures is the nested SignatureData's for each signer - Signatures []SignatureData + BitArray *apicrypto.CompactBitArray // Bitmap of signers. + Signatures []SignatureData // Individual signatures. } func (m *SingleSignatureData) isSignatureData() {} func (m *MultiSignatureData) isSignatureData() {} -// SignatureDataToModeInfoAndSig converts a SignatureData to a ModeInfo and raw bytes signature +// SignatureDataToModeInfoAndSig converts SignatureData to ModeInfo and its corresponding raw signature. func SignatureDataToModeInfoAndSig(data SignatureData) (*apitx.ModeInfo, []byte) { if data == nil { return nil, nil @@ -64,17 +52,14 @@ func SignatureDataToModeInfoAndSig(data SignatureData) (*apitx.ModeInfo, []byte) }, }, data.Signature case *MultiSignatureData: - n := len(data.Signatures) - modeInfos := make([]*apitx.ModeInfo, n) - sigs := make([][]byte, n) + modeInfos := make([]*apitx.ModeInfo, len(data.Signatures)) + sigs := make([][]byte, len(data.Signatures)) for i, d := range data.Signatures { modeInfos[i], sigs[i] = SignatureDataToModeInfoAndSig(d) } - multisig := cryptotypes.MultiSignature{ - Signatures: sigs, - } + multisig := cryptotypes.MultiSignature{Signatures: sigs} sig, err := multisig.Marshal() if err != nil { panic(err) @@ -93,6 +78,7 @@ func SignatureDataToModeInfoAndSig(data SignatureData) (*apitx.ModeInfo, []byte) } } +// ModeInfoAndSigToSignatureData converts ModeInfo and a raw signature to SignatureData. func ModeInfoAndSigToSignatureData(modeInfo *apitx.ModeInfo, sig []byte) (SignatureData, error) { switch mi := modeInfo.Sum.(type) { case *apitx.ModeInfo_Single_: @@ -115,20 +101,17 @@ func ModeInfoAndSigToSignatureData(modeInfo *apitx.ModeInfo, sig []byte) (Signat if err != nil { return nil, err } - - return &MultiSignatureData{ - BitArray: &apicrypto.CompactBitArray{ - ExtraBitsStored: multi.Bitarray.GetExtraBitsStored(), - Elems: multi.Bitarray.GetElems(), - }, - Signatures: sigsV2, - }, nil } + return &MultiSignatureData{ + BitArray: multi.Bitarray, + Signatures: sigsV2, + }, nil } return nil, fmt.Errorf("unsupported ModeInfo type %T", modeInfo) } +// decodeMultiSignatures decodes a byte array into individual signatures. func decodeMultiSignatures(bz []byte) ([][]byte, error) { multisig := cryptotypes.MultiSignature{} @@ -138,7 +121,7 @@ func decodeMultiSignatures(bz []byte) ([][]byte, error) { } if len(multisig.XXX_unrecognized) > 0 { - return nil, errors.New("rejecting unrecognized fields found in MultiSignature") + return nil, errors.New("unrecognized fields in MultiSignature") } return multisig.Signatures, nil } diff --git a/client/v2/tx/tx.go b/client/v2/tx/tx.go index c1f2b81690ef..a7ee38e3d6f4 100644 --- a/client/v2/tx/tx.go +++ b/client/v2/tx/tx.go @@ -22,6 +22,7 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keyring" ) +// txParamsFromFlagSet extracts the transaction parameters from the provided FlagSet. func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring, ac address.Codec) (params TxParameters, err error) { timeout, _ := flags.GetUint64(flags2.FlagTimeoutHeight) chainID, _ := flags.GetString(flags2.FlagChainID) @@ -97,6 +98,7 @@ func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring, ac addr return txParams, nil } +// validate checks the provided flags for consistency and requirements based on the operation mode. func validate(flags *pflag.FlagSet) error { offline, _ := flags.GetBool(flags2.FlagOffline) if offline { @@ -153,6 +155,9 @@ func GenerateOrBroadcastTxCLI(ctx client.Context, flagSet *pflag.FlagSet, msgs . return BroadcastTx(ctx, txf, msgs...) } +// newFactory creates a new transaction Factory based on the provided context and flag set. +// It initializes a new CLI keyring, extracts transaction parameters from the flag set, +// configures transaction settings, and sets up an account retriever for the transaction Factory. func newFactory(ctx client.Context, flagSet *pflag.FlagSet) (Factory, error) { k, err := keyring.NewAutoCLIKeyring(ctx.Keyring, ctx.AddressCodec) if err != nil { @@ -175,6 +180,7 @@ func newFactory(ctx client.Context, flagSet *pflag.FlagSet) (Factory, error) { } accRetriever := newAccountRetriever(ctx.AddressCodec, ctx, ctx.InterfaceRegistry) + txf, err := NewFactory(k, ctx.Codec, accRetriever, txConfig, ctx.AddressCodec, ctx, params) if err != nil { return Factory{}, err @@ -212,6 +218,9 @@ func generateAuxSignerData(ctx client.Context, txf Factory, msgs ...transaction. return ctx.PrintProto(auxSignerData) } +// generateOnly prepares the transaction and prints the unsigned transaction string. +// It first calls Prepare on the transaction factory to set up any necessary pre-conditions. +// If preparation is successful, it generates an unsigned transaction string using the provided messages. func generateOnly(ctx client.Context, txf Factory, msgs ...transaction.Msg) error { err := txf.Prepare() if err != nil { @@ -226,6 +235,8 @@ func generateOnly(ctx client.Context, txf Factory, msgs ...transaction.Msg) erro return ctx.PrintString(uTx) } +// dryRun performs a dry run of the transaction to estimate the gas required. +// It prepares the transaction factory and simulates the transaction with the provided messages. func dryRun(txf Factory, msgs ...transaction.Msg) error { if txf.txParams.offline { return errors.New("dry-run: cannot use offline mode") @@ -404,6 +415,7 @@ func countDirectSigners(sigData SignatureData) int { } } +// getSignMode returns the corresponding apitxsigning.SignMode based on the provided mode string. func getSignMode(mode string) apitxsigning.SignMode { switch mode { case "direct": diff --git a/client/v2/tx/types.go b/client/v2/tx/types.go index affa9a5ec0e6..68f7ba7893f6 100644 --- a/client/v2/tx/types.go +++ b/client/v2/tx/types.go @@ -32,35 +32,45 @@ type HasValidateBasic interface { ValidateBasic() error } +// TxParameters defines the parameters required for constructing a transaction. type TxParameters struct { - timeoutHeight uint64 - chainID string - memo string - signMode apitxsigning.SignMode + timeoutHeight uint64 // timeoutHeight indicates the block height after which the transaction is no longer valid. + chainID string // chainID specifies the unique identifier of the blockchain where the transaction will be processed. + memo string // memo contains any arbitrary memo to be attached to the transaction. + signMode apitxsigning.SignMode // signMode determines the signing mode to be used for the transaction. - AccountConfig - GasConfig - FeeConfig - ExecutionOptions - ExtensionOptions + AccountConfig // AccountConfig includes information about the transaction originator's account. + GasConfig // GasConfig specifies the gas settings for the transaction. + FeeConfig // FeeConfig details the fee associated with the transaction. + ExecutionOptions // ExecutionOptions includes settings that modify how the transaction is executed. + ExtensionOptions // ExtensionOptions allows for additional features or data to be included in the transaction. } // AccountConfig defines the 'account' related fields in a transaction. type AccountConfig struct { + // accountNumber is the unique identifier for the account. accountNumber uint64 - sequence uint64 - fromName string - fromAddress string - address []byte + // sequence is the sequence number of the transaction. + sequence uint64 + // fromName is the name of the account sending the transaction. + fromName string + // fromAddress is the address of the account sending the transaction. + fromAddress string + // address is the byte representation of the account address. + address []byte } // GasConfig defines the 'gas' related fields in a transaction. +// GasConfig defines the gas-related settings for a transaction. type GasConfig struct { - gas uint64 - gasAdjustment float64 - gasPrices []*base.DecCoin + gas uint64 // gas is the amount of gas requested for the transaction. + gasAdjustment float64 // gasAdjustment is the factor by which the estimated gas is multiplied to calculate the final gas limit. + gasPrices []*base.DecCoin // gasPrices is a list of denominations of DecCoin used to calculate the fee paid for the gas. } +// NewGasConfig creates a new GasConfig with the specified gas, gasAdjustment, and gasPrices. +// If the provided gas value is zero, it defaults to a predefined value (defaultGas). +// The gasPrices string is parsed into a slice of DecCoin. func NewGasConfig(gas uint64, gasAdjustment float64, gasPrices string) (GasConfig, error) { if gas == 0 { gas = defaultGas @@ -78,13 +88,15 @@ func NewGasConfig(gas uint64, gasAdjustment float64, gasPrices string) (GasConfi }, nil } -// FeeConfig defines the 'fee' related fields in a transaction. +// FeeConfig holds the fee details for a transaction. type FeeConfig struct { - fees []*base.Coin - feePayer string - feeGranter string + fees []*base.Coin // fees are the amounts paid for the transaction. + feePayer string // feePayer is the account responsible for paying the fees. + feeGranter string // feeGranter is the account granting the fee payment if different from the payer. } +// NewFeeConfig creates a new FeeConfig with the specified fees, feePayer, and feeGranter. +// It parses the fees string into a slice of Coin, handling normalization. func NewFeeConfig(fees, feePayer, feeGranter string) (FeeConfig, error) { parsedFees, err := coins.ParseCoinsNormalized(fees) if err != nil { @@ -99,17 +111,20 @@ func NewFeeConfig(fees, feePayer, feeGranter string) (FeeConfig, error) { } // ExecutionOptions defines the transaction execution options ran by the client +// ExecutionOptions defines the settings for transaction execution. type ExecutionOptions struct { - unordered bool - offline bool - offChain bool - generateOnly bool - simulateAndExecute bool - preprocessTxHook PreprocessTxFn + unordered bool // unordered indicates if the transaction execution order is not guaranteed. + offline bool // offline specifies if the transaction should be prepared for offline signing. + offChain bool // offChain indicates if the transaction should be executed off the blockchain. + generateOnly bool // generateOnly specifies if the transaction should only be generated and not executed. + simulateAndExecute bool // simulateAndExecute indicates if the transaction should be simulated before execution. + preprocessTxHook PreprocessTxFn // preprocessTxHook is a function hook for preprocessing the transaction. } +// ExtensionOptions holds a slice of Any protocol buffer messages that can be used to extend the functionality +// of a transaction with additional data. This is typically used to include non-standard or experimental features. type ExtensionOptions struct { - ExtOptions []*gogoany.Any + ExtOptions []*gogoany.Any // ExtOptions are the extension options in the form of Any protocol buffer messages. } // GasEstimateResponse defines a response definition for tx gas estimation. @@ -121,26 +136,35 @@ func (gr GasEstimateResponse) String() string { return fmt.Sprintf("gas estimate: %d", gr.GasEstimate) } +// Tx defines the interface for transaction operations. type Tx interface { + // GetMsgs retrieves the messages included in the transaction. GetMsgs() ([]transaction.Msg, error) + // GetSigners fetches the addresses of the signers of the transaction. GetSigners() ([][]byte, error) + // GetPubKeys retrieves the public keys of the signers of the transaction. GetPubKeys() ([]cryptotypes.PubKey, error) + // GetSignatures fetches the signatures attached to the transaction. GetSignatures() ([]Signature, error) } +// wrappedTx wraps a transaction and provides a codec for binary encoding/decoding. type wrappedTx struct { - tx *apitx.Tx - cdc codec.BinaryCodec + tx *apitx.Tx // tx is the transaction being wrapped. + cdc codec.BinaryCodec // cdc is the codec used for binary encoding/decoding. } +// GetMsgs retrieves the messages included in the transaction. func (w wrappedTx) GetMsgs() ([]transaction.Msg, error) { return nil, errors.New("not implemented") } +// GetSigners fetches the addresses of the signers of the transaction. func (w wrappedTx) GetSigners() ([][]byte, error) { return nil, errors.New("not implemented") } +// GetPubKeys retrieves the public keys of the signers from the transaction's SignerInfos. func (w wrappedTx) GetPubKeys() ([]cryptotypes.PubKey, error) { signerInfos := w.tx.AuthInfo.SignerInfos pks := make([]cryptotypes.PubKey, len(signerInfos)) @@ -165,6 +189,7 @@ func (w wrappedTx) GetPubKeys() ([]cryptotypes.PubKey, error) { return pks, nil } +// GetSignatures fetches the signatures attached to the transaction. func (w wrappedTx) GetSignatures() ([]Signature, error) { signerInfos := w.tx.AuthInfo.SignerInfos sigs := w.tx.Signatures @@ -196,6 +221,7 @@ func (w wrappedTx) GetSignatures() ([]Signature, error) { return signatures, nil } +// decodeAny decodes a protobuf Any message into a concrete proto.Message. func (w wrappedTx) decodeAny(anyPb *anypb.Any) (proto.Message, error) { name := anyPb.GetTypeUrl() if i := strings.LastIndexByte(name, '/'); i >= 0 { From 4fadeff0c56e86ca279c7fe8ba6e916f3efa4e63 Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Thu, 20 Jun 2024 12:04:34 +0200 Subject: [PATCH 14/36] lint --- client/v2/internal/grpc/client.go | 1 - client/v2/tx/aux_builder_test.go | 4 ++-- client/v2/tx/builder_test.go | 7 +++++-- client/v2/tx/common_test.go | 6 +++--- client/v2/tx/config.go | 7 ++++--- client/v2/tx/encoder_test.go | 6 ++++-- crypto/keyring/autocli.go | 3 +-- 7 files changed, 19 insertions(+), 15 deletions(-) diff --git a/client/v2/internal/grpc/client.go b/client/v2/internal/grpc/client.go index db671e450b55..ef671260075c 100644 --- a/client/v2/internal/grpc/client.go +++ b/client/v2/internal/grpc/client.go @@ -13,7 +13,6 @@ var _ gogogrpc.ClientConn = ClientConn{} type ClientConn struct{} func (c ClientConn) Invoke(ctx context.Context, method string, args, reply interface{}, opts ...grpc.CallOption) error { - //TODO implement me panic("implement me") } diff --git a/client/v2/tx/aux_builder_test.go b/client/v2/tx/aux_builder_test.go index e22cdcb8f58b..5ea22d47fcb7 100644 --- a/client/v2/tx/aux_builder_test.go +++ b/client/v2/tx/aux_builder_test.go @@ -1,15 +1,15 @@ package tx_test import ( - "cosmossdk.io/core/transaction" - "google.golang.org/protobuf/types/known/anypb" "testing" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/anypb" apisigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" apitx "cosmossdk.io/api/cosmos/tx/v1beta1" "cosmossdk.io/client/v2/tx" + "cosmossdk.io/core/transaction" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec/testutil" diff --git a/client/v2/tx/builder_test.go b/client/v2/tx/builder_test.go index fc6889f6b9e5..7986ce8ece9d 100644 --- a/client/v2/tx/builder_test.go +++ b/client/v2/tx/builder_test.go @@ -129,7 +129,8 @@ func Test_txBuilder_GetTx(t *testing.T) { TimeoutHeight: 0, Unordered: false, ExtensionOptions: nil, - NonCriticalExtensionOptions: nil}) { + NonCriticalExtensionOptions: nil, + }) { return false } if !reflect.DeepEqual(tx.AuthInfo, &apitx.AuthInfo{ @@ -138,7 +139,9 @@ func Test_txBuilder_GetTx(t *testing.T) { Amount: nil, GasLimit: 0, Payer: "", - Granter: ""}}) { + Granter: "", + }, + }) { return false } if tx.Signatures != nil { diff --git a/client/v2/tx/common_test.go b/client/v2/tx/common_test.go index 9c4ef259f3e6..6a07cb83a708 100644 --- a/client/v2/tx/common_test.go +++ b/client/v2/tx/common_test.go @@ -2,12 +2,11 @@ package tx import ( "context" - abciv1beta1 "cosmossdk.io/api/cosmos/base/abci/v1beta1" - apitx "cosmossdk.io/api/cosmos/tx/v1beta1" - "github.com/cosmos/cosmos-sdk/types" "google.golang.org/grpc" + abciv1beta1 "cosmossdk.io/api/cosmos/base/abci/v1beta1" + apitx "cosmossdk.io/api/cosmos/tx/v1beta1" "cosmossdk.io/client/v2/autocli/keyring" txdecode "cosmossdk.io/x/tx/decode" "cosmossdk.io/x/tx/signing" @@ -19,6 +18,7 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/hd" cryptoKeyring "github.com/cosmos/cosmos-sdk/crypto/keyring" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/types" ) var ( diff --git a/client/v2/tx/config.go b/client/v2/tx/config.go index f01a6eaab617..f628c65cfbeb 100644 --- a/client/v2/tx/config.go +++ b/client/v2/tx/config.go @@ -122,7 +122,8 @@ func NewTxConfig(options ConfigOptions) (TxConfig, error) { if options.Decoder == nil { options.Decoder, err = txdecode.NewDecoder(txdecode.Options{ SigningContext: signingCtx.SigningContext(), - ProtoCodec: options.Cdc}) + ProtoCodec: options.Cdc, + }) if err != nil { return nil, err } @@ -196,14 +197,14 @@ func (t defaultTxSigningConfig) SigningContext() *signing.Context { // MarshalSignatureJSON takes a slice of Signature objects and returns their JSON encoding. // This method is not yet implemented and will panic if called. func (t defaultTxSigningConfig) MarshalSignatureJSON(signatures []Signature) ([]byte, error) { - //TODO implement me + // TODO implement me panic("implement me") } // UnmarshalSignatureJSON takes a JSON byte slice and returns a slice of Signature objects. // This method is not yet implemented and will panic if called. func (t defaultTxSigningConfig) UnmarshalSignatureJSON(bytes []byte) ([]Signature, error) { - //TODO implement me + // TODO implement me panic("implement me") } diff --git a/client/v2/tx/encoder_test.go b/client/v2/tx/encoder_test.go index 748886c6cb61..63cdd9ab3a9a 100644 --- a/client/v2/tx/encoder_test.go +++ b/client/v2/tx/encoder_test.go @@ -1,10 +1,12 @@ package tx import ( - apitx "cosmossdk.io/api/cosmos/tx/v1beta1" + "testing" + "github.com/stretchr/testify/require" "google.golang.org/protobuf/types/known/anypb" - "testing" + + apitx "cosmossdk.io/api/cosmos/tx/v1beta1" ) func Test_txEncoder_txDecoder(t *testing.T) { diff --git a/crypto/keyring/autocli.go b/crypto/keyring/autocli.go index 7867d6e8e800..1cb4d7ca0443 100644 --- a/crypto/keyring/autocli.go +++ b/crypto/keyring/autocli.go @@ -1,9 +1,8 @@ package keyring import ( - "cosmossdk.io/core/address" - signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + "cosmossdk.io/core/address" authsigning "cosmossdk.io/x/auth/signing" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" From c2b2539b9a829e23112fa9414698475ab7ebdfc0 Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Fri, 21 Jun 2024 12:52:05 +0200 Subject: [PATCH 15/36] add: SignaturesMarshal --- client/v2/autocli/flag/address.go | 8 +- client/v2/autocli/keyring/keyring.go | 10 ++ client/v2/tx/builder.go | 1 + client/v2/tx/config.go | 76 +++++++++++++-- client/v2/tx/config_test.go | 133 +++++++++++++++++++++++++++ client/v2/tx/encoder.go | 5 +- client/v2/tx/factory.go | 17 +++- client/v2/tx/factory_test.go | 2 - client/v2/tx/signature.go | 70 ++++++++++++++ client/v2/tx/signature_test.go | 32 +++++++ 10 files changed, 332 insertions(+), 22 deletions(-) diff --git a/client/v2/autocli/flag/address.go b/client/v2/autocli/flag/address.go index 58108d094990..b9eb54120203 100644 --- a/client/v2/autocli/flag/address.go +++ b/client/v2/autocli/flag/address.go @@ -55,7 +55,7 @@ func (a addressValue) String() string { // Set implements the flag.Value interface for addressValue. func (a *addressValue) Set(s string) error { // we get the keyring on set, as in NewValue the context is the parent context (before RunE) - keyring := getKeyringFromCtx(a.ctx) + keyring := getKeyringFromCtx(a.ctx, a.addressCodec) addr, err := keyring.LookupAddressByKeyName(s) if err == nil { addrStr, err := a.addressCodec.BytesToString(addr) @@ -110,7 +110,7 @@ func (a consensusAddressValue) String() string { func (a *consensusAddressValue) Set(s string) error { // we get the keyring on set, as in NewValue the context is the parent context (before RunE) - keyring := getKeyringFromCtx(a.ctx) + keyring := getKeyringFromCtx(a.ctx, a.addressCodec) addr, err := keyring.LookupAddressByKeyName(s) if err == nil { addrStr, err := a.addressCodec.BytesToString(addr) @@ -147,11 +147,11 @@ func (a *consensusAddressValue) Set(s string) error { return nil } -func getKeyringFromCtx(ctx *context.Context) keyring.Keyring { +func getKeyringFromCtx(ctx *context.Context, ac address.Codec) keyring.Keyring { dctx := *ctx if dctx != nil { if clientCtx := dctx.Value(client.ClientContextKey); clientCtx != nil { - k, err := sdkkeyring.NewAutoCLIKeyring(clientCtx.(*client.Context).Keyring) + k, err := sdkkeyring.NewAutoCLIKeyring(clientCtx.(*client.Context).Keyring, ac) if err != nil { panic(fmt.Errorf("failed to create keyring: %w", err)) } diff --git a/client/v2/autocli/keyring/keyring.go b/client/v2/autocli/keyring/keyring.go index a838b12d8455..73bf89d54f45 100644 --- a/client/v2/autocli/keyring/keyring.go +++ b/client/v2/autocli/keyring/keyring.go @@ -46,3 +46,13 @@ func (k *KeyringImpl) LookupAddressByKeyName(name string) ([]byte, error) { func (k *KeyringImpl) Sign(name string, msg []byte, signMode signingv1beta1.SignMode) ([]byte, error) { return k.k.Sign(name, msg, signMode) } + +// KeyType returns the type of the key. +func (k *KeyringImpl) KeyType(name string) (uint, error) { + return k.k.KeyType(name) +} + +// KeyInfo given a key name or address returns key name, key address and key type. +func (k *KeyringImpl) KeyInfo(nameOrAddr string) (string, string, uint, error) { + return k.k.KeyInfo(nameOrAddr) +} diff --git a/client/v2/tx/builder.go b/client/v2/tx/builder.go index e345498c6c0d..e2c2759c059c 100644 --- a/client/v2/tx/builder.go +++ b/client/v2/tx/builder.go @@ -19,6 +19,7 @@ var ( _ TxBuilderProvider = BuilderProvider{} ) +// ExtendedTxBuilder defines an interface for setting extension options in a transaction. type ExtendedTxBuilder interface { SetExtensionOptions(...*gogoany.Any) } diff --git a/client/v2/tx/config.go b/client/v2/tx/config.go index f628c65cfbeb..7f1e33ad1a1c 100644 --- a/client/v2/tx/config.go +++ b/client/v2/tx/config.go @@ -1,10 +1,6 @@ package tx import ( - "errors" - - "google.golang.org/protobuf/reflect/protoreflect" - apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" "cosmossdk.io/core/address" txdecode "cosmossdk.io/x/tx/decode" @@ -13,6 +9,12 @@ import ( "cosmossdk.io/x/tx/signing/direct" "cosmossdk.io/x/tx/signing/directaux" "cosmossdk.io/x/tx/signing/textual" + "errors" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/known/anypb" "github.com/cosmos/cosmos-sdk/codec" ) @@ -163,6 +165,7 @@ func (t defaultEncodingConfig) TxJSONDecoder() txApiDecoder { type defaultTxSigningConfig struct { signingCtx *signing.Context handlerMap *signing.HandlerMap + cdc codec.BinaryCodec } // newDefaultTxSigningConfig creates a new defaultTxSigningConfig instance using the provided ConfigOptions. @@ -181,6 +184,7 @@ func newDefaultTxSigningConfig(opts ConfigOptions) (*defaultTxSigningConfig, err return &defaultTxSigningConfig{ signingCtx: signingCtx, handlerMap: handlerMap, + cdc: opts.Cdc, }, nil } @@ -197,15 +201,69 @@ func (t defaultTxSigningConfig) SigningContext() *signing.Context { // MarshalSignatureJSON takes a slice of Signature objects and returns their JSON encoding. // This method is not yet implemented and will panic if called. func (t defaultTxSigningConfig) MarshalSignatureJSON(signatures []Signature) ([]byte, error) { - // TODO implement me - panic("implement me") + descriptor := make([]*apitxsigning.SignatureDescriptor, len(signatures)) + + for i, sig := range signatures { + descData, err := SignatureDataToProto(sig.Data) + if err != nil { + return nil, err + } + + anyPk, err := codectypes.NewAnyWithValue(sig.PubKey) + if err != nil { + return nil, err + } + + descriptor[i] = &apitxsigning.SignatureDescriptor{ + PublicKey: &anypb.Any{ + TypeUrl: codectypes.MsgTypeURL(sig.PubKey), + Value: anyPk.Value, + }, + Data: descData, + Sequence: sig.Sequence, + } + } + + return jsonMarshalOptions.Marshal(&apitxsigning.SignatureDescriptors{Signatures: descriptor}) } // UnmarshalSignatureJSON takes a JSON byte slice and returns a slice of Signature objects. // This method is not yet implemented and will panic if called. -func (t defaultTxSigningConfig) UnmarshalSignatureJSON(bytes []byte) ([]Signature, error) { - // TODO implement me - panic("implement me") +func (t defaultTxSigningConfig) UnmarshalSignatureJSON(bz []byte) ([]Signature, error) { + var descriptor apitxsigning.SignatureDescriptors + + err := protojson.UnmarshalOptions{}.Unmarshal(bz, &descriptor) + if err != nil { + return nil, err + } + + sigs := make([]Signature, len(descriptor.Signatures)) + for i, desc := range descriptor.Signatures { + var pubkey cryptotypes.PubKey + + anyPk := &codectypes.Any{ + TypeUrl: desc.PublicKey.TypeUrl, + Value: desc.PublicKey.Value, + } + + err = t.cdc.UnpackAny(anyPk, &pubkey) + if err != nil { + return nil, err + } + + data, err := SignatureDataFromProto(desc.Data) + if err != nil { + return nil, err + } + + sigs[i] = Signature{ + PubKey: pubkey, + Data: data, + Sequence: desc.Sequence, + } + } + + return sigs, nil } // newSigningContext creates a new signing context using the provided ConfigOptions. diff --git a/client/v2/tx/config_test.go b/client/v2/tx/config_test.go index 41871e6c48a3..f92dcd1ffd7b 100644 --- a/client/v2/tx/config_test.go +++ b/client/v2/tx/config_test.go @@ -2,6 +2,15 @@ package tx import ( "context" + apicrypto "cosmossdk.io/api/cosmos/crypto/multisig/v1beta1" + _ "cosmossdk.io/api/cosmos/crypto/secp256k1" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + codec2 "github.com/cosmos/cosmos-sdk/crypto/codec" + kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + _ "github.com/cosmos/cosmos-sdk/crypto/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "testing" "github.com/stretchr/testify/require" @@ -159,3 +168,127 @@ func TestNewTxConfig(t *testing.T) { }) } } + +func Test_defaultTxSigningConfig_MarshalSignatureJSON(t *testing.T) { + tests := []struct { + name string + options ConfigOptions + signatures func(t *testing.T) []Signature + }{ + { + name: "single signature", + options: ConfigOptions{ + AddressCodec: ac, + Cdc: cdc, + ValidatorAddressCodec: valCodec, + }, + signatures: func(t *testing.T) []Signature { + t.Helper() + + k := setKeyring() + pk, err := k.GetPubKey("alice") + signature, err := k.Sign("alice", make([]byte, 10), apitxsigning.SignMode_SIGN_MODE_DIRECT) + require.NoError(t, err) + return []Signature{ + { + PubKey: pk, + Data: &SingleSignatureData{ + SignMode: apitxsigning.SignMode_SIGN_MODE_DIRECT, + Signature: signature, + }, + }, + } + }, + }, + { + name: "multisig signatures", + options: ConfigOptions{ + AddressCodec: ac, + Cdc: cdc, + ValidatorAddressCodec: valCodec, + }, + signatures: func(t *testing.T) []Signature { + t.Helper() + + n := 2 + pubKeys := make([]cryptotypes.PubKey, n) + sigs := make([]SignatureData, n) + for i := 0; i < n; i++ { + sk := secp256k1.GenPrivKey() + pubKeys[i] = sk.PubKey() + msg, err := sk.Sign(make([]byte, 10)) + require.NoError(t, err) + sigs[i] = &SingleSignatureData{ + SignMode: apitxsigning.SignMode_SIGN_MODE_DIRECT, + Signature: msg, + } + } + bitArray := cryptotypes.NewCompactBitArray(n) + mKey := kmultisig.NewLegacyAminoPubKey(n, pubKeys) + return []Signature{ + { + PubKey: mKey, + Data: &MultiSignatureData{ + BitArray: &apicrypto.CompactBitArray{ + ExtraBitsStored: bitArray.ExtraBitsStored, + Elems: bitArray.Elems, + }, + Signatures: sigs, + }, + }, + } + + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + config, err := NewTxConfig(tt.options) + require.NoError(t, err) + + got, err := config.MarshalSignatureJSON(tt.signatures(t)) + require.NoError(t, err) + require.NotNil(t, got) + }) + } +} + +func Test_defaultTxSigningConfig_UnmarshalSignatureJSON(t *testing.T) { + registry := codectypes.NewInterfaceRegistry() + codec2.RegisterInterfaces(registry) + cdc := codec.NewProtoCodec(registry) + tests := []struct { + name string + options ConfigOptions + bz []byte + }{ + { + name: "single signature", + options: ConfigOptions{ + AddressCodec: ac, + Cdc: cdc, + ValidatorAddressCodec: valCodec, + }, + bz: []byte(`{"signatures":[{"public_key":{"@type":"/cosmos.crypto.secp256k1.PubKey", "key":"A0/vnNfExjWI07A/61KBudIyy6NNbz1xruWSEf+/4f6H"}, "data":{"single":{"mode":"SIGN_MODE_DIRECT", "signature":"usUTJwdc4PWPuox0Y0G/RuHoxyj+QpUcBGvXyNdDX1FOdoVj0tg4TGKT2NnM3QP6wCNbubjHuMOhTtqfW8SkYg=="}}}]}`), + }, + { + name: "multisig signatures", + options: ConfigOptions{ + AddressCodec: ac, + Cdc: cdc, + ValidatorAddressCodec: valCodec, + }, + bz: []byte(`{"signatures":[{"public_key":{"@type":"/cosmos.crypto.multisig.LegacyAminoPubKey","threshold":2,"public_keys":[{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A4Bs9huvS/COpZNhVhTnhgc8YR6VrSQ8hLQIHgnA+m3w"},{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AuNz2lFkLn3sKNjC5r4OWhgkWg5DZpGUiR9OdpzXspnp"}]},"data":{"multi":{"bitarray":{"extra_bits_stored":2,"elems":"AA=="},"signatures":[{"single":{"mode":"SIGN_MODE_DIRECT","signature":"vng4IlPzLH3fDFpikM5y1SfXFGny4BcLGwIFU0Ty4yoWjIxjTS4m6fgDB61sxEkV5DK/CD7gUwenGuEpzJ2IGw=="}},{"single":{"mode":"SIGN_MODE_DIRECT","signature":"2dsGmr13bq/mPxbk9AgqcFpuvk4beszWu6uxkx+EhTMdVGp4J8FtjZc8xs/Pp3oTWY4ScAORYQHxwqN4qwMXGg=="}}]}}}]}`), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + config, err := NewTxConfig(tt.options) + require.NoError(t, err) + + got, err := config.UnmarshalSignatureJSON(tt.bz) + require.NoError(t, err) + require.NotNil(t, got) + }) + } +} diff --git a/client/v2/tx/encoder.go b/client/v2/tx/encoder.go index 4067c73f44da..ed03f27988a3 100644 --- a/client/v2/tx/encoder.go +++ b/client/v2/tx/encoder.go @@ -1,11 +1,10 @@ package tx import ( - "google.golang.org/protobuf/encoding/protojson" - protov2 "google.golang.org/protobuf/proto" - apitx "cosmossdk.io/api/cosmos/tx/v1beta1" txdecode "cosmossdk.io/x/tx/decode" + "google.golang.org/protobuf/encoding/protojson" + protov2 "google.golang.org/protobuf/proto" ) var ( diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index 3686993d5acd..13e8439db48b 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -7,6 +7,8 @@ import ( "math/big" "strings" + apicrypto "cosmossdk.io/api/cosmos/crypto/multisig/v1beta1" + "github.com/cosmos/go-bip39" gogogrpc "github.com/cosmos/gogoproto/grpc" "google.golang.org/protobuf/types/known/anypb" @@ -67,10 +69,6 @@ func (f *Factory) Prepare() error { return errors.New("missing 'from address' field") } - if err := f.accountRetriever.EnsureExists(context.Background(), f.txParams.address); err != nil { - return err - } - if f.txParams.accountNumber == 0 || f.txParams.sequence == 0 { fc := f num, seq, err := fc.accountRetriever.GetAccountNumberSequence(context.Background(), f.txParams.address) @@ -502,6 +500,16 @@ func (f *Factory) getSimPK() (cryptotypes.PubKey, error) { if err != nil { return nil, err } + } else { + // When in dry-run mode, attempt to retrieve the account using the provided address. + // If the account retrieval fails, the default public key is used. + acc, err := f.accountRetriever.GetAccount(context.Background(), f.txParams.address) + if err != nil { + // If there is an error retrieving the account, return the default public key. + return pk, nil + } + // If the account is successfully retrieved, use its public key. + pk = acc.GetPubKey() } return pk, nil @@ -523,6 +531,7 @@ func (f *Factory) getSimSignatureData(pk cryptotypes.PubKey) SignatureData { } return &MultiSignatureData{ + BitArray: &apicrypto.CompactBitArray{}, Signatures: multiSignatureData, } } diff --git a/client/v2/tx/factory_test.go b/client/v2/tx/factory_test.go index f93b7bbf9d27..f7c2c8cc4c07 100644 --- a/client/v2/tx/factory_test.go +++ b/client/v2/tx/factory_test.go @@ -2,7 +2,6 @@ package tx import ( "context" - "fmt" "testing" "github.com/stretchr/testify/require" @@ -359,7 +358,6 @@ func TestFactory_getSignBytesAdapter(t *testing.T) { } got, err := f.getSignBytesAdapter(context.Background(), signerData, txb) - fmt.Println(got) if tt.error { require.Error(t, err) } else { diff --git a/client/v2/tx/signature.go b/client/v2/tx/signature.go index ac9a03d253e1..8769a53efae3 100644 --- a/client/v2/tx/signature.go +++ b/client/v2/tx/signature.go @@ -125,3 +125,73 @@ func decodeMultiSignatures(bz []byte) ([][]byte, error) { } return multisig.Signatures, nil } + +// SignatureDataToProto converts a SignatureData interface to a protobuf SignatureDescriptor_Data. +// This function supports both SingleSignatureData and MultiSignatureData types. +// For SingleSignatureData, it directly maps the signature mode and signature bytes to the protobuf structure. +// For MultiSignatureData, it recursively converts each signature in the collection to the corresponding protobuf structure. +func SignatureDataToProto(data SignatureData) (*apitxsigning.SignatureDescriptor_Data, error) { + switch data := data.(type) { + case *SingleSignatureData: + // Handle single signature data conversion. + return &apitxsigning.SignatureDescriptor_Data{ + Sum: &apitxsigning.SignatureDescriptor_Data_Single_{ + Single: &apitxsigning.SignatureDescriptor_Data_Single{ + Mode: data.SignMode, + Signature: data.Signature, + }, + }, + }, nil + case *MultiSignatureData: + var err error + descDatas := make([]*apitxsigning.SignatureDescriptor_Data, len(data.Signatures)) + + for i, j := range data.Signatures { + descDatas[i], err = SignatureDataToProto(j) + if err != nil { + return nil, err + } + } + return &apitxsigning.SignatureDescriptor_Data{ + Sum: &apitxsigning.SignatureDescriptor_Data_Multi_{ + Multi: &apitxsigning.SignatureDescriptor_Data_Multi{ + Bitarray: data.BitArray, + Signatures: descDatas, + }, + }, + }, nil + } + + // Return an error if the data type is not supported. + return nil, fmt.Errorf("unexpected signature data type %T", data) +} + +// SignatureDataFromProto converts a protobuf SignatureDescriptor_Data to a SignatureData interface. +// This function supports both Single and Multi signature data types. +func SignatureDataFromProto(descData *apitxsigning.SignatureDescriptor_Data) (SignatureData, error) { + switch descData := descData.Sum.(type) { + case *apitxsigning.SignatureDescriptor_Data_Single_: + return &SingleSignatureData{ + SignMode: descData.Single.Mode, + Signature: descData.Single.Signature, + }, nil + case *apitxsigning.SignatureDescriptor_Data_Multi_: + var err error + multi := descData.Multi + data := make([]SignatureData, len(multi.Signatures)) + + for i, j := range multi.Signatures { + data[i], err = SignatureDataFromProto(j) + if err != nil { + return nil, err + } + } + + return &MultiSignatureData{ + BitArray: multi.Bitarray, + Signatures: data, + }, nil + } + + return nil, fmt.Errorf("unexpected signature data type %T", descData) +} diff --git a/client/v2/tx/signature_test.go b/client/v2/tx/signature_test.go index 2bd1cf2a03f5..f0bdce1fdb74 100644 --- a/client/v2/tx/signature_test.go +++ b/client/v2/tx/signature_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/require" + apimultisig "cosmossdk.io/api/cosmos/crypto/multisig/v1beta1" apisigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" apitx "cosmossdk.io/api/cosmos/tx/v1beta1" ) @@ -95,6 +96,37 @@ func TestModeInfoAndSigToSignatureData(t *testing.T) { Signature: []byte("signature"), }, }, + { + name: "to MultiSignatureData", + args: args{ + modeInfo: func() *apitx.ModeInfo { + return &apitx.ModeInfo{ + Sum: &apitx.ModeInfo_Multi_{ + Multi: &apitx.ModeInfo_Multi{ + Bitarray: &apimultisig.CompactBitArray{}, + ModeInfos: []*apitx.ModeInfo{ + { + Sum: &apitx.ModeInfo_Single_{ + Single: &apitx.ModeInfo_Single{Mode: apisigning.SignMode_SIGN_MODE_DIRECT}, + }, + }, + }, + }, + }, + } + }, + sig: []byte("\n\tsignature"), + }, + want: &MultiSignatureData{ // Changed from SingleSignatureData to MultiSignatureData + BitArray: &apimultisig.CompactBitArray{}, + Signatures: []SignatureData{ + &SingleSignatureData{ + SignMode: apisigning.SignMode_SIGN_MODE_DIRECT, + Signature: []byte("signature"), + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 1a29aa801c9d39d742b161425b4ab77c1a69e7a0 Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Fri, 21 Jun 2024 13:34:54 +0200 Subject: [PATCH 16/36] lint --- client/v2/tx/aux_builder.go | 11 +++++------ client/v2/tx/builder_test.go | 9 ++++----- client/v2/tx/common_test.go | 2 +- client/v2/tx/config.go | 14 ++++++++------ client/v2/tx/config_test.go | 19 +++++++++---------- client/v2/tx/encoder.go | 5 +++-- client/v2/tx/factory.go | 3 +-- client/v2/tx/factory_test.go | 1 + 8 files changed, 32 insertions(+), 32 deletions(-) diff --git a/client/v2/tx/aux_builder.go b/client/v2/tx/aux_builder.go index fcb7b4c72448..d6d8a098ed87 100644 --- a/client/v2/tx/aux_builder.go +++ b/client/v2/tx/aux_builder.go @@ -9,7 +9,6 @@ import ( gogoany "github.com/cosmos/gogoproto/types/any" "google.golang.org/protobuf/types/known/anypb" - apisigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" apitx "cosmossdk.io/api/cosmos/tx/v1beta1" "cosmossdk.io/core/transaction" @@ -128,7 +127,7 @@ func (b *AuxTxBuilder) SetSignMode(mode apitxsigning.SignMode) error { case apitxsigning.SignMode_SIGN_MODE_DIRECT_AUX, apitxsigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON: default: return sdkerrors.ErrInvalidRequest.Wrapf("AuxTxBuilder can only sign with %s or %s", - apisigning.SignMode_SIGN_MODE_DIRECT_AUX, apisigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON) + apitxsigning.SignMode_SIGN_MODE_DIRECT_AUX, apitxsigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON) } b.auxSignerData.Mode = mode @@ -189,8 +188,8 @@ func validateAuxSignerData(a *apitx.AuxSignerData) error { return errors.New("address cannot be empty: invalid request") } - if a.Mode != apisigning.SignMode_SIGN_MODE_DIRECT_AUX && a.Mode != apisigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON { - return fmt.Errorf("AuxTxBuilder can only sign with %s or %s", apisigning.SignMode_SIGN_MODE_DIRECT_AUX, apisigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON) + if a.Mode != apitxsigning.SignMode_SIGN_MODE_DIRECT_AUX && a.Mode != apitxsigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON { + return fmt.Errorf("AuxTxBuilder can only sign with %s or %s", apitxsigning.SignMode_SIGN_MODE_DIRECT_AUX, apitxsigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON) } if len(a.Sig) == 0 { @@ -229,14 +228,14 @@ func (b *AuxTxBuilder) GetSignBytes() ([]byte, error) { var signBz []byte switch b.auxSignerData.Mode { - case apisigning.SignMode_SIGN_MODE_DIRECT_AUX: + case apitxsigning.SignMode_SIGN_MODE_DIRECT_AUX: { signBz, err = proto.Marshal(b.auxSignerData.SignDoc) if err != nil { return nil, err } } - case apisigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON: + case apitxsigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON: { handler := aminojson.NewSignModeHandler(aminojson.SignModeHandlerOptions{ FileResolver: proto.HybridResolver, diff --git a/client/v2/tx/builder_test.go b/client/v2/tx/builder_test.go index 7986ce8ece9d..4694c2fe9011 100644 --- a/client/v2/tx/builder_test.go +++ b/client/v2/tx/builder_test.go @@ -232,9 +232,6 @@ func Test_msgsV1toAnyV2(t *testing.T) { } func Test_intoAnyV2(t *testing.T) { - type args struct { - v1s []*codectypes.Any - } tests := []struct { name string msgs []*codectypes.Any @@ -307,8 +304,10 @@ func Test_txBuilder_getFee(t *testing.T) { t.Run(tt.name, func(t *testing.T) { b := newTxBuilder(ac, decoder, cdc) b.SetFeeAmount(tt.feeAmount) - b.SetFeeGranter(tt.feeGranter) - b.SetFeePayer(tt.feePayer) + err := b.SetFeeGranter(tt.feeGranter) + require.NoError(t, err) + err = b.SetFeePayer(tt.feePayer) + require.NoError(t, err) fee, err := b.getFee() require.NoError(t, err) diff --git a/client/v2/tx/common_test.go b/client/v2/tx/common_test.go index 6a07cb83a708..a45f9f0818f4 100644 --- a/client/v2/tx/common_test.go +++ b/client/v2/tx/common_test.go @@ -106,7 +106,7 @@ func (m mockClientConn) Invoke(_ context.Context, _ string, _, reply interface{} }, Result: nil, } - *reply.(*apitx.SimulateResponse) = simResponse + *reply.(*apitx.SimulateResponse) = simResponse // nolint:govet // ignore linting error return nil } diff --git a/client/v2/tx/config.go b/client/v2/tx/config.go index 7f1e33ad1a1c..57ef4b824835 100644 --- a/client/v2/tx/config.go +++ b/client/v2/tx/config.go @@ -1,6 +1,12 @@ package tx import ( + "errors" + + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/known/anypb" + apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" "cosmossdk.io/core/address" txdecode "cosmossdk.io/x/tx/decode" @@ -9,14 +15,10 @@ import ( "cosmossdk.io/x/tx/signing/direct" "cosmossdk.io/x/tx/signing/directaux" "cosmossdk.io/x/tx/signing/textual" - "errors" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - "google.golang.org/protobuf/encoding/protojson" - "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/types/known/anypb" "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" ) var ( diff --git a/client/v2/tx/config_test.go b/client/v2/tx/config_test.go index f92dcd1ffd7b..7d1f223d1214 100644 --- a/client/v2/tx/config_test.go +++ b/client/v2/tx/config_test.go @@ -2,23 +2,22 @@ package tx import ( "context" - apicrypto "cosmossdk.io/api/cosmos/crypto/multisig/v1beta1" - _ "cosmossdk.io/api/cosmos/crypto/secp256k1" - "github.com/cosmos/cosmos-sdk/codec" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - codec2 "github.com/cosmos/cosmos-sdk/crypto/codec" - kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - _ "github.com/cosmos/cosmos-sdk/crypto/types" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "testing" "github.com/stretchr/testify/require" + apicrypto "cosmossdk.io/api/cosmos/crypto/multisig/v1beta1" + _ "cosmossdk.io/api/cosmos/crypto/secp256k1" apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" "cosmossdk.io/x/tx/signing" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec/address" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + codec2 "github.com/cosmos/cosmos-sdk/crypto/codec" + kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" ) type mockModeHandler struct{} @@ -187,6 +186,7 @@ func Test_defaultTxSigningConfig_MarshalSignatureJSON(t *testing.T) { k := setKeyring() pk, err := k.GetPubKey("alice") + require.NoError(t, err) signature, err := k.Sign("alice", make([]byte, 10), apitxsigning.SignMode_SIGN_MODE_DIRECT) require.NoError(t, err) return []Signature{ @@ -237,7 +237,6 @@ func Test_defaultTxSigningConfig_MarshalSignatureJSON(t *testing.T) { }, }, } - }, }, } diff --git a/client/v2/tx/encoder.go b/client/v2/tx/encoder.go index ed03f27988a3..4067c73f44da 100644 --- a/client/v2/tx/encoder.go +++ b/client/v2/tx/encoder.go @@ -1,10 +1,11 @@ package tx import ( - apitx "cosmossdk.io/api/cosmos/tx/v1beta1" - txdecode "cosmossdk.io/x/tx/decode" "google.golang.org/protobuf/encoding/protojson" protov2 "google.golang.org/protobuf/proto" + + apitx "cosmossdk.io/api/cosmos/tx/v1beta1" + txdecode "cosmossdk.io/x/tx/decode" ) var ( diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index 13e8439db48b..3c32d81f90e9 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -7,13 +7,12 @@ import ( "math/big" "strings" - apicrypto "cosmossdk.io/api/cosmos/crypto/multisig/v1beta1" - "github.com/cosmos/go-bip39" gogogrpc "github.com/cosmos/gogoproto/grpc" "google.golang.org/protobuf/types/known/anypb" base "cosmossdk.io/api/cosmos/base/v1beta1" + apicrypto "cosmossdk.io/api/cosmos/crypto/multisig/v1beta1" apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" apitx "cosmossdk.io/api/cosmos/tx/v1beta1" "cosmossdk.io/client/v2/autocli/keyring" diff --git a/client/v2/tx/factory_test.go b/client/v2/tx/factory_test.go index f7c2c8cc4c07..557320f2a94a 100644 --- a/client/v2/tx/factory_test.go +++ b/client/v2/tx/factory_test.go @@ -337,6 +337,7 @@ func TestFactory_getSignBytesAdapter(t *testing.T) { Count: 0, }, }...) + require.NoError(t, err) pk, err := f.keybase.GetPubKey("alice") require.NoError(t, err) From 5854380fa432e8d230988fbf6becc8d44a682864 Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Fri, 21 Jun 2024 13:36:29 +0200 Subject: [PATCH 17/36] remove todo --- client/v2/tx/builder.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/v2/tx/builder.go b/client/v2/tx/builder.go index e2c2759c059c..66b42e918a08 100644 --- a/client/v2/tx/builder.go +++ b/client/v2/tx/builder.go @@ -30,7 +30,7 @@ type ExtendedTxBuilder interface { // also know how to encode itself. type TxBuilder interface { GetTx() (*apitx.Tx, error) - GetSigningTxData() (*signing.TxData, error) // TODO: check this + GetSigningTxData() (*signing.TxData, error) SetMsgs(...transaction.Msg) error SetMemo(string) @@ -70,7 +70,8 @@ func (b BuilderProvider) NewTxBuilder() TxBuilder { return newTxBuilder(b.addressCodec, b.decoder, b.codec) } -// TODO: work on this +// WrapTxBuilder +// TODO: is this necessary func (b BuilderProvider) WrapTxBuilder(tx *apitx.Tx) (TxBuilder, error) { return &txBuilder{ addressCodec: b.addressCodec, From 4094945be1b95dd0bcc2e4519de450eef4e617ee Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Fri, 21 Jun 2024 14:19:28 +0200 Subject: [PATCH 18/36] fix: go-mod-tidy-all --- client/v2/go.mod | 2 +- client/v2/{tx/account.go => internal/account/retriever.go} | 6 +++--- client/v2/tx/common_test.go | 5 +++-- client/v2/tx/factory.go | 7 ++++--- client/v2/tx/tx.go | 3 ++- 5 files changed, 13 insertions(+), 10 deletions(-) rename client/v2/{tx/account.go => internal/account/retriever.go} (95%) diff --git a/client/v2/go.mod b/client/v2/go.mod index 9fe024436acb..56f40fb37a47 100644 --- a/client/v2/go.mod +++ b/client/v2/go.mod @@ -54,7 +54,7 @@ require ( github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/cosmos-db v1.0.2 // indirect github.com/cosmos/crypto v0.0.0-20240309083813-82ed2537802e // indirect - github.com/cosmos/go-bip39 v1.0.0 // indirect + github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/gogogateway v1.2.0 // indirect github.com/cosmos/gogoproto v1.5.0 github.com/cosmos/iavl v1.2.0 // indirect diff --git a/client/v2/tx/account.go b/client/v2/internal/account/retriever.go similarity index 95% rename from client/v2/tx/account.go rename to client/v2/internal/account/retriever.go index 21665a8fc625..045f36781f29 100644 --- a/client/v2/tx/account.go +++ b/client/v2/internal/account/retriever.go @@ -1,4 +1,4 @@ -package tx +package account import ( "context" @@ -46,8 +46,8 @@ type accountRetriever struct { registry codectypes.InterfaceRegistry } -// newAccountRetriever creates a new instance of accountRetriever. -func newAccountRetriever(ac address.Codec, conn gogogrpc.ClientConn, registry codectypes.InterfaceRegistry) *accountRetriever { +// NewAccountRetriever creates a new instance of accountRetriever. +func NewAccountRetriever(ac address.Codec, conn gogogrpc.ClientConn, registry codectypes.InterfaceRegistry) *accountRetriever { return &accountRetriever{ ac: ac, conn: conn, diff --git a/client/v2/tx/common_test.go b/client/v2/tx/common_test.go index a45f9f0818f4..3b474e9fef7a 100644 --- a/client/v2/tx/common_test.go +++ b/client/v2/tx/common_test.go @@ -8,6 +8,7 @@ import ( abciv1beta1 "cosmossdk.io/api/cosmos/base/abci/v1beta1" apitx "cosmossdk.io/api/cosmos/tx/v1beta1" "cosmossdk.io/client/v2/autocli/keyring" + "cosmossdk.io/client/v2/internal/account" txdecode "cosmossdk.io/x/tx/decode" "cosmossdk.io/x/tx/signing" @@ -80,11 +81,11 @@ func (m mockAccount) GetSequence() uint64 { type mockAccountRetriever struct{} -func (m mockAccountRetriever) GetAccount(_ context.Context, address []byte) (Account, error) { +func (m mockAccountRetriever) GetAccount(_ context.Context, address []byte) (account.Account, error) { return mockAccount{addr: address}, nil } -func (m mockAccountRetriever) GetAccountWithHeight(_ context.Context, address []byte) (Account, int64, error) { +func (m mockAccountRetriever) GetAccountWithHeight(_ context.Context, address []byte) (account.Account, int64, error) { return mockAccount{addr: address}, 0, nil } diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index 3c32d81f90e9..57cefebc7344 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -16,6 +16,7 @@ import ( apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" apitx "cosmossdk.io/api/cosmos/tx/v1beta1" "cosmossdk.io/client/v2/autocli/keyring" + "cosmossdk.io/client/v2/internal/account" "cosmossdk.io/client/v2/internal/coins" "cosmossdk.io/core/address" "cosmossdk.io/core/transaction" @@ -34,7 +35,7 @@ import ( type Factory struct { keybase keyring.Keyring cdc codec.BinaryCodec - accountRetriever AccountRetriever + accountRetriever account.AccountRetriever ac address.Codec conn gogogrpc.ClientConn txConfig TxConfig @@ -42,7 +43,7 @@ type Factory struct { } // NewFactory returns a new instance of Factory. -func NewFactory(keybase keyring.Keyring, cdc codec.BinaryCodec, accRetriever AccountRetriever, txConfig TxConfig, ac address.Codec, conn gogogrpc.ClientConn, parameters TxParameters) (Factory, error) { +func NewFactory(keybase keyring.Keyring, cdc codec.BinaryCodec, accRetriever account.AccountRetriever, txConfig TxConfig, ac address.Codec, conn gogogrpc.ClientConn, parameters TxParameters) (Factory, error) { return Factory{ keybase: keybase, cdc: cdc, @@ -469,7 +470,7 @@ func (f *Factory) Fees() []*base.Coin { return f.txParams.fees } func (f *Factory) GasPrices() []*base.DecCoin { return f.txParams.gasPrices } // AccountRetriever returns the account retriever. -func (f *Factory) AccountRetriever() AccountRetriever { return f.accountRetriever } +func (f *Factory) AccountRetriever() account.AccountRetriever { return f.accountRetriever } // TimeoutHeight returns the timeout height. func (f *Factory) TimeoutHeight() uint64 { return f.txParams.timeoutHeight } diff --git a/client/v2/tx/tx.go b/client/v2/tx/tx.go index a7ee38e3d6f4..c70b2366afd8 100644 --- a/client/v2/tx/tx.go +++ b/client/v2/tx/tx.go @@ -13,6 +13,7 @@ import ( apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" apitx "cosmossdk.io/api/cosmos/tx/v1beta1" keyring2 "cosmossdk.io/client/v2/autocli/keyring" + "cosmossdk.io/client/v2/internal/account" "cosmossdk.io/core/address" "cosmossdk.io/core/transaction" @@ -179,7 +180,7 @@ func newFactory(ctx client.Context, flagSet *pflag.FlagSet) (Factory, error) { return Factory{}, err } - accRetriever := newAccountRetriever(ctx.AddressCodec, ctx, ctx.InterfaceRegistry) + accRetriever := account.NewAccountRetriever(ctx.AddressCodec, ctx, ctx.InterfaceRegistry) txf, err := NewFactory(k, ctx.Codec, accRetriever, txConfig, ctx.AddressCodec, ctx, params) if err != nil { From 9ee9940f508d62ce2fe88b7716d6331bfba113a1 Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Fri, 28 Jun 2024 11:30:04 +0200 Subject: [PATCH 19/36] update: coderrabbit suggestions --- client/v2/tx/builder_test.go | 2 ++ client/v2/tx/factory.go | 7 +++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/client/v2/tx/builder_test.go b/client/v2/tx/builder_test.go index 4694c2fe9011..cb16e3464e56 100644 --- a/client/v2/tx/builder_test.go +++ b/client/v2/tx/builder_test.go @@ -573,6 +573,8 @@ func Test_txBuilder_SetSignatures(t *testing.T) { sigs := tt.signatures() err := b.SetSignatures(sigs...) require.NoError(t, err) + tx, _ := b.GetTx() + require.Equal(t, len(sigs), len(tx.Signatures)) }) } } diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index 57cefebc7344..5ffdf95236d0 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -70,18 +70,17 @@ func (f *Factory) Prepare() error { } if f.txParams.accountNumber == 0 || f.txParams.sequence == 0 { - fc := f - num, seq, err := fc.accountRetriever.GetAccountNumberSequence(context.Background(), f.txParams.address) + num, seq, err := f.accountRetriever.GetAccountNumberSequence(context.Background(), f.txParams.address) if err != nil { return err } if f.txParams.accountNumber == 0 { - fc.WithAccountNumber(num) + f.WithAccountNumber(num) } if f.txParams.sequence == 0 { - fc.WithSequence(seq) + f.WithSequence(seq) } } From cd552928f848e9141af11eff957ef0c3d14bde53 Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Fri, 5 Jul 2024 11:07:46 +0200 Subject: [PATCH 20/36] fix: signingTxData encoding --- client/v2/tx/builder.go | 5 +++-- client/v2/tx/builder_test.go | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/client/v2/tx/builder.go b/client/v2/tx/builder.go index 66b42e918a08..2a0540754d46 100644 --- a/client/v2/tx/builder.go +++ b/client/v2/tx/builder.go @@ -1,6 +1,7 @@ package tx import ( + gogoproto "github.com/cosmos/gogoproto/proto" gogoany "github.com/cosmos/gogoproto/types/any" "google.golang.org/protobuf/types/known/anypb" @@ -178,11 +179,11 @@ func (b *txBuilder) GetSigningTxData() (*signing.TxData, error) { return nil, err } - bodyBytes, err := marshalOption.Marshal(tx.Body) + bodyBytes, err := gogoproto.Marshal(tx.Body) if err != nil { return nil, err } - authBytes, err := marshalOption.Marshal(tx.AuthInfo) + authBytes, err := gogoproto.Marshal(tx.AuthInfo) if err != nil { return nil, err } diff --git a/client/v2/tx/builder_test.go b/client/v2/tx/builder_test.go index cb16e3464e56..b02d76293a00 100644 --- a/client/v2/tx/builder_test.go +++ b/client/v2/tx/builder_test.go @@ -356,7 +356,7 @@ func Test_txBuilder_GetSigningTxData(t *testing.T) { PubKey: pk, Data: &SingleSignatureData{ SignMode: apisigning.SignMode_SIGN_MODE_DIRECT, - Signature: nil, + Signature: []byte("signature"), }, Sequence: 0, }}...) From 3dcc970927530bec6f375206e43e9ba338071d8b Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Fri, 5 Jul 2024 21:24:32 +0200 Subject: [PATCH 21/36] add: DecodedTx wrapper --- client/v2/tx/builder.go | 74 +++++++++++----------- client/v2/tx/builder_test.go | 84 ++++++++++++------------- client/v2/tx/config.go | 24 ++++---- client/v2/tx/encoder.go | 42 +++++++------ client/v2/tx/encoder_test.go | 110 ++++++++++++++++----------------- client/v2/tx/factory.go | 12 +--- client/v2/tx/types.go | 104 +------------------------------ client/v2/tx/wrapper.go | 115 +++++++++++++++++++++++++++++++++++ 8 files changed, 284 insertions(+), 281 deletions(-) create mode 100644 client/v2/tx/wrapper.go diff --git a/client/v2/tx/builder.go b/client/v2/tx/builder.go index 2a0540754d46..e73767ed82df 100644 --- a/client/v2/tx/builder.go +++ b/client/v2/tx/builder.go @@ -1,7 +1,6 @@ package tx import ( - gogoproto "github.com/cosmos/gogoproto/proto" gogoany "github.com/cosmos/gogoproto/types/any" "google.golang.org/protobuf/types/known/anypb" @@ -30,7 +29,7 @@ type ExtendedTxBuilder interface { // signatures, and provide canonical bytes to sign over. The transaction must // also know how to encode itself. type TxBuilder interface { - GetTx() (*apitx.Tx, error) + GetTx() (Tx, error) GetSigningTxData() (*signing.TxData, error) SetMsgs(...transaction.Msg) error @@ -110,7 +109,11 @@ func newTxBuilder(addressCodec address.Codec, decoder Decoder, codec codec.Binar } // GetTx converts txBuilder messages to V2 and returns a Tx. -func (b *txBuilder) GetTx() (*apitx.Tx, error) { +func (b *txBuilder) GetTx() (Tx, error) { + return b.getTx() +} + +func (b *txBuilder) getTx() (*wrappedTx, error) { msgs, err := msgsV1toAnyV2(b.msgs) if err != nil { return nil, err @@ -135,11 +138,31 @@ func (b *txBuilder) GetTx() (*apitx.Tx, error) { Fee: fee, } - return &apitx.Tx{ - Body: body, - AuthInfo: authInfo, - Signatures: b.signatures, - }, nil + bodyBytes, err := marshalOption.Marshal(body) + if err != nil { + return nil, err + } + + authInfoBytes, err := marshalOption.Marshal(authInfo) + if err != nil { + return nil, err + } + + txRawBytes, err := marshalOption.Marshal(&apitx.TxRaw{ + BodyBytes: bodyBytes, + AuthInfoBytes: authInfoBytes, + Signatures: b.signatures, + }) + if err != nil { + return nil, err + } + + decodedTx, err := b.decoder.Decode(txRawBytes) + if err != nil { + return nil, err + } + + return newWrapperTx(b.codec, decodedTx), nil } // getFee computes the transaction fee information for the txBuilder. @@ -174,40 +197,17 @@ func (b *txBuilder) getFee() (fee *apitx.Fee, err error) { // GetSigningTxData returns a TxData with the txBuilder info. func (b *txBuilder) GetSigningTxData() (*signing.TxData, error) { - tx, err := b.GetTx() - if err != nil { - return nil, err - } - - bodyBytes, err := gogoproto.Marshal(tx.Body) - if err != nil { - return nil, err - } - authBytes, err := gogoproto.Marshal(tx.AuthInfo) - if err != nil { - return nil, err - } - - rawTx, err := marshalOption.Marshal(&apitx.TxRaw{ - BodyBytes: bodyBytes, - AuthInfoBytes: authBytes, - Signatures: b.signatures, - }) - if err != nil { - return nil, err - } - - decodedTx, err := b.decoder.Decode(rawTx) + tx, err := b.getTx() if err != nil { return nil, err } return &signing.TxData{ - Body: decodedTx.Tx.Body, - AuthInfo: decodedTx.Tx.AuthInfo, - BodyBytes: decodedTx.TxRaw.BodyBytes, - AuthInfoBytes: decodedTx.TxRaw.AuthInfoBytes, - BodyHasUnknownNonCriticals: decodedTx.TxBodyHasUnknownNonCriticals, + Body: tx.Tx.Body, + AuthInfo: tx.Tx.AuthInfo, + BodyBytes: tx.TxRaw.BodyBytes, + AuthInfoBytes: tx.TxRaw.AuthInfoBytes, + BodyHasUnknownNonCriticals: tx.TxBodyHasUnknownNonCriticals, }, nil } diff --git a/client/v2/tx/builder_test.go b/client/v2/tx/builder_test.go index b02d76293a00..6588c4a47af0 100644 --- a/client/v2/tx/builder_test.go +++ b/client/v2/tx/builder_test.go @@ -1,18 +1,15 @@ package tx import ( - "reflect" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" "testing" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/types/known/anypb" - base "cosmossdk.io/api/cosmos/base/v1beta1" apisigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" - apitx "cosmossdk.io/api/cosmos/tx/v1beta1" "cosmossdk.io/core/address" "cosmossdk.io/core/transaction" txdecode "cosmossdk.io/x/tx/decode" + "github.com/stretchr/testify/require" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" @@ -115,39 +112,31 @@ func Test_txBuilder_GetTx(t *testing.T) { tests := []struct { name string txSetter func() *txBuilder - checkResult func(*apitx.Tx) bool + checkResult func(Tx) }{ { name: "empty tx", txSetter: func() *txBuilder { return newTxBuilder(ac, decoder, cdc) }, - checkResult: func(tx *apitx.Tx) bool { - if !reflect.DeepEqual(tx.Body, &apitx.TxBody{ - Messages: []*anypb.Any{}, - Memo: "", - TimeoutHeight: 0, - Unordered: false, - ExtensionOptions: nil, - NonCriticalExtensionOptions: nil, - }) { - return false - } - if !reflect.DeepEqual(tx.AuthInfo, &apitx.AuthInfo{ - SignerInfos: nil, - Fee: &apitx.Fee{ - Amount: nil, - GasLimit: 0, - Payer: "", - Granter: "", - }, - }) { - return false - } - if tx.Signatures != nil { - return false - } - return true + checkResult: func(tx Tx) { + wTx, ok := tx.(*wrappedTx) + require.True(t, ok) + //require.Equal(t, []*anypb.Any(nil), wTx.Tx.Body.Messages) + require.Nil(t, wTx.Tx.Body.Messages) + require.Empty(t, wTx.Tx.Body.Memo) + require.Equal(t, uint64(0), wTx.Tx.Body.TimeoutHeight) + require.Equal(t, wTx.Tx.Body.Unordered, false) + require.Nil(t, wTx.Tx.Body.ExtensionOptions) + require.Nil(t, wTx.Tx.Body.NonCriticalExtensionOptions) + + require.Nil(t, wTx.Tx.AuthInfo.SignerInfos) + require.Nil(t, wTx.Tx.AuthInfo.Fee.Amount) + require.Equal(t, uint64(0), wTx.Tx.AuthInfo.Fee.GasLimit) + require.Empty(t, wTx.Tx.AuthInfo.Fee.Payer) + require.Empty(t, wTx.Tx.AuthInfo.Fee.Granter) + + require.Nil(t, wTx.Tx.Signatures) }, }, { @@ -182,17 +171,16 @@ func Test_txBuilder_GetTx(t *testing.T) { require.NoError(t, err) return b }, - checkResult: func(tx *apitx.Tx) bool { - if len(tx.Body.Messages) < 1 { - return false - } - if tx.AuthInfo.SignerInfos == nil || tx.AuthInfo.Fee.Amount == nil { - return false - } - if tx.Signatures == nil { - return false - } - return true + checkResult: func(tx Tx) { + wTx, ok := tx.(*wrappedTx) + require.True(t, ok) + require.True(t, len(wTx.Tx.Body.Messages) == 1) + + require.NotNil(t, wTx.Tx.AuthInfo.SignerInfos) + require.NotNil(t, wTx.Tx.AuthInfo.Fee.Amount) + + require.NotNil(t, wTx.Tx.Signatures) + }, }, } @@ -202,7 +190,7 @@ func Test_txBuilder_GetTx(t *testing.T) { got, err := b.GetTx() require.NoError(t, err) require.NotNil(t, got) - require.True(t, tt.checkResult(got)) + tt.checkResult(got) }) } } @@ -569,12 +557,16 @@ func Test_txBuilder_SetSignatures(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + cryptocodec.RegisterInterfaces(cdc.InterfaceRegistry()) b := newTxBuilder(ac, decoder, cdc) sigs := tt.signatures() err := b.SetSignatures(sigs...) require.NoError(t, err) - tx, _ := b.GetTx() - require.Equal(t, len(sigs), len(tx.Signatures)) + tx, err := b.GetTx() + require.NoError(t, err) + signatures, err := tx.GetSignatures() + require.NoError(t, err) + require.Equal(t, len(sigs), len(signatures)) }) } } diff --git a/client/v2/tx/config.go b/client/v2/tx/config.go index 57ef4b824835..9acfc04c9785 100644 --- a/client/v2/tx/config.go +++ b/client/v2/tx/config.go @@ -45,13 +45,13 @@ type TxConfig interface { // It provides methods for both binary and JSON encoding/decoding. type TxEncodingConfig interface { // TxEncoder returns an encoder for binary transaction encoding. - TxEncoder() txApiEncoder + TxEncoder() txEncoder // TxDecoder returns a decoder for binary transaction decoding. - TxDecoder() txApiDecoder + TxDecoder() txDecoder // TxJSONEncoder returns an encoder for JSON transaction encoding. - TxJSONEncoder() txApiEncoder + TxJSONEncoder() txEncoder // TxJSONDecoder returns a decoder for JSON transaction decoding. - TxJSONDecoder() txApiDecoder + TxJSONDecoder() txDecoder } // TxSigningConfig defines the interface for transaction signing configurations. @@ -144,23 +144,23 @@ func NewTxConfig(options ConfigOptions) (TxConfig, error) { type defaultEncodingConfig struct{} // TxEncoder returns the default transaction encoder. -func (t defaultEncodingConfig) TxEncoder() txApiEncoder { - return txEncoder +func (t defaultEncodingConfig) TxEncoder() txEncoder { + return encodeTx } // TxDecoder returns the default transaction decoder. -func (t defaultEncodingConfig) TxDecoder() txApiDecoder { - return txDecoder +func (t defaultEncodingConfig) TxDecoder() txDecoder { + return decodeTx } // TxJSONEncoder returns the default JSON transaction encoder. -func (t defaultEncodingConfig) TxJSONEncoder() txApiEncoder { - return txJsonEncoder +func (t defaultEncodingConfig) TxJSONEncoder() txEncoder { + return encodeJsonTx } // TxJSONDecoder returns the default JSON transaction decoder. -func (t defaultEncodingConfig) TxJSONDecoder() txApiDecoder { - return txJsonDecoder +func (t defaultEncodingConfig) TxJSONDecoder() txDecoder { + return decodeJsonTx } // defaultTxSigningConfig is a struct that holds the signing context and handler map. diff --git a/client/v2/tx/encoder.go b/client/v2/tx/encoder.go index 4067c73f44da..dda3c0317575 100644 --- a/client/v2/tx/encoder.go +++ b/client/v2/tx/encoder.go @@ -1,10 +1,12 @@ package tx import ( + "errors" + "fmt" + "google.golang.org/protobuf/encoding/protojson" protov2 "google.golang.org/protobuf/proto" - apitx "cosmossdk.io/api/cosmos/tx/v1beta1" txdecode "cosmossdk.io/x/tx/decode" ) @@ -26,29 +28,35 @@ type Decoder interface { } // txApiDecoder is a function type that unmarshals transaction bytes into an API Tx type. -type txApiDecoder func(txBytes []byte) (*apitx.Tx, error) +type txDecoder func(txBytes []byte) (Tx, error) // txApiEncoder is a function type that marshals a transaction into bytes. -type txApiEncoder func(tx *apitx.Tx) ([]byte, error) +type txEncoder func(tx Tx) ([]byte, error) -// txDecoder decodes transaction bytes into an apitx.Tx structure. -func txDecoder(txBytes []byte) (*apitx.Tx, error) { - var tx apitx.Tx - return &tx, protov2.Unmarshal(txBytes, &tx) +// decodeTx decodes transaction bytes into an apitx.Tx structure. +func decodeTx(txBytes []byte) (Tx, error) { + return nil, errors.New("not implemented") } -// txEncoder encodes an apitx.Tx into bytes using protobuf marshaling options. -func txEncoder(tx *apitx.Tx) ([]byte, error) { - return marshalOption.Marshal(tx) +// encodeTx encodes an apitx.Tx into bytes using protobuf marshaling options. +func encodeTx(tx Tx) ([]byte, error) { + wTx, ok := tx.(*wrappedTx) + if !ok { + return nil, fmt.Errorf("unexpected tx type: %T", tx) + } + return marshalOption.Marshal(wTx.Tx) } -// txJsonDecoder decodes transaction bytes into an apitx.Tx structure using JSON format. -func txJsonDecoder(txBytes []byte) (*apitx.Tx, error) { - var tx apitx.Tx - return &tx, protojson.Unmarshal(txBytes, &tx) +// decodeJsonTx decodes transaction bytes into an apitx.Tx structure using JSON format. +func decodeJsonTx(txBytes []byte) (Tx, error) { + return nil, errors.New("not implemented") } -// txJsonEncoder encodes an apitx.Tx into bytes using JSON marshaling options. -func txJsonEncoder(tx *apitx.Tx) ([]byte, error) { - return jsonMarshalOptions.Marshal(tx) +// encodeJsonTx encodes an apitx.Tx into bytes using JSON marshaling options. +func encodeJsonTx(tx Tx) ([]byte, error) { + wTx, ok := tx.(*wrappedTx) + if !ok { + return nil, fmt.Errorf("unexpected tx type: %T", tx) + } + return jsonMarshalOptions.Marshal(wTx.Tx) } diff --git a/client/v2/tx/encoder_test.go b/client/v2/tx/encoder_test.go index 63cdd9ab3a9a..4d1a78945de6 100644 --- a/client/v2/tx/encoder_test.go +++ b/client/v2/tx/encoder_test.go @@ -4,60 +4,73 @@ import ( "testing" "github.com/stretchr/testify/require" - "google.golang.org/protobuf/types/known/anypb" - apitx "cosmossdk.io/api/cosmos/tx/v1beta1" + base "cosmossdk.io/api/cosmos/base/v1beta1" + apisigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + "cosmossdk.io/core/transaction" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + countertypes "github.com/cosmos/cosmos-sdk/testutil/x/counter/types" ) +func getWrappedTx(t *testing.T) *wrappedTx { + t.Helper() + + pk := secp256k1.GenPrivKey().PubKey() + addr, _ := ac.BytesToString(pk.Address()) + b := newTxBuilder(ac, decoder, cdc) + + err := b.SetMsgs([]transaction.Msg{&countertypes.MsgIncreaseCounter{ + Signer: addr, + Count: 0, + }}...) + require.NoError(t, err) + + err = b.SetFeePayer(addr) + require.NoError(t, err) + + b.SetFeeAmount([]*base.Coin{{ + Denom: "cosmos", + Amount: "1000", + }}) + + err = b.SetSignatures([]Signature{{ + PubKey: pk, + Data: &SingleSignatureData{ + SignMode: apisigning.SignMode_SIGN_MODE_DIRECT, + Signature: nil, + }, + Sequence: 0, + }}...) + require.NoError(t, err) + wTx, err := b.getTx() + return wTx +} + func Test_txEncoder_txDecoder(t *testing.T) { tests := []struct { name string - tx *apitx.Tx }{ { - name: "encode and tx", - tx: &apitx.Tx{ - Body: &apitx.TxBody{ - Messages: []*anypb.Any{{ - TypeUrl: "/test/decode", - Value: []byte("foo"), - }}, - Memo: "memo", - TimeoutHeight: 1, - Unordered: false, - ExtensionOptions: nil, - NonCriticalExtensionOptions: nil, - }, - AuthInfo: &apitx.AuthInfo{ - SignerInfos: []*apitx.SignerInfo{ - { - PublicKey: &anypb.Any{ - TypeUrl: "customKey", - Value: []byte("key"), - }, - Sequence: 1, - }, - }, - Fee: nil, - }, - Signatures: nil, - }, + name: "encode tx", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - encodedTx, err := txEncoder(tt.tx) + wTx := getWrappedTx(t) + + encodedTx, err := encodeTx(wTx) require.NoError(t, err) require.NotNil(t, encodedTx) - isDeterministic, err := txEncoder(tt.tx) + isDeterministic, err := encodeTx(wTx) require.NoError(t, err) require.NotNil(t, encodedTx) require.Equal(t, encodedTx, isDeterministic) - decodedTx, err := txDecoder(encodedTx) - require.NoError(t, err) - require.NotNil(t, decodedTx) + //decodedTx, err := decodeTx(encodedTx) + //require.NoError(t, err) + //require.NotNil(t, decodedTx) }) } } @@ -65,39 +78,20 @@ func Test_txEncoder_txDecoder(t *testing.T) { func Test_txJsonEncoder_txJsonDecoder(t *testing.T) { tests := []struct { name string - tx *apitx.Tx }{ { name: "json encode and decode tx", - tx: &apitx.Tx{ - Body: &apitx.TxBody{ - Messages: []*anypb.Any{}, - Memo: "memo", - TimeoutHeight: 1, - Unordered: false, - ExtensionOptions: nil, - NonCriticalExtensionOptions: nil, - }, - AuthInfo: &apitx.AuthInfo{ - SignerInfos: []*apitx.SignerInfo{ - { - PublicKey: &anypb.Any{}, - Sequence: 1, - }, - }, - Fee: nil, - }, - Signatures: nil, - }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - encodedTx, err := txJsonEncoder(tt.tx) + wTx := getWrappedTx(t) + + encodedTx, err := encodeJsonTx(wTx) require.NoError(t, err) require.NotNil(t, encodedTx) - decodedTx, err := txJsonDecoder(encodedTx) + decodedTx, err := decodeJsonTx(encodedTx) require.NoError(t, err) require.NotNil(t, decodedTx) }) diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index 5ffdf95236d0..ed6b9f5a3d7e 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -328,11 +328,7 @@ func (f *Factory) Sign(ctx context.Context, txBuilder TxBuilder, overwriteSig bo return err } - txWrap := wrappedTx{ - tx: tx, - cdc: f.cdc, - } - prevSignatures, err = txWrap.GetSignatures() + prevSignatures, err = tx.GetSignatures() if err != nil { return err } @@ -353,12 +349,8 @@ func (f *Factory) Sign(ctx context.Context, txBuilder TxBuilder, overwriteSig bo if err != nil { return err } - txWrap := wrappedTx{ - tx: tx, - cdc: f.cdc, - } - if err := checkMultipleSigners(txWrap); err != nil { + if err := checkMultipleSigners(tx); err != nil { return err } diff --git a/client/v2/tx/types.go b/client/v2/tx/types.go index 68f7ba7893f6..85cc11e129cf 100644 --- a/client/v2/tx/types.go +++ b/client/v2/tx/types.go @@ -1,22 +1,15 @@ package tx import ( - "errors" "fmt" - "reflect" - "strings" - "github.com/cosmos/gogoproto/proto" gogoany "github.com/cosmos/gogoproto/types/any" - "google.golang.org/protobuf/types/known/anypb" - + base "cosmossdk.io/api/cosmos/base/v1beta1" apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" - apitx "cosmossdk.io/api/cosmos/tx/v1beta1" "cosmossdk.io/client/v2/internal/coins" "cosmossdk.io/core/transaction" - "github.com/cosmos/cosmos-sdk/codec" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" ) @@ -138,8 +131,8 @@ func (gr GasEstimateResponse) String() string { // Tx defines the interface for transaction operations. type Tx interface { - // GetMsgs retrieves the messages included in the transaction. - GetMsgs() ([]transaction.Msg, error) + transaction.Tx + // GetSigners fetches the addresses of the signers of the transaction. GetSigners() ([][]byte, error) // GetPubKeys retrieves the public keys of the signers of the transaction. @@ -147,94 +140,3 @@ type Tx interface { // GetSignatures fetches the signatures attached to the transaction. GetSignatures() ([]Signature, error) } - -// wrappedTx wraps a transaction and provides a codec for binary encoding/decoding. -type wrappedTx struct { - tx *apitx.Tx // tx is the transaction being wrapped. - cdc codec.BinaryCodec // cdc is the codec used for binary encoding/decoding. -} - -// GetMsgs retrieves the messages included in the transaction. -func (w wrappedTx) GetMsgs() ([]transaction.Msg, error) { - return nil, errors.New("not implemented") -} - -// GetSigners fetches the addresses of the signers of the transaction. -func (w wrappedTx) GetSigners() ([][]byte, error) { - return nil, errors.New("not implemented") -} - -// GetPubKeys retrieves the public keys of the signers from the transaction's SignerInfos. -func (w wrappedTx) GetPubKeys() ([]cryptotypes.PubKey, error) { - signerInfos := w.tx.AuthInfo.SignerInfos - pks := make([]cryptotypes.PubKey, len(signerInfos)) - - for i, si := range signerInfos { - // NOTE: it is okay to leave this nil if there is no PubKey in the SignerInfo. - // PubKey's can be left unset in SignerInfo. - if si.PublicKey == nil { - continue - } - maybePk, err := w.decodeAny(si.PublicKey) - if err != nil { - return nil, err - } - pk, ok := maybePk.(cryptotypes.PubKey) - if !ok { - return nil, fmt.Errorf("invalid public key type: %T", maybePk) - } - pks[i] = pk - } - - return pks, nil -} - -// GetSignatures fetches the signatures attached to the transaction. -func (w wrappedTx) GetSignatures() ([]Signature, error) { - signerInfos := w.tx.AuthInfo.SignerInfos - sigs := w.tx.Signatures - - pubKeys, err := w.GetPubKeys() - if err != nil { - return nil, err - } - signatures := make([]Signature, len(sigs)) - - for i, si := range signerInfos { - if si.ModeInfo == nil || si.ModeInfo.Sum == nil { - signatures[i] = Signature{ - PubKey: pubKeys[i], - } - } else { - sigData, err := ModeInfoAndSigToSignatureData(si.ModeInfo, sigs[i]) - if err != nil { - return nil, err - } - signatures[i] = Signature{ - PubKey: pubKeys[i], - Data: sigData, - Sequence: si.GetSequence(), - } - } - } - - return signatures, nil -} - -// decodeAny decodes a protobuf Any message into a concrete proto.Message. -func (w wrappedTx) decodeAny(anyPb *anypb.Any) (proto.Message, error) { - name := anyPb.GetTypeUrl() - if i := strings.LastIndexByte(name, '/'); i >= 0 { - name = name[i+len("/"):] - } - typ := proto.MessageType(name) - if typ == nil { - return nil, fmt.Errorf("unknown type: %s", name) - } - v1 := reflect.New(typ.Elem()).Interface().(proto.Message) - err := w.cdc.Unmarshal(anyPb.GetValue(), v1) - if err != nil { - return nil, err - } - return v1, nil -} diff --git a/client/v2/tx/wrapper.go b/client/v2/tx/wrapper.go new file mode 100644 index 000000000000..023ae764552e --- /dev/null +++ b/client/v2/tx/wrapper.go @@ -0,0 +1,115 @@ +package tx + +import ( + "fmt" + "reflect" + "strings" + + "github.com/cosmos/gogoproto/proto" + "google.golang.org/protobuf/types/known/anypb" + + "cosmossdk.io/core/transaction" + "cosmossdk.io/x/tx/decode" + + "github.com/cosmos/cosmos-sdk/codec" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" +) + +var ( + _ transaction.Tx = wrappedTx{} + _ Tx = wrappedTx{} +) + +// wrappedTx wraps a transaction and provides a codec for binary encoding/decoding. +type wrappedTx struct { + *decode.DecodedTx + + cdc codec.BinaryCodec +} + +func newWrapperTx(cdc codec.BinaryCodec, decodedTx *decode.DecodedTx) *wrappedTx { + return &wrappedTx{ + DecodedTx: decodedTx, + cdc: cdc, + } +} + +// GetSigners fetches the addresses of the signers of the transaction. +func (w wrappedTx) GetSigners() ([][]byte, error) { + return w.Signers, nil +} + +// GetPubKeys retrieves the public keys of the signers from the transaction's SignerInfos. +func (w wrappedTx) GetPubKeys() ([]cryptotypes.PubKey, error) { + signerInfos := w.Tx.AuthInfo.SignerInfos + pks := make([]cryptotypes.PubKey, len(signerInfos)) + + for i, si := range signerInfos { + // NOTE: it is okay to leave this nil if there is no PubKey in the SignerInfo. + // PubKey's can be left unset in SignerInfo. + if si.PublicKey == nil { + continue + } + maybePk, err := w.decodeAny(si.PublicKey) + if err != nil { + return nil, err + } + pk, ok := maybePk.(cryptotypes.PubKey) + if !ok { + return nil, fmt.Errorf("invalid public key type: %T", maybePk) + } + pks[i] = pk + } + + return pks, nil +} + +// GetSignatures fetches the signatures attached to the transaction. +func (w wrappedTx) GetSignatures() ([]Signature, error) { + signerInfos := w.Tx.AuthInfo.SignerInfos + sigs := w.Tx.Signatures + + pubKeys, err := w.GetPubKeys() + if err != nil { + return nil, err + } + signatures := make([]Signature, len(sigs)) + + for i, si := range signerInfos { + if si.ModeInfo == nil || si.ModeInfo.Sum == nil { + signatures[i] = Signature{ + PubKey: pubKeys[i], + } + } else { + sigData, err := ModeInfoAndSigToSignatureData(si.ModeInfo, sigs[i]) + if err != nil { + return nil, err + } + signatures[i] = Signature{ + PubKey: pubKeys[i], + Data: sigData, + Sequence: si.GetSequence(), + } + } + } + + return signatures, nil +} + +// decodeAny decodes a protobuf Any message into a concrete proto.Message. +func (w wrappedTx) decodeAny(anyPb *anypb.Any) (proto.Message, error) { + name := anyPb.GetTypeUrl() + if i := strings.LastIndexByte(name, '/'); i >= 0 { + name = name[i+len("/"):] + } + typ := proto.MessageType(name) + if typ == nil { + return nil, fmt.Errorf("unknown type: %s", name) + } + v1 := reflect.New(typ.Elem()).Interface().(proto.Message) + err := w.cdc.Unmarshal(anyPb.GetValue(), v1) + if err != nil { + return nil, err + } + return v1, nil +} From 4b55c930cbc8f4d1a351eae17a4805e441985137 Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Mon, 8 Jul 2024 10:56:55 +0200 Subject: [PATCH 22/36] update: encoder decoder --- client/v2/tx/aux_builder_test.go | 10 ++--- client/v2/tx/config.go | 16 +++++--- client/v2/tx/encoder.go | 67 +++++++++++++++++++++++++++++--- client/v2/tx/encoder_test.go | 53 +++++++++++++------------ 4 files changed, 105 insertions(+), 41 deletions(-) diff --git a/client/v2/tx/aux_builder_test.go b/client/v2/tx/aux_builder_test.go index 5ea22d47fcb7..a560b2f111cb 100644 --- a/client/v2/tx/aux_builder_test.go +++ b/client/v2/tx/aux_builder_test.go @@ -1,4 +1,4 @@ -package tx_test +package tx import ( "testing" @@ -8,17 +8,14 @@ import ( apisigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" apitx "cosmossdk.io/api/cosmos/tx/v1beta1" - "cosmossdk.io/client/v2/tx" "cosmossdk.io/core/transaction" "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/codec/testutil" codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "github.com/cosmos/cosmos-sdk/testutil/testdata" "github.com/cosmos/cosmos-sdk/testutil/x/counter" countertypes "github.com/cosmos/cosmos-sdk/testutil/x/counter/types" - moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" ) const ( @@ -36,14 +33,13 @@ var ( func TestAuxTxBuilder(t *testing.T) { counterModule := counter.AppModule{} - cdc := moduletestutil.MakeTestEncodingConfig(testutil.CodecOptions{}, counterModule).Codec reg := codectypes.NewInterfaceRegistry() testdata.RegisterInterfaces(reg) // required for test case: "GetAuxSignerData works for DIRECT_AUX" counterModule.RegisterInterfaces(reg) - var b tx.AuxTxBuilder + var b AuxTxBuilder testcases := []struct { name string @@ -217,7 +213,7 @@ func TestAuxTxBuilder(t *testing.T) { for _, tc := range testcases { tc := tc t.Run(tc.name, func(t *testing.T) { - b = tx.NewAuxTxBuilder() + b = NewAuxTxBuilder() err := tc.malleate() if tc.expErr { diff --git a/client/v2/tx/config.go b/client/v2/tx/config.go index 9acfc04c9785..94aba97421c5 100644 --- a/client/v2/tx/config.go +++ b/client/v2/tx/config.go @@ -135,13 +135,19 @@ func NewTxConfig(options ConfigOptions) (TxConfig, error) { return &txConfig{ TxBuilderProvider: NewBuilderProvider(options.AddressCodec, options.Decoder, options.Cdc), - TxEncodingConfig: defaultEncodingConfig{}, - TxSigningConfig: signingCtx, + TxEncodingConfig: defaultEncodingConfig{ + cdc: options.Cdc, + decoder: options.Decoder, + }, + TxSigningConfig: signingCtx, }, nil } // defaultEncodingConfig is an empty struct that implements the TxEncodingConfig interface. -type defaultEncodingConfig struct{} +type defaultEncodingConfig struct { + cdc codec.BinaryCodec + decoder Decoder +} // TxEncoder returns the default transaction encoder. func (t defaultEncodingConfig) TxEncoder() txEncoder { @@ -150,7 +156,7 @@ func (t defaultEncodingConfig) TxEncoder() txEncoder { // TxDecoder returns the default transaction decoder. func (t defaultEncodingConfig) TxDecoder() txDecoder { - return decodeTx + return decodeTx(t.cdc, t.decoder) } // TxJSONEncoder returns the default JSON transaction encoder. @@ -160,7 +166,7 @@ func (t defaultEncodingConfig) TxJSONEncoder() txEncoder { // TxJSONDecoder returns the default JSON transaction decoder. func (t defaultEncodingConfig) TxJSONDecoder() txDecoder { - return decodeJsonTx + return decodeJsonTx(t.cdc, t.decoder) } // defaultTxSigningConfig is a struct that holds the signing context and handler map. diff --git a/client/v2/tx/encoder.go b/client/v2/tx/encoder.go index dda3c0317575..ba1581455c10 100644 --- a/client/v2/tx/encoder.go +++ b/client/v2/tx/encoder.go @@ -1,13 +1,15 @@ package tx import ( - "errors" "fmt" "google.golang.org/protobuf/encoding/protojson" protov2 "google.golang.org/protobuf/proto" + txv1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1" txdecode "cosmossdk.io/x/tx/decode" + + "github.com/cosmos/cosmos-sdk/codec" ) var ( @@ -34,8 +36,25 @@ type txDecoder func(txBytes []byte) (Tx, error) type txEncoder func(tx Tx) ([]byte, error) // decodeTx decodes transaction bytes into an apitx.Tx structure. -func decodeTx(txBytes []byte) (Tx, error) { - return nil, errors.New("not implemented") +func decodeTx(cdc codec.BinaryCodec, decoder Decoder) txDecoder { + return func(txBytes []byte) (Tx, error) { + tx := new(txv1beta1.Tx) + err := protov2.Unmarshal(txBytes, tx) + if err != nil { + return nil, err + } + + pTxBytes, err := protoTxBytes(tx) + if err != nil { + return nil, err + } + + decodedTx, err := decoder.Decode(pTxBytes) + if err != nil { + return nil, err + } + return newWrapperTx(cdc, decodedTx), nil + } } // encodeTx encodes an apitx.Tx into bytes using protobuf marshaling options. @@ -48,8 +67,28 @@ func encodeTx(tx Tx) ([]byte, error) { } // decodeJsonTx decodes transaction bytes into an apitx.Tx structure using JSON format. -func decodeJsonTx(txBytes []byte) (Tx, error) { - return nil, errors.New("not implemented") +func decodeJsonTx(cdc codec.BinaryCodec, decoder Decoder) txDecoder { + return func(txBytes []byte) (Tx, error) { + jsonTx := new(txv1beta1.Tx) + err := protojson.UnmarshalOptions{ + AllowPartial: false, + DiscardUnknown: false, + }.Unmarshal(txBytes, jsonTx) + if err != nil { + return nil, err + } + + pTxBytes, err := protoTxBytes(jsonTx) + if err != nil { + return nil, err + } + + decodedTx, err := decoder.Decode(pTxBytes) + if err != nil { + return nil, err + } + return newWrapperTx(cdc, decodedTx), nil + } } // encodeJsonTx encodes an apitx.Tx into bytes using JSON marshaling options. @@ -60,3 +99,21 @@ func encodeJsonTx(tx Tx) ([]byte, error) { } return jsonMarshalOptions.Marshal(wTx.Tx) } + +func protoTxBytes(tx *txv1beta1.Tx) ([]byte, error) { + bodyBytes, err := marshalOption.Marshal(tx.Body) + if err != nil { + return nil, err + } + + authInfoBytes, err := marshalOption.Marshal(tx.AuthInfo) + if err != nil { + return nil, err + } + + return marshalOption.Marshal(&txv1beta1.TxRaw{ + BodyBytes: bodyBytes, + AuthInfoBytes: authInfoBytes, + Signatures: tx.Signatures, + }) +} diff --git a/client/v2/tx/encoder_test.go b/client/v2/tx/encoder_test.go index 4d1a78945de6..18e51571776b 100644 --- a/client/v2/tx/encoder_test.go +++ b/client/v2/tx/encoder_test.go @@ -6,11 +6,11 @@ import ( "github.com/stretchr/testify/require" base "cosmossdk.io/api/cosmos/base/v1beta1" + countertypes "cosmossdk.io/api/cosmos/counter/v1" apisigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" "cosmossdk.io/core/transaction" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - countertypes "github.com/cosmos/cosmos-sdk/testutil/x/counter/types" ) func getWrappedTx(t *testing.T) *wrappedTx { @@ -48,31 +48,28 @@ func getWrappedTx(t *testing.T) *wrappedTx { } func Test_txEncoder_txDecoder(t *testing.T) { - tests := []struct { - name string - }{ - { - name: "encode tx", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - wTx := getWrappedTx(t) + wTx := getWrappedTx(t) - encodedTx, err := encodeTx(wTx) - require.NoError(t, err) - require.NotNil(t, encodedTx) + encodedTx, err := encodeTx(wTx) + require.NoError(t, err) + require.NotNil(t, encodedTx) - isDeterministic, err := encodeTx(wTx) - require.NoError(t, err) - require.NotNil(t, encodedTx) - require.Equal(t, encodedTx, isDeterministic) + isDeterministic, err := encodeTx(wTx) + require.NoError(t, err) + require.NotNil(t, encodedTx) + require.Equal(t, encodedTx, isDeterministic) - //decodedTx, err := decodeTx(encodedTx) - //require.NoError(t, err) - //require.NotNil(t, decodedTx) - }) - } + f := decodeTx(cdc, decoder) + decodedTx, err := f(encodedTx) + require.NoError(t, err) + require.NotNil(t, decodedTx) + + dTx, ok := decodedTx.(*wrappedTx) + require.True(t, ok) + require.Equal(t, wTx.TxRaw, dTx.TxRaw) + require.Equal(t, wTx.Tx.AuthInfo.String(), dTx.Tx.AuthInfo.String()) + require.Equal(t, wTx.Tx.Body.String(), dTx.Tx.Body.String()) + require.Equal(t, wTx.Tx.Signatures, dTx.Tx.Signatures) } func Test_txJsonEncoder_txJsonDecoder(t *testing.T) { @@ -91,9 +88,17 @@ func Test_txJsonEncoder_txJsonDecoder(t *testing.T) { require.NoError(t, err) require.NotNil(t, encodedTx) - decodedTx, err := decodeJsonTx(encodedTx) + f := decodeJsonTx(cdc, decoder) + decodedTx, err := f(encodedTx) require.NoError(t, err) require.NotNil(t, decodedTx) + + dTx, ok := decodedTx.(*wrappedTx) + require.True(t, ok) + require.Equal(t, wTx.TxRaw, dTx.TxRaw) + require.Equal(t, wTx.Tx.AuthInfo.String(), dTx.Tx.AuthInfo.String()) + require.Equal(t, wTx.Tx.Body.String(), dTx.Tx.Body.String()) + require.Equal(t, wTx.Tx.Signatures, dTx.Tx.Signatures) }) } } From 3db0babfdb15306e591981a94692f629481ddca7 Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Mon, 8 Jul 2024 11:01:04 +0200 Subject: [PATCH 23/36] lint :) --- client/v2/tx/builder_test.go | 8 ++++---- client/v2/tx/encoder_test.go | 1 + client/v2/tx/types.go | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/client/v2/tx/builder_test.go b/client/v2/tx/builder_test.go index 6588c4a47af0..2455503d9e0e 100644 --- a/client/v2/tx/builder_test.go +++ b/client/v2/tx/builder_test.go @@ -1,18 +1,19 @@ package tx import ( - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" "testing" + "github.com/stretchr/testify/require" + base "cosmossdk.io/api/cosmos/base/v1beta1" apisigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" "cosmossdk.io/core/address" "cosmossdk.io/core/transaction" txdecode "cosmossdk.io/x/tx/decode" - "github.com/stretchr/testify/require" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" @@ -122,7 +123,7 @@ func Test_txBuilder_GetTx(t *testing.T) { checkResult: func(tx Tx) { wTx, ok := tx.(*wrappedTx) require.True(t, ok) - //require.Equal(t, []*anypb.Any(nil), wTx.Tx.Body.Messages) + // require.Equal(t, []*anypb.Any(nil), wTx.Tx.Body.Messages) require.Nil(t, wTx.Tx.Body.Messages) require.Empty(t, wTx.Tx.Body.Memo) require.Equal(t, uint64(0), wTx.Tx.Body.TimeoutHeight) @@ -180,7 +181,6 @@ func Test_txBuilder_GetTx(t *testing.T) { require.NotNil(t, wTx.Tx.AuthInfo.Fee.Amount) require.NotNil(t, wTx.Tx.Signatures) - }, }, } diff --git a/client/v2/tx/encoder_test.go b/client/v2/tx/encoder_test.go index 18e51571776b..e06c2206f850 100644 --- a/client/v2/tx/encoder_test.go +++ b/client/v2/tx/encoder_test.go @@ -44,6 +44,7 @@ func getWrappedTx(t *testing.T) *wrappedTx { }}...) require.NoError(t, err) wTx, err := b.getTx() + require.NoError(t, err) return wTx } diff --git a/client/v2/tx/types.go b/client/v2/tx/types.go index 85cc11e129cf..23cafbaef875 100644 --- a/client/v2/tx/types.go +++ b/client/v2/tx/types.go @@ -4,7 +4,7 @@ import ( "fmt" gogoany "github.com/cosmos/gogoproto/types/any" - + base "cosmossdk.io/api/cosmos/base/v1beta1" apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" "cosmossdk.io/client/v2/internal/coins" From 87677d78b365bfce3535f8b06f50d7f763254fc5 Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Mon, 8 Jul 2024 12:51:26 +0200 Subject: [PATCH 24/36] add: readme with diagrams --- client/v2/tx/README.md | 528 ++++++++++++++++++++++++++++++++++++++++ client/v2/tx/factory.go | 27 +- client/v2/tx/tx.go | 6 +- 3 files changed, 533 insertions(+), 28 deletions(-) create mode 100644 client/v2/tx/README.md diff --git a/client/v2/tx/README.md b/client/v2/tx/README.md new file mode 100644 index 000000000000..1115737cb621 --- /dev/null +++ b/client/v2/tx/README.md @@ -0,0 +1,528 @@ +The tx package provides a robust set of tools for building, signing, and managing transactions in a Cosmos SDK-based blockchain application. + +## Overview + +This package includes several key components: + +1. Transaction Factory +2. Transaction Builder +3. Transaction Config +4. Transaction Encoder/Decoder +5. Signature Handling + +## Architecture + +```mermaid +graph TD + A[Client] --> B[Factory] + B --> C[TxBuilder] + B --> D[TxConfig] + D --> E[TxEncodingConfig] + D --> F[TxSigningConfig] + C --> G[Tx] + G --> H[Encoder] + G --> I[Decoder] + F --> J[SignModeHandler] + F --> K[SigningContext] +``` + +## Key Components + +### TxConfig + +`TxConfig` provides configuration for transaction handling, including: + +- Encoding and decoding +- Sign mode handling +- Signature JSON marshaling/unmarshaling + +```mermaid +classDiagram + class TxConfig { + <> + +TxEncodingConfig + +TxSigningConfig + +TxBuilderProvider + } + + class TxEncodingConfig { + <> + +TxEncoder() txEncoder + +TxDecoder() txDecoder + +TxJSONEncoder() txEncoder + +TxJSONDecoder() txDecoder + } + + class TxSigningConfig { + <> + +SignModeHandler() *signing.HandlerMap + +SigningContext() *signing.Context + +MarshalSignatureJSON([]Signature) ([]byte, error) + +UnmarshalSignatureJSON([]byte) ([]Signature, error) + } + + class TxBuilderProvider { + <> + } + + class txConfig { + +TxBuilderProvider + +TxEncodingConfig + +TxSigningConfig + } + + class defaultEncodingConfig { + -cdc codec.BinaryCodec + -decoder Decoder + +TxEncoder() txEncoder + +TxDecoder() txDecoder + +TxJSONEncoder() txEncoder + +TxJSONDecoder() txDecoder + } + + class defaultTxSigningConfig { + -signingCtx *signing.Context + -handlerMap *signing.HandlerMap + -cdc codec.BinaryCodec + +SignModeHandler() *signing.HandlerMap + +SigningContext() *signing.Context + +MarshalSignatureJSON([]Signature) ([]byte, error) + +UnmarshalSignatureJSON([]byte) ([]Signature, error) + } + + TxConfig <|-- txConfig + TxEncodingConfig <|.. defaultEncodingConfig + TxSigningConfig <|.. defaultTxSigningConfig + txConfig *-- TxBuilderProvider + txConfig *-- defaultEncodingConfig + txConfig *-- defaultTxSigningConfig +``` + +### TxBuilder + +`TxBuilder` is responsible for constructing the transaction. + +```mermaid +classDiagram + class TxBuilder { + <> + +GetTx() (Tx, error) + +GetSigningTxData() (*signing.TxData, error) + +SetMsgs(...transaction.Msg) error + +SetMemo(string) + +SetFeeAmount([]*base.Coin) + +SetGasLimit(uint64) + +SetTimeoutHeight(uint64) + +SetFeePayer(string) error + +SetFeeGranter(string) error + +SetUnordered(bool) + +SetSignatures(...Signature) error + } + + class ExtendedTxBuilder { + <> + +SetExtensionOptions(...*gogoany.Any) + } + + class txBuilder { + -addressCodec address.Codec + -decoder Decoder + -codec codec.BinaryCodec + -msgs []transaction.Msg + -timeoutHeight uint64 + -granter []byte + -payer []byte + -unordered bool + -memo string + -gasLimit uint64 + -fees []*base.Coin + -signerInfos []*apitx.SignerInfo + -signatures [][]byte + -extensionOptions []*anypb.Any + -nonCriticalExtensionOptions []*anypb.Any + +GetTx() (Tx, error) + +GetSigningTxData() (*signing.TxData, error) + +SetMsgs(...transaction.Msg) error + +SetMemo(string) + +SetFeeAmount([]*base.Coin) + +SetGasLimit(uint64) + +SetTimeoutHeight(uint64) + +SetFeePayer(string) error + +SetFeeGranter(string) error + +SetUnordered(bool) + +SetSignatures(...Signature) error + -getTx() (*wrappedTx, error) + -getFee() (*apitx.Fee, error) + } + + class TxBuilderProvider { + <> + +NewTxBuilder() TxBuilder + +WrapTxBuilder(*apitx.Tx) (TxBuilder, error) + } + + class BuilderProvider { + -addressCodec address.Codec + -decoder Decoder + -codec codec.BinaryCodec + +NewTxBuilder() TxBuilder + +WrapTxBuilder(*apitx.Tx) (TxBuilder, error) + } + + TxBuilder <|.. txBuilder : implements + ExtendedTxBuilder <|.. txBuilder : implements + TxBuilderProvider <|.. BuilderProvider : implements + BuilderProvider ..> txBuilder : creates +``` + +### Factory + +The `Factory` is the main entry point for creating and managing transactions. It handles: + +- Account preparation +- Gas calculation +- Transaction simulation +- Unsigned transaction building + +```mermaid +classDiagram + class Factory { + -keybase keyring.Keyring + -cdc codec.BinaryCodec + -accountRetriever account.AccountRetriever + -ac address.Codec + -conn gogogrpc.ClientConn + -txConfig TxConfig + -txParams TxParameters + + +NewFactory(keybase, cdc, accRetriever, txConfig, ac, conn, parameters) Factory + +Prepare() error + +BuildUnsignedTx(msgs ...transaction.Msg) (TxBuilder, error) + +calculateGas(msgs ...transaction.Msg) error + +Simulate(msgs ...transaction.Msg) (*apitx.SimulateResponse, uint64, error) + +UnsignedTxString(msgs ...transaction.Msg) (string, error) + +BuildSimTx(msgs ...transaction.Msg) ([]byte, error) + +Sign(ctx context.Context, txBuilder TxBuilder, overwriteSig bool) error + +WithGas(gas uint64) + +WithSequence(sequence uint64) + +WithAccountNumber(accnum uint64) + +PreprocessTx(keyname string, builder TxBuilder) error + +AccountNumber() uint64 + +Sequence() uint64 + +GasAdjustment() float64 + +Keybase() keyring.Keyring + +SimulateAndExecute() bool + +SignMode() apitxsigning.SignMode + -getSimPK() (cryptotypes.PubKey, error) + -getSimSignatureData(pk cryptotypes.PubKey) SignatureData + -getSignBytesAdapter(ctx context.Context, signerData signing.SignerData, builder TxBuilder) ([]byte, error) + } + + class TxParameters { + <> + } + + class TxConfig { + <> + } + + class TxBuilder { + <> + } + + Factory *-- TxParameters + Factory *-- TxConfig + Factory ..> TxBuilder : creates and uses +``` + +### Encoder/Decoder + +The package includes functions for encoding and decoding transactions in both binary and JSON formats. + +```mermaid +classDiagram + class Decoder { + <> + +Decode(txBytes []byte) (*txdecode.DecodedTx, error) + } + + class txDecoder { + <> + +decode(txBytes []byte) (Tx, error) + } + + class txEncoder { + <> + +encode(tx Tx) ([]byte, error) + } + + class EncoderUtils { + <> + +decodeTx(cdc codec.BinaryCodec, decoder Decoder) txDecoder + +encodeTx(tx Tx) ([]byte, error) + +decodeJsonTx(cdc codec.BinaryCodec, decoder Decoder) txDecoder + +encodeJsonTx(tx Tx) ([]byte, error) + -protoTxBytes(tx *txv1beta1.Tx) ([]byte, error) + } + + class MarshalOptions { + <> + +Deterministic bool + } + + class JSONMarshalOptions { + <> + +Indent string + +UseProtoNames bool + +UseEnumNumbers bool + } + + Decoder <.. EncoderUtils : uses + txDecoder <.. EncoderUtils : creates + txEncoder <.. EncoderUtils : implements + EncoderUtils ..> MarshalOptions : uses + EncoderUtils ..> JSONMarshalOptions : uses +``` + +### Sequence Diagrams + +#### Generate Aux Signer Data +```mermaid +sequenceDiagram + participant User + participant GenerateOrBroadcastTxCLI + participant generateAuxSignerData + participant makeAuxSignerData + participant AuxTxBuilder + participant ctx.PrintProto + + User->>GenerateOrBroadcastTxCLI: Call with isAux flag + GenerateOrBroadcastTxCLI->>generateAuxSignerData: Call + + generateAuxSignerData->>makeAuxSignerData: Call + makeAuxSignerData->>AuxTxBuilder: NewAuxTxBuilder() + + makeAuxSignerData->>AuxTxBuilder: SetAddress(f.txParams.fromAddress) + + alt f.txParams.offline + makeAuxSignerData->>AuxTxBuilder: SetAccountNumber(f.AccountNumber()) + makeAuxSignerData->>AuxTxBuilder: SetSequence(f.Sequence()) + else + makeAuxSignerData->>f.accountRetriever: GetAccountNumberSequence() + makeAuxSignerData->>AuxTxBuilder: SetAccountNumber(accNum) + makeAuxSignerData->>AuxTxBuilder: SetSequence(seq) + end + + makeAuxSignerData->>AuxTxBuilder: SetMsgs(msgs...) + makeAuxSignerData->>AuxTxBuilder: SetSignMode(f.SignMode()) + + makeAuxSignerData->>f.keybase: GetPubKey(f.txParams.fromName) + makeAuxSignerData->>AuxTxBuilder: SetPubKey(pubKey) + + makeAuxSignerData->>AuxTxBuilder: SetChainID(f.txParams.chainID) + makeAuxSignerData->>AuxTxBuilder: GetSignBytes() + + makeAuxSignerData->>f.keybase: Sign(f.txParams.fromName, signBz, f.SignMode()) + makeAuxSignerData->>AuxTxBuilder: SetSignature(sig) + + makeAuxSignerData->>AuxTxBuilder: GetAuxSignerData() + AuxTxBuilder-->>makeAuxSignerData: Return AuxSignerData + makeAuxSignerData-->>generateAuxSignerData: Return AuxSignerData + + generateAuxSignerData->>ctx.PrintProto: Print AuxSignerData + ctx.PrintProto-->>GenerateOrBroadcastTxCLI: Return result + GenerateOrBroadcastTxCLI-->>User: Return result +``` +#### Generate Only +```mermaid +sequenceDiagram + participant User + participant GenerateOrBroadcastTxCLI + participant generateOnly + participant Factory + participant TxBuilder + participant ctx.PrintString + + User->>GenerateOrBroadcastTxCLI: Call with generateOnly flag + GenerateOrBroadcastTxCLI->>generateOnly: Call + + generateOnly->>Factory: Prepare() + alt Error in Prepare + Factory-->>generateOnly: Return error + generateOnly-->>GenerateOrBroadcastTxCLI: Return error + GenerateOrBroadcastTxCLI-->>User: Return error + end + + generateOnly->>Factory: UnsignedTxString(msgs...) + Factory->>Factory: SimulateAndExecute() + alt SimulateAndExecute is true + Factory->>Factory: calculateGas(msgs...) + Factory->>Factory: Simulate(msgs...) + Factory->>Factory: WithGas(adjusted) + end + + Factory->>Factory: BuildUnsignedTx(msgs...) + Factory->>TxBuilder: NewTxBuilder() + Factory->>TxBuilder: SetMsgs(msgs...) + Factory->>TxBuilder: SetMemo(f.txParams.memo) + Factory->>TxBuilder: SetFeeAmount(fees) + Factory->>TxBuilder: SetGasLimit(f.txParams.gas) + Factory->>TxBuilder: SetFeeGranter(f.txParams.feeGranter) + Factory->>TxBuilder: SetFeePayer(f.txParams.feePayer) + Factory->>TxBuilder: SetTimeoutHeight(f.txParams.timeoutHeight) + + Factory->>TxBuilder: GetTx() + Factory->>Factory: txConfig.TxJSONEncoder() + Factory->>Factory: encoder(tx) + + Factory-->>generateOnly: Return unsigned tx string + generateOnly->>ctx.PrintString: Print unsigned tx string + ctx.PrintString-->>generateOnly: Return result + generateOnly-->>GenerateOrBroadcastTxCLI: Return result + GenerateOrBroadcastTxCLI-->>User: Return result +``` + +#### DryRun +```mermaid +sequenceDiagram + participant User + participant GenerateOrBroadcastTxCLI + participant dryRun + participant Factory + participant os.Stderr + + User->>GenerateOrBroadcastTxCLI: Call with dryRun flag + GenerateOrBroadcastTxCLI->>dryRun: Call + + dryRun->>Factory: Check txParams.offline + alt txParams.offline is true + Factory-->>dryRun: Return error (cannot use offline mode) + dryRun-->>GenerateOrBroadcastTxCLI: Return error + GenerateOrBroadcastTxCLI-->>User: Return error + end + + dryRun->>Factory: Prepare() + alt Error in Prepare + Factory-->>dryRun: Return error + dryRun-->>GenerateOrBroadcastTxCLI: Return error + GenerateOrBroadcastTxCLI-->>User: Return error + end + + dryRun->>Factory: Simulate(msgs...) + Factory->>Factory: BuildSimTx(msgs...) + Factory->>Factory: BuildUnsignedTx(msgs...) + Factory->>Factory: getSimPK() + Factory->>Factory: getSimSignatureData(pk) + Factory->>Factory: SetSignatures(sig) + Factory->>Factory: TxEncoder()(tx) + + Factory->>Factory: txConfig.SignModeHandler().GetSignBytes() + Factory->>Factory: keybase.Sign() + Factory->>Factory: SetSignatures() + + Factory->>ServiceClient: Simulate(context.Background(), &apitx.SimulateRequest{}) + ServiceClient->>Factory: Return result + + Factory-->>dryRun: Return (simulation, gas, error) + alt Error in Simulate + dryRun-->>GenerateOrBroadcastTxCLI: Return error + GenerateOrBroadcastTxCLI-->>User: Return error + end + + dryRun->>os.Stderr: Fprintf(GasEstimateResponse{GasEstimate: gas}) + os.Stderr-->>dryRun: Return result + dryRun-->>GenerateOrBroadcastTxCLI: Return result + GenerateOrBroadcastTxCLI-->>User: Return result +``` + +#### Generate and Broadcast Tx +```mermaid +sequenceDiagram + participant User + participant GenerateOrBroadcastTxCLI + participant BroadcastTx + participant Factory + participant TxBuilder + participant clientCtx + + User->>GenerateOrBroadcastTxCLI: Call + GenerateOrBroadcastTxCLI->>BroadcastTx: Call + + BroadcastTx->>Factory: Prepare() + alt Error in Prepare + Factory-->>BroadcastTx: Return error + BroadcastTx-->>GenerateOrBroadcastTxCLI: Return error + GenerateOrBroadcastTxCLI-->>User: Return error + end + + BroadcastTx->>Factory: SimulateAndExecute() + alt SimulateAndExecute is true + BroadcastTx->>Factory: calculateGas(msgs...) + Factory->>Factory: Simulate(msgs...) + Factory->>Factory: WithGas(adjusted) + end + + BroadcastTx->>Factory: BuildUnsignedTx(msgs...) + Factory->>TxBuilder: NewTxBuilder() + Factory->>TxBuilder: SetMsgs(msgs...) + Factory->>TxBuilder: SetMemo(memo) + Factory->>TxBuilder: SetFeeAmount(fees) + Factory->>TxBuilder: SetGasLimit(gas) + Factory->>TxBuilder: SetFeeGranter(feeGranter) + Factory->>TxBuilder: SetFeePayer(feePayer) + Factory->>TxBuilder: SetTimeoutHeight(timeoutHeight) + + alt !clientCtx.SkipConfirm + BroadcastTx->>TxBuilder: GetTx() + BroadcastTx->>Factory: txConfig.TxJSONEncoder() + BroadcastTx->>clientCtx: PrintRaw(txBytes) + BroadcastTx->>clientCtx: Input.GetConfirmation() + alt Not confirmed + BroadcastTx-->>GenerateOrBroadcastTxCLI: Return error + GenerateOrBroadcastTxCLI-->>User: Return error + end + end + + BroadcastTx->>Factory: Sign(ctx, builder, true) + Factory->>Factory: keybase.GetPubKey(fromName) + Factory->>Factory: getSignBytesAdapter() + Factory->>Factory: keybase.Sign(fromName, bytesToSign, signMode) + Factory->>TxBuilder: SetSignatures(sig) + + BroadcastTx->>TxBuilder: GetTx() + BroadcastTx->>Factory: txConfig.TxEncoder() + BroadcastTx->>clientCtx: BroadcastTx(txBytes) + + alt Error in BroadcastTx + clientCtx-->>BroadcastTx: Return error + BroadcastTx-->>GenerateOrBroadcastTxCLI: Return error + GenerateOrBroadcastTxCLI-->>User: Return error + end + + BroadcastTx->>clientCtx: OutputTx(res) + clientCtx-->>BroadcastTx: Return result + BroadcastTx-->>GenerateOrBroadcastTxCLI: Return result + GenerateOrBroadcastTxCLI-->>User: Return result +``` + +## Usage + +To use the `tx` package, typically you would: + +1. Create a `Factory` +2. Prepare the account +3. Build an unsigned transaction +4. Simulate the transaction (optional) +5. Sign the transaction +6. Broadcast the transaction + +Here's a simplified example: + +```go +factory, _ := NewFactory(...) +factory.Prepare() +txBuilder, _ := factory.BuildUnsignedTx(msgs...) +factory.Sign(ctx, txBuilder, true) +txBytes, _ := factory.txConfig.TxEncoder()(txBuilder.GetTx()) +// Broadcast txBytes +``` \ No newline at end of file diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index ed6b9f5a3d7e..bcbcd4fc12f7 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -43,7 +43,8 @@ type Factory struct { } // NewFactory returns a new instance of Factory. -func NewFactory(keybase keyring.Keyring, cdc codec.BinaryCodec, accRetriever account.AccountRetriever, txConfig TxConfig, ac address.Codec, conn gogogrpc.ClientConn, parameters TxParameters) (Factory, error) { +func NewFactory(keybase keyring.Keyring, cdc codec.BinaryCodec, accRetriever account.AccountRetriever, + txConfig TxConfig, ac address.Codec, conn gogogrpc.ClientConn, parameters TxParameters) (Factory, error) { return Factory{ keybase: keybase, cdc: cdc, @@ -439,36 +440,12 @@ func (f *Factory) AccountNumber() uint64 { return f.txParams.accountNumber } // Sequence returns the sequence number. func (f *Factory) Sequence() uint64 { return f.txParams.sequence } -// Gas returns the gas value. -func (f *Factory) Gas() uint64 { return f.txParams.gas } - // GasAdjustment returns the gas adjustment value. func (f *Factory) GasAdjustment() float64 { return f.txParams.gasAdjustment } // Keybase returns the keyring. func (f *Factory) Keybase() keyring.Keyring { return f.keybase } -// ChainID returns the chain ID. -func (f *Factory) ChainID() string { return f.txParams.chainID } - -// Memo returns the memo. -func (f *Factory) Memo() string { return f.txParams.memo } - -// Fees returns the fees. -func (f *Factory) Fees() []*base.Coin { return f.txParams.fees } - -// GasPrices returns the gas prices. -func (f *Factory) GasPrices() []*base.DecCoin { return f.txParams.gasPrices } - -// AccountRetriever returns the account retriever. -func (f *Factory) AccountRetriever() account.AccountRetriever { return f.accountRetriever } - -// TimeoutHeight returns the timeout height. -func (f *Factory) TimeoutHeight() uint64 { return f.txParams.timeoutHeight } - -// FromName returns the from name. -func (f *Factory) FromName() string { return f.txParams.fromName } - // SimulateAndExecute returns whether to simulate and execute. func (f *Factory) SimulateAndExecute() bool { return f.txParams.simulateAndExecute } diff --git a/client/v2/tx/tx.go b/client/v2/tx/tx.go index c70b2366afd8..53cab6a1411b 100644 --- a/client/v2/tx/tx.go +++ b/client/v2/tx/tx.go @@ -211,7 +211,7 @@ func validateMessages(msgs ...transaction.Msg) error { // generateAuxSignerData simply generates and prints the AuxSignerData. func generateAuxSignerData(ctx client.Context, txf Factory, msgs ...transaction.Msg) error { - auxSignerData, err := makeAuxSignerData(ctx, txf, msgs...) + auxSignerData, err := makeAuxSignerData(txf, msgs...) if err != nil { return err } @@ -344,7 +344,7 @@ func BroadcastTx(clientCtx client.Context, txf Factory, msgs ...transaction.Msg) } // makeAuxSignerData generates an AuxSignerData from the client inputs. -func makeAuxSignerData(clientCtx client.Context, f Factory, msgs ...transaction.Msg) (*apitx.AuxSignerData, error) { +func makeAuxSignerData(f Factory, msgs ...transaction.Msg) (*apitx.AuxSignerData, error) { b := NewAuxTxBuilder() b.SetAddress(f.txParams.fromAddress) @@ -380,7 +380,7 @@ func makeAuxSignerData(clientCtx client.Context, f Factory, msgs ...transaction. return nil, err } - b.SetChainID(clientCtx.ChainID) + b.SetChainID(f.txParams.chainID) signBz, err := b.GetSignBytes() if err != nil { return nil, err From d3606701e6cad54d5dd390383264584a3b286831 Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Mon, 8 Jul 2024 12:52:43 +0200 Subject: [PATCH 25/36] lint --- client/v2/tx/factory.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index bcbcd4fc12f7..06769434a895 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -44,7 +44,8 @@ type Factory struct { // NewFactory returns a new instance of Factory. func NewFactory(keybase keyring.Keyring, cdc codec.BinaryCodec, accRetriever account.AccountRetriever, - txConfig TxConfig, ac address.Codec, conn gogogrpc.ClientConn, parameters TxParameters) (Factory, error) { + txConfig TxConfig, ac address.Codec, conn gogogrpc.ClientConn, parameters TxParameters, +) (Factory, error) { return Factory{ keybase: keybase, cdc: cdc, From 5a8aba97da6661f3a8a5f7534b9eaf4169960a00 Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Wed, 17 Jul 2024 18:15:31 +0200 Subject: [PATCH 26/36] update: buildSignedTx --- client/v2/tx/README.md | 233 ++++++++++++++++++----------------- client/v2/tx/factory.go | 91 ++++++++------ client/v2/tx/factory_test.go | 6 +- client/v2/tx/tx.go | 16 +-- 4 files changed, 183 insertions(+), 163 deletions(-) diff --git a/client/v2/tx/README.md b/client/v2/tx/README.md index 1115737cb621..e0e5a4842770 100644 --- a/client/v2/tx/README.md +++ b/client/v2/tx/README.md @@ -40,25 +40,25 @@ graph TD classDiagram class TxConfig { <> - +TxEncodingConfig - +TxSigningConfig - +TxBuilderProvider + TxEncodingConfig + TxSigningConfig + TxBuilderProvider } class TxEncodingConfig { <> - +TxEncoder() txEncoder - +TxDecoder() txDecoder - +TxJSONEncoder() txEncoder - +TxJSONDecoder() txDecoder + TxEncoder() txEncoder + TxDecoder() txDecoder + TxJSONEncoder() txEncoder + TxJSONDecoder() txDecoder } class TxSigningConfig { <> - +SignModeHandler() *signing.HandlerMap - +SigningContext() *signing.Context - +MarshalSignatureJSON([]Signature) ([]byte, error) - +UnmarshalSignatureJSON([]byte) ([]Signature, error) + SignModeHandler() *signing.HandlerMap + SigningContext() *signing.Context + MarshalSignatureJSON([]Signature) ([]byte, error) + UnmarshalSignatureJSON([]byte) ([]Signature, error) } class TxBuilderProvider { @@ -66,28 +66,28 @@ classDiagram } class txConfig { - +TxBuilderProvider - +TxEncodingConfig - +TxSigningConfig + TxBuilderProvider + TxEncodingConfig + TxSigningConfig } class defaultEncodingConfig { - -cdc codec.BinaryCodec - -decoder Decoder - +TxEncoder() txEncoder - +TxDecoder() txDecoder - +TxJSONEncoder() txEncoder - +TxJSONDecoder() txDecoder + cdc codec.BinaryCodec + decoder Decoder + TxEncoder() txEncoder + TxDecoder() txDecoder + TxJSONEncoder() txEncoder + TxJSONDecoder() txDecoder } class defaultTxSigningConfig { - -signingCtx *signing.Context - -handlerMap *signing.HandlerMap - -cdc codec.BinaryCodec - +SignModeHandler() *signing.HandlerMap - +SigningContext() *signing.Context - +MarshalSignatureJSON([]Signature) ([]byte, error) - +UnmarshalSignatureJSON([]byte) ([]Signature, error) + signingCtx *signing.Context + handlerMap *signing.HandlerMap + cdc codec.BinaryCodec + SignModeHandler() *signing.HandlerMap + SigningContext() *signing.Context + MarshalSignatureJSON([]Signature) ([]byte, error) + UnmarshalSignatureJSON([]byte) ([]Signature, error) } TxConfig <|-- txConfig @@ -106,17 +106,17 @@ classDiagram classDiagram class TxBuilder { <> - +GetTx() (Tx, error) - +GetSigningTxData() (*signing.TxData, error) - +SetMsgs(...transaction.Msg) error - +SetMemo(string) - +SetFeeAmount([]*base.Coin) - +SetGasLimit(uint64) - +SetTimeoutHeight(uint64) - +SetFeePayer(string) error - +SetFeeGranter(string) error - +SetUnordered(bool) - +SetSignatures(...Signature) error + GetTx() (Tx, error) + GetSigningTxData() (*signing.TxData, error) + SetMsgs(...transaction.Msg) error + SetMemo(string) + SetFeeAmount([]*base.Coin) + SetGasLimit(uint64) + SetTimeoutHeight(uint64) + SetFeePayer(string) error + SetFeeGranter(string) error + SetUnordered(bool) + SetSignatures(...Signature) error } class ExtendedTxBuilder { @@ -125,48 +125,48 @@ classDiagram } class txBuilder { - -addressCodec address.Codec - -decoder Decoder - -codec codec.BinaryCodec - -msgs []transaction.Msg - -timeoutHeight uint64 - -granter []byte - -payer []byte - -unordered bool - -memo string - -gasLimit uint64 - -fees []*base.Coin - -signerInfos []*apitx.SignerInfo - -signatures [][]byte - -extensionOptions []*anypb.Any - -nonCriticalExtensionOptions []*anypb.Any - +GetTx() (Tx, error) - +GetSigningTxData() (*signing.TxData, error) - +SetMsgs(...transaction.Msg) error - +SetMemo(string) - +SetFeeAmount([]*base.Coin) - +SetGasLimit(uint64) - +SetTimeoutHeight(uint64) - +SetFeePayer(string) error - +SetFeeGranter(string) error - +SetUnordered(bool) - +SetSignatures(...Signature) error - -getTx() (*wrappedTx, error) - -getFee() (*apitx.Fee, error) + addressCodec address.Codec + decoder Decoder + codec codec.BinaryCodec + msgs []transaction.Msg + timeoutHeight uint64 + granter []byte + payer []byte + unordered bool + memo string + gasLimit uint64 + fees []*base.Coin + signerInfos []*apitx.SignerInfo + signatures [][]byte + extensionOptions []*anypb.Any + nonCriticalExtensionOptions []*anypb.Any + GetTx() (Tx, error) + GetSigningTxData() (*signing.TxData, error) + SetMsgs(...transaction.Msg) error + SetMemo(string) + SetFeeAmount([]*base.Coin) + SetGasLimit(uint64) + SetTimeoutHeight(uint64) + SetFeePayer(string) error + SetFeeGranter(string) error + SetUnordered(bool) + SetSignatures(...Signature) error + getTx() (*wrappedTx, error) + getFee() (*apitx.Fee, error) } class TxBuilderProvider { <> - +NewTxBuilder() TxBuilder - +WrapTxBuilder(*apitx.Tx) (TxBuilder, error) + NewTxBuilder() TxBuilder + WrapTxBuilder(*apitx.Tx) (TxBuilder, error) } class BuilderProvider { - -addressCodec address.Codec - -decoder Decoder - -codec codec.BinaryCodec - +NewTxBuilder() TxBuilder - +WrapTxBuilder(*apitx.Tx) (TxBuilder, error) + addressCodec address.Codec + decoder Decoder + codec codec.BinaryCodec + NewTxBuilder() TxBuilder + WrapTxBuilder(*apitx.Tx) (TxBuilder, error) } TxBuilder <|.. txBuilder : implements @@ -187,35 +187,36 @@ The `Factory` is the main entry point for creating and managing transactions. It ```mermaid classDiagram class Factory { - -keybase keyring.Keyring - -cdc codec.BinaryCodec - -accountRetriever account.AccountRetriever - -ac address.Codec - -conn gogogrpc.ClientConn - -txConfig TxConfig - -txParams TxParameters - - +NewFactory(keybase, cdc, accRetriever, txConfig, ac, conn, parameters) Factory - +Prepare() error - +BuildUnsignedTx(msgs ...transaction.Msg) (TxBuilder, error) - +calculateGas(msgs ...transaction.Msg) error - +Simulate(msgs ...transaction.Msg) (*apitx.SimulateResponse, uint64, error) - +UnsignedTxString(msgs ...transaction.Msg) (string, error) - +BuildSimTx(msgs ...transaction.Msg) ([]byte, error) - +Sign(ctx context.Context, txBuilder TxBuilder, overwriteSig bool) error - +WithGas(gas uint64) - +WithSequence(sequence uint64) - +WithAccountNumber(accnum uint64) - +PreprocessTx(keyname string, builder TxBuilder) error - +AccountNumber() uint64 - +Sequence() uint64 - +GasAdjustment() float64 - +Keybase() keyring.Keyring - +SimulateAndExecute() bool - +SignMode() apitxsigning.SignMode - -getSimPK() (cryptotypes.PubKey, error) - -getSimSignatureData(pk cryptotypes.PubKey) SignatureData - -getSignBytesAdapter(ctx context.Context, signerData signing.SignerData, builder TxBuilder) ([]byte, error) + keybase keyring.Keyring + cdc codec.BinaryCodec + accountRetriever account.AccountRetriever + ac address.Codec + conn gogogrpc.ClientConn + txConfig TxConfig + txParams TxParameters + + NewFactory(keybase, cdc, accRetriever, txConfig, ac, conn, parameters) Factory + Prepare() error + BuildUnsignedTx(msgs ...transaction.Msg) (TxBuilder, error) + BuildsSignedTx(ctx context.Context, msgs ...transaction.Msg) (Tx, error) + calculateGas(msgs ...transaction.Msg) error + Simulate(msgs ...transaction.Msg) (*apitx.SimulateResponse, uint64, error) + UnsignedTxString(msgs ...transaction.Msg) (string, error) + BuildSimTx(msgs ...transaction.Msg) ([]byte, error) + sign(ctx context.Context, txBuilder TxBuilder, overwriteSig bool) (Tx, error) + WithGas(gas uint64) + WithSequence(sequence uint64) + WithAccountNumber(accnum uint64) + preprocessTx(keyname string, builder TxBuilder) error + accountNumber() uint64 + sequence() uint64 + GgasAdjustment() float64 + keyring() keyring.Keyring + simulateAndExecute() bool + signMode() apitxsigning.SignMode + getSimPK() (cryptotypes.PubKey, error) + getSimSignatureData(pk cryptotypes.PubKey) SignatureData + getSignBytesAdapter(ctx context.Context, signerData signing.SignerData, builder TxBuilder) ([]byte, error) } class TxParameters { @@ -243,38 +244,38 @@ The package includes functions for encoding and decoding transactions in both bi classDiagram class Decoder { <> - +Decode(txBytes []byte) (*txdecode.DecodedTx, error) + Decode(txBytes []byte) (*txdecode.DecodedTx, error) } class txDecoder { <> - +decode(txBytes []byte) (Tx, error) + decode(txBytes []byte) (Tx, error) } class txEncoder { <> - +encode(tx Tx) ([]byte, error) + encode(tx Tx) ([]byte, error) } class EncoderUtils { <> - +decodeTx(cdc codec.BinaryCodec, decoder Decoder) txDecoder - +encodeTx(tx Tx) ([]byte, error) - +decodeJsonTx(cdc codec.BinaryCodec, decoder Decoder) txDecoder - +encodeJsonTx(tx Tx) ([]byte, error) - -protoTxBytes(tx *txv1beta1.Tx) ([]byte, error) + decodeTx(cdc codec.BinaryCodec, decoder Decoder) txDecoder + encodeTx(tx Tx) ([]byte, error) + decodeJsonTx(cdc codec.BinaryCodec, decoder Decoder) txDecoder + encodeJsonTx(tx Tx) ([]byte, error) + protoTxBytes(tx *txv1beta1.Tx) ([]byte, error) } class MarshalOptions { <> - +Deterministic bool + Deterministic bool } class JSONMarshalOptions { <> - +Indent string - +UseProtoNames bool - +UseEnumNumbers bool + Indent string + UseProtoNames bool + UseEnumNumbers bool } Decoder <.. EncoderUtils : uses @@ -483,13 +484,13 @@ sequenceDiagram end end - BroadcastTx->>Factory: Sign(ctx, builder, true) + BroadcastTx->>Factory: sign(ctx, builder, true) Factory->>Factory: keybase.GetPubKey(fromName) Factory->>Factory: getSignBytesAdapter() Factory->>Factory: keybase.Sign(fromName, bytesToSign, signMode) Factory->>TxBuilder: SetSignatures(sig) + Factory->>TxBuilder: GetTx() - BroadcastTx->>TxBuilder: GetTx() BroadcastTx->>Factory: txConfig.TxEncoder() BroadcastTx->>clientCtx: BroadcastTx(txBytes) diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index 06769434a895..8992872644be 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -40,6 +40,8 @@ type Factory struct { conn gogogrpc.ClientConn txConfig TxConfig txParams TxParameters + + cachedTx TxBuilder } // NewFactory returns a new instance of Factory. @@ -162,6 +164,20 @@ func (f *Factory) BuildUnsignedTx(msgs ...transaction.Msg) (TxBuilder, error) { return txBuilder, nil } +func (f *Factory) BuildsSignedTx(ctx context.Context, msgs ...transaction.Msg) (Tx, error) { + err := f.Prepare() + if err != nil { + return nil, err + } + + tx, err := f.BuildUnsignedTx(msgs...) + if err != nil { + return nil, err + } + + return f.sign(ctx, tx, true) +} + // calculateGas calculates the gas required for the given messages. func (f *Factory) calculateGas(msgs ...transaction.Msg) error { if f.txParams.offline { @@ -193,7 +209,7 @@ func (f *Factory) Simulate(msgs ...transaction.Msg) (*apitx.SimulateResponse, ui return nil, 0, err } - return simRes, uint64(f.GasAdjustment() * float64(simRes.GasInfo.GasUsed)), nil + return simRes, uint64(f.gasAdjustment() * float64(simRes.GasInfo.GasUsed)), nil } // UnsignedTxString will generate an unsigned transaction and print it to the writer @@ -201,7 +217,7 @@ func (f *Factory) Simulate(msgs ...transaction.Msg) (*apitx.SimulateResponse, ui // simulated and also printed to the same writer before the transaction is // printed. func (f *Factory) UnsignedTxString(msgs ...transaction.Msg) (string, error) { - if f.SimulateAndExecute() { + if f.simulateAndExecute() { err := f.calculateGas(msgs...) if err != nil { return "", err @@ -250,7 +266,7 @@ func (f *Factory) BuildSimTx(msgs ...transaction.Msg) ([]byte, error) { sig := Signature{ PubKey: pk, Data: f.getSimSignatureData(pk), - Sequence: f.Sequence(), + Sequence: f.sequence(), } if err := txb.SetSignatures(sig); err != nil { return nil, err @@ -268,15 +284,15 @@ func (f *Factory) BuildSimTx(msgs ...transaction.Msg) ([]byte, error) { return encoder(tx) } -// Sign signs a given tx with a named key. The bytes signed over are canonical. +// sign signs a given tx with a named key. The bytes signed over are canonical. // The resulting signature will be added to the transaction builder overwriting the previous // ones if overwrite=true (otherwise, the signature will be appended). // Signing a transaction with multiple signers in the DIRECT mode is not supported and will // return an error. // An error is returned upon failure. -func (f *Factory) Sign(ctx context.Context, txBuilder TxBuilder, overwriteSig bool) error { +func (f *Factory) sign(ctx context.Context, txBuilder TxBuilder, overwriteSig bool) (Tx, error) { if f.keybase == nil { - return errors.New("keybase must be set prior to signing a transaction") + return nil, errors.New("keybase must be set prior to signing a transaction") } var err error @@ -286,12 +302,12 @@ func (f *Factory) Sign(ctx context.Context, txBuilder TxBuilder, overwriteSig bo pubKey, err := f.keybase.GetPubKey(f.txParams.fromName) if err != nil { - return err + return nil, err } addr, err := f.ac.BytesToString(pubKey.Address()) if err != nil { - return err + return nil, err } signerData := signing.SignerData{ @@ -327,12 +343,12 @@ func (f *Factory) Sign(ctx context.Context, txBuilder TxBuilder, overwriteSig bo if !overwriteSig { tx, err := txBuilder.GetTx() if err != nil { - return err + return nil, err } prevSignatures, err = tx.GetSignatures() if err != nil { - return err + return nil, err } } // Overwrite or append signer infos. @@ -344,32 +360,32 @@ func (f *Factory) Sign(ctx context.Context, txBuilder TxBuilder, overwriteSig bo sigs = append(sigs, sig) } if err := txBuilder.SetSignatures(sigs...); err != nil { - return err + return nil, err } tx, err := txBuilder.GetTx() if err != nil { - return err + return nil, err } if err := checkMultipleSigners(tx); err != nil { - return err + return nil, err } bytesToSign, err := f.getSignBytesAdapter(ctx, signerData, txBuilder) if err != nil { - return err + return nil, err } // Sign those bytes sigBytes, err := f.keybase.Sign(f.txParams.fromName, bytesToSign, f.txParams.signMode) if err != nil { - return err + return nil, err } // Construct the SignatureV2 struct sigData = SingleSignatureData{ - SignMode: f.SignMode(), + SignMode: f.signMode(), Signature: sigBytes, } sig = Signature{ @@ -386,12 +402,17 @@ func (f *Factory) Sign(ctx context.Context, txBuilder TxBuilder, overwriteSig bo } if err != nil { - return fmt.Errorf("unable to set signatures on payload: %w", err) + return nil, fmt.Errorf("unable to set signatures on payload: %w", err) } // Run optional preprocessing if specified. By default, this is unset // and will return nil. - return f.PreprocessTx(f.txParams.fromName, txBuilder) + err = f.preprocessTx(f.txParams.fromName, txBuilder) + if err != nil { + return nil, err + } + + return txBuilder.GetTx() } // getSignBytesAdapter returns the sign bytes for a given transaction and sign mode. @@ -402,7 +423,7 @@ func (f *Factory) getSignBytesAdapter(ctx context.Context, signerData signing.Si } // Generate the bytes to be signed. - return f.txConfig.SignModeHandler().GetSignBytes(ctx, f.SignMode(), signerData, *txData) + return f.txConfig.SignModeHandler().GetSignBytes(ctx, f.signMode(), signerData, *txData) } // WithGas returns a copy of the Factory with an updated gas value. @@ -420,38 +441,38 @@ func (f *Factory) WithAccountNumber(accnum uint64) { f.txParams.accountNumber = accnum } -// PreprocessTx calls the preprocessing hook with the factory parameters and +// preprocessTx calls the preprocessing hook with the factory parameters and // returns the result. -func (f *Factory) PreprocessTx(keyname string, builder TxBuilder) error { +func (f *Factory) preprocessTx(keyname string, builder TxBuilder) error { if f.txParams.preprocessTxHook == nil { // Allow pass-through return nil } - keyType, err := f.Keybase().KeyType(keyname) + keyType, err := f.keyring().KeyType(keyname) if err != nil { return err } return f.txParams.preprocessTxHook(f.txParams.chainID, keyType, builder) } -// AccountNumber returns the account number. -func (f *Factory) AccountNumber() uint64 { return f.txParams.accountNumber } +// accountNumber returns the account number. +func (f *Factory) accountNumber() uint64 { return f.txParams.accountNumber } -// Sequence returns the sequence number. -func (f *Factory) Sequence() uint64 { return f.txParams.sequence } +// sequence returns the sequence number. +func (f *Factory) sequence() uint64 { return f.txParams.sequence } -// GasAdjustment returns the gas adjustment value. -func (f *Factory) GasAdjustment() float64 { return f.txParams.gasAdjustment } +// gasAdjustment returns the gas adjustment value. +func (f *Factory) gasAdjustment() float64 { return f.txParams.gasAdjustment } -// Keybase returns the keyring. -func (f *Factory) Keybase() keyring.Keyring { return f.keybase } +// keyring returns the keyring. +func (f *Factory) keyring() keyring.Keyring { return f.keybase } -// SimulateAndExecute returns whether to simulate and execute. -func (f *Factory) SimulateAndExecute() bool { return f.txParams.simulateAndExecute } +// simulateAndExecute returns whether to simulate and execute. +func (f *Factory) simulateAndExecute() bool { return f.txParams.simulateAndExecute } -// SignMode returns the sign mode. -func (f *Factory) SignMode() apitxsigning.SignMode { return f.txParams.signMode } +// signMode returns the sign mode. +func (f *Factory) signMode() apitxsigning.SignMode { return f.txParams.signMode } // getSimPK gets the public key to use for building a simulation tx. // Note, we should only check for keys in the keybase if we are in simulate and execute mode, @@ -495,7 +516,7 @@ func (f *Factory) getSimSignatureData(pk cryptotypes.PubKey) SignatureData { multiSignatureData := make([]SignatureData, 0, multisigPubKey.Threshold) for i := uint32(0); i < multisigPubKey.Threshold; i++ { multiSignatureData = append(multiSignatureData, &SingleSignatureData{ - SignMode: f.SignMode(), + SignMode: f.signMode(), }) } diff --git a/client/v2/tx/factory_test.go b/client/v2/tx/factory_test.go index 557320f2a94a..adb786e6d60a 100644 --- a/client/v2/tx/factory_test.go +++ b/client/v2/tx/factory_test.go @@ -292,12 +292,14 @@ func TestFactory_Sign(t *testing.T) { require.Nil(t, builderTx.signatures) require.Nil(t, builderTx.signerInfos) - err = f.Sign(context.Background(), builder, true) + tx, err := f.sign(context.Background(), builder, true) if tt.wantErr { require.Error(t, err) } else { require.NoError(t, err) - require.NotNil(t, builderTx.signatures) + sigs, err := tx.GetSignatures() + require.NoError(t, err) + require.NotNil(t, sigs) require.NotNil(t, builderTx.signerInfos) } }) diff --git a/client/v2/tx/tx.go b/client/v2/tx/tx.go index 53cab6a1411b..02f223961879 100644 --- a/client/v2/tx/tx.go +++ b/client/v2/tx/tx.go @@ -277,7 +277,7 @@ func BroadcastTx(clientCtx client.Context, txf Factory, msgs ...transaction.Msg) return err } - if txf.SimulateAndExecute() { + if txf.simulateAndExecute() { err = txf.calculateGas(msgs...) if err != nil { return err @@ -320,11 +320,7 @@ func BroadcastTx(clientCtx client.Context, txf Factory, msgs ...transaction.Msg) } } - if err = txf.Sign(clientCtx.CmdContext, builder, true); err != nil { - return err - } - - signedTx, err := builder.GetTx() + signedTx, err := txf.sign(clientCtx.CmdContext, builder, true) if err != nil { return err } @@ -349,8 +345,8 @@ func makeAuxSignerData(f Factory, msgs ...transaction.Msg) (*apitx.AuxSignerData b.SetAddress(f.txParams.fromAddress) if f.txParams.offline { - b.SetAccountNumber(f.AccountNumber()) - b.SetSequence(f.Sequence()) + b.SetAccountNumber(f.accountNumber()) + b.SetSequence(f.sequence()) } else { accNum, seq, err := f.accountRetriever.GetAccountNumberSequence(context.Background(), f.txParams.address) if err != nil { @@ -365,7 +361,7 @@ func makeAuxSignerData(f Factory, msgs ...transaction.Msg) (*apitx.AuxSignerData return nil, err } - err = b.SetSignMode(f.SignMode()) + err = b.SetSignMode(f.signMode()) if err != nil { return nil, err } @@ -386,7 +382,7 @@ func makeAuxSignerData(f Factory, msgs ...transaction.Msg) (*apitx.AuxSignerData return nil, err } - sig, err := f.keybase.Sign(f.txParams.fromName, signBz, f.SignMode()) + sig, err := f.keybase.Sign(f.txParams.fromName, signBz, f.signMode()) if err != nil { return nil, err } From 7acdd15bc519a6f748621676d5c111f0c0fc0942 Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Tue, 20 Aug 2024 11:19:11 +0200 Subject: [PATCH 27/36] update: use timeoutTimestamp, remove PreprocessTxFn and ExtensionOptions, fix godoc typos --- client/v2/tx/builder.go | 39 +++++++++++++++++++-------------------- client/v2/tx/encoder.go | 4 ++-- client/v2/tx/factory.go | 28 +--------------------------- client/v2/tx/tx.go | 18 +++++++++--------- client/v2/tx/types.go | 32 ++++++++++---------------------- 5 files changed, 41 insertions(+), 80 deletions(-) diff --git a/client/v2/tx/builder.go b/client/v2/tx/builder.go index e73767ed82df..5df762d69d73 100644 --- a/client/v2/tx/builder.go +++ b/client/v2/tx/builder.go @@ -1,8 +1,10 @@ package tx import ( - gogoany "github.com/cosmos/gogoproto/types/any" + "time" + "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/timestamppb" base "cosmossdk.io/api/cosmos/base/v1beta1" apitx "cosmossdk.io/api/cosmos/tx/v1beta1" @@ -19,11 +21,6 @@ var ( _ TxBuilderProvider = BuilderProvider{} ) -// ExtendedTxBuilder defines an interface for setting extension options in a transaction. -type ExtendedTxBuilder interface { - SetExtensionOptions(...*gogoany.Any) -} - // TxBuilder defines an interface which an application-defined concrete transaction // type must implement. Namely, it must be able to set messages, generate // signatures, and provide canonical bytes to sign over. The transaction must @@ -36,7 +33,7 @@ type TxBuilder interface { SetMemo(string) SetFeeAmount([]*base.Coin) SetGasLimit(uint64) - SetTimeoutHeight(uint64) + SetTimeoutTimestamp(time.Time) SetFeePayer(string) error SetFeeGranter(string) error SetUnordered(bool) @@ -85,16 +82,17 @@ type txBuilder struct { decoder Decoder codec codec.BinaryCodec - msgs []transaction.Msg - timeoutHeight uint64 - granter []byte - payer []byte - unordered bool - memo string - gasLimit uint64 - fees []*base.Coin - signerInfos []*apitx.SignerInfo - signatures [][]byte + msgs []transaction.Msg + timeoutHeight uint64 + timeoutTimestamp time.Time + granter []byte + payer []byte + unordered bool + memo string + gasLimit uint64 + fees []*base.Coin + signerInfos []*apitx.SignerInfo + signatures [][]byte extensionOptions []*anypb.Any nonCriticalExtensionOptions []*anypb.Any @@ -123,6 +121,7 @@ func (b *txBuilder) getTx() (*wrappedTx, error) { Messages: msgs, Memo: b.memo, TimeoutHeight: b.timeoutHeight, + TimeoutTimestamp: timestamppb.New(b.timeoutTimestamp), Unordered: b.unordered, ExtensionOptions: b.extensionOptions, NonCriticalExtensionOptions: b.nonCriticalExtensionOptions, @@ -232,9 +231,9 @@ func (b *txBuilder) SetGasLimit(gasLimit uint64) { b.gasLimit = gasLimit } -// SetTimeoutHeight sets the timeout height for the transaction. -func (b *txBuilder) SetTimeoutHeight(timeoutHeight uint64) { - b.timeoutHeight = timeoutHeight +// SetTimeoutTimestamp sets the timeout timestamp for the transaction. +func (b *txBuilder) SetTimeoutTimestamp(timeoutHeight time.Time) { + b.timeoutTimestamp = timeoutHeight } // SetFeePayer sets the fee payer for the transaction. diff --git a/client/v2/tx/encoder.go b/client/v2/tx/encoder.go index ba1581455c10..2094efe6d3d5 100644 --- a/client/v2/tx/encoder.go +++ b/client/v2/tx/encoder.go @@ -29,10 +29,10 @@ type Decoder interface { Decode(txBytes []byte) (*txdecode.DecodedTx, error) } -// txApiDecoder is a function type that unmarshals transaction bytes into an API Tx type. +// txDecoder is a function type that unmarshals transaction bytes into an API Tx type. type txDecoder func(txBytes []byte) (Tx, error) -// txApiEncoder is a function type that marshals a transaction into bytes. +// txEncoder is a function type that marshals a transaction into bytes. type txEncoder func(tx Tx) ([]byte, error) // decodeTx decodes transaction bytes into an apitx.Tx structure. diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index 8992872644be..48410d522615 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -155,11 +155,7 @@ func (f *Factory) BuildUnsignedTx(msgs ...transaction.Msg) (TxBuilder, error) { if err != nil { return nil, err } - txBuilder.SetTimeoutHeight(f.txParams.timeoutHeight) - - if etx, ok := txBuilder.(ExtendedTxBuilder); ok { - etx.SetExtensionOptions(f.txParams.ExtOptions...) - } + txBuilder.SetTimeoutTimestamp(f.txParams.timeoutTimestamp) return txBuilder, nil } @@ -405,13 +401,6 @@ func (f *Factory) sign(ctx context.Context, txBuilder TxBuilder, overwriteSig bo return nil, fmt.Errorf("unable to set signatures on payload: %w", err) } - // Run optional preprocessing if specified. By default, this is unset - // and will return nil. - err = f.preprocessTx(f.txParams.fromName, txBuilder) - if err != nil { - return nil, err - } - return txBuilder.GetTx() } @@ -441,21 +430,6 @@ func (f *Factory) WithAccountNumber(accnum uint64) { f.txParams.accountNumber = accnum } -// preprocessTx calls the preprocessing hook with the factory parameters and -// returns the result. -func (f *Factory) preprocessTx(keyname string, builder TxBuilder) error { - if f.txParams.preprocessTxHook == nil { - // Allow pass-through - return nil - } - - keyType, err := f.keyring().KeyType(keyname) - if err != nil { - return err - } - return f.txParams.preprocessTxHook(f.txParams.chainID, keyType, builder) -} - // accountNumber returns the account number. func (f *Factory) accountNumber() uint64 { return f.txParams.accountNumber } diff --git a/client/v2/tx/tx.go b/client/v2/tx/tx.go index 02f223961879..a0c64dc30c00 100644 --- a/client/v2/tx/tx.go +++ b/client/v2/tx/tx.go @@ -5,10 +5,10 @@ import ( "context" "errors" "fmt" - "os" - "github.com/cosmos/gogoproto/proto" "github.com/spf13/pflag" + "os" + "time" apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" apitx "cosmossdk.io/api/cosmos/tx/v1beta1" @@ -25,7 +25,9 @@ import ( // txParamsFromFlagSet extracts the transaction parameters from the provided FlagSet. func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring, ac address.Codec) (params TxParameters, err error) { - timeout, _ := flags.GetUint64(flags2.FlagTimeoutHeight) + //timeout, _ := flags.GetUint64(flags2.FlagTimeoutHeight) + timestampUnix, _ := flags.GetInt64(flags2.FlagTimeoutTimestamp) + timeoutTimestamp := time.Unix(timestampUnix, 0) chainID, _ := flags.GetString(flags2.FlagChainID) memo, _ := flags.GetString(flags2.FlagNote) signMode, _ := flags.GetString(flags2.FlagSignMode) @@ -72,10 +74,10 @@ func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring, ac addr } txParams := TxParameters{ - timeoutHeight: timeout, - chainID: chainID, - memo: memo, - signMode: getSignMode(signMode), + timeoutTimestamp: timeoutTimestamp, + chainID: chainID, + memo: memo, + signMode: getSignMode(signMode), AccountConfig: AccountConfig{ accountNumber: accNumber, sequence: sequence, @@ -91,9 +93,7 @@ func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring, ac addr offChain: false, generateOnly: generateOnly, simulateAndExecute: gasSetting.Simulate, - preprocessTxHook: nil, // TODO: in context }, - ExtensionOptions: ExtensionOptions{}, // TODO } return txParams, nil diff --git a/client/v2/tx/types.go b/client/v2/tx/types.go index 23cafbaef875..a951efdb3041 100644 --- a/client/v2/tx/types.go +++ b/client/v2/tx/types.go @@ -2,8 +2,7 @@ package tx import ( "fmt" - - gogoany "github.com/cosmos/gogoproto/types/any" + "time" base "cosmossdk.io/api/cosmos/base/v1beta1" apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" @@ -15,9 +14,6 @@ import ( const defaultGas = 200000 -// PreprocessTxFn defines a hook by which chains can preprocess transactions before broadcasting -type PreprocessTxFn func(chainID string, key uint, tx TxBuilder) error - // HasValidateBasic is a copy of types.HasValidateBasic to avoid sdk import. type HasValidateBasic interface { // ValidateBasic does a simple validation check that @@ -27,16 +23,15 @@ type HasValidateBasic interface { // TxParameters defines the parameters required for constructing a transaction. type TxParameters struct { - timeoutHeight uint64 // timeoutHeight indicates the block height after which the transaction is no longer valid. - chainID string // chainID specifies the unique identifier of the blockchain where the transaction will be processed. - memo string // memo contains any arbitrary memo to be attached to the transaction. - signMode apitxsigning.SignMode // signMode determines the signing mode to be used for the transaction. + timeoutTimestamp time.Time // timeoutTimestamp indicates a timestamp after which the transaction is no longer valid. + chainID string // chainID specifies the unique identifier of the blockchain where the transaction will be processed. + memo string // memo contains any arbitrary memo to be attached to the transaction. + signMode apitxsigning.SignMode // signMode determines the signing mode to be used for the transaction. AccountConfig // AccountConfig includes information about the transaction originator's account. GasConfig // GasConfig specifies the gas settings for the transaction. FeeConfig // FeeConfig details the fee associated with the transaction. ExecutionOptions // ExecutionOptions includes settings that modify how the transaction is executed. - ExtensionOptions // ExtensionOptions allows for additional features or data to be included in the transaction. } // AccountConfig defines the 'account' related fields in a transaction. @@ -106,18 +101,11 @@ func NewFeeConfig(fees, feePayer, feeGranter string) (FeeConfig, error) { // ExecutionOptions defines the transaction execution options ran by the client // ExecutionOptions defines the settings for transaction execution. type ExecutionOptions struct { - unordered bool // unordered indicates if the transaction execution order is not guaranteed. - offline bool // offline specifies if the transaction should be prepared for offline signing. - offChain bool // offChain indicates if the transaction should be executed off the blockchain. - generateOnly bool // generateOnly specifies if the transaction should only be generated and not executed. - simulateAndExecute bool // simulateAndExecute indicates if the transaction should be simulated before execution. - preprocessTxHook PreprocessTxFn // preprocessTxHook is a function hook for preprocessing the transaction. -} - -// ExtensionOptions holds a slice of Any protocol buffer messages that can be used to extend the functionality -// of a transaction with additional data. This is typically used to include non-standard or experimental features. -type ExtensionOptions struct { - ExtOptions []*gogoany.Any // ExtOptions are the extension options in the form of Any protocol buffer messages. + unordered bool // unordered indicates if the transaction execution order is not guaranteed. + offline bool // offline specifies if the transaction should be prepared for offline signing. + offChain bool // offChain indicates if the transaction should be executed off the blockchain. + generateOnly bool // generateOnly specifies if the transaction should only be generated and not executed. + simulateAndExecute bool // simulateAndExecute indicates if the transaction should be simulated before execution. } // GasEstimateResponse defines a response definition for tx gas estimation. From a0dc1478127669dfaa18b0c938e73f81b615de07 Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Tue, 20 Aug 2024 13:21:04 +0200 Subject: [PATCH 28/36] update: prepare is now only used in constructor --- client/v2/tx/factory.go | 40 ++++++++-------- client/v2/tx/factory_test.go | 92 +++++++++++++++++++++++++++--------- client/v2/tx/tx.go | 17 +------ 3 files changed, 90 insertions(+), 59 deletions(-) diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index 48410d522615..7c85c8a61aa5 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -48,6 +48,11 @@ type Factory struct { func NewFactory(keybase keyring.Keyring, cdc codec.BinaryCodec, accRetriever account.AccountRetriever, txConfig TxConfig, ac address.Codec, conn gogogrpc.ClientConn, parameters TxParameters, ) (Factory, error) { + parameters, err := prepareTxParams(parameters, accRetriever) + if err != nil { + return Factory{}, err + } + return Factory{ keybase: keybase, cdc: cdc, @@ -59,36 +64,34 @@ func NewFactory(keybase keyring.Keyring, cdc codec.BinaryCodec, accRetriever acc }, nil } -// Prepare ensures the account defined by ctx.GetFromAddress() exists and +// prepareTxParams 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. -// Note: When in offline mode, the Prepare does nothing and returns the original factory. -func (f *Factory) Prepare() error { - if f.txParams.ExecutionOptions.offline || f.txParams.ExecutionOptions.offChain { - return nil +func prepareTxParams(parameters TxParameters, accRetriever account.AccountRetriever) (TxParameters, error) { + if parameters.ExecutionOptions.offline || parameters.ExecutionOptions.offChain { + return parameters, nil } - if len(f.txParams.address) == 0 { - return errors.New("missing 'from address' field") + if len(parameters.address) == 0 { + return parameters, errors.New("missing 'from address' field") } - if f.txParams.accountNumber == 0 || f.txParams.sequence == 0 { - num, seq, err := f.accountRetriever.GetAccountNumberSequence(context.Background(), f.txParams.address) + if parameters.accountNumber == 0 || parameters.sequence == 0 { + num, seq, err := accRetriever.GetAccountNumberSequence(context.Background(), parameters.address) if err != nil { - return err + return parameters, err } - if f.txParams.accountNumber == 0 { - f.WithAccountNumber(num) + if parameters.accountNumber == 0 { + parameters.accountNumber = num } - if f.txParams.sequence == 0 { - f.WithSequence(seq) + if parameters.sequence == 0 { + parameters.sequence = seq } } - return nil + return parameters, nil } // BuildUnsignedTx builds a transaction to be signed given a set of messages. @@ -161,11 +164,6 @@ func (f *Factory) BuildUnsignedTx(msgs ...transaction.Msg) (TxBuilder, error) { } func (f *Factory) BuildsSignedTx(ctx context.Context, msgs ...transaction.Msg) (Tx, error) { - err := f.Prepare() - if err != nil { - return nil, err - } - tx, err := f.BuildUnsignedTx(msgs...) if err != nil { return nil, err diff --git a/client/v2/tx/factory_test.go b/client/v2/tx/factory_test.go index adb786e6d60a..a900a98f29b1 100644 --- a/client/v2/tx/factory_test.go +++ b/client/v2/tx/factory_test.go @@ -16,7 +16,12 @@ import ( countertypes "github.com/cosmos/cosmos-sdk/testutil/x/counter/types" ) -func TestFactory_Prepare(t *testing.T) { +var ( + signer = "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9" + addr, _ = ac.StringToBytes(signer) +) + +func TestFactory_prepareTxParams(t *testing.T) { tests := []struct { name string txParams TxParameters @@ -26,7 +31,7 @@ func TestFactory_Prepare(t *testing.T) { name: "no error", txParams: TxParameters{ AccountConfig: AccountConfig{ - address: []byte("hello"), + address: addr, }, }, }, @@ -38,10 +43,9 @@ func TestFactory_Prepare(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - f, err := NewFactory(keybase, cdc, mockAccountRetriever{}, txConf, ac, mockClientConn{}, tt.txParams) - require.NoError(t, err) - require.NotNil(t, f) - if err := f.Prepare(); (err != nil) != tt.error { + var err error + tt.txParams, err = prepareTxParams(tt.txParams, mockAccountRetriever{}) + if (err != nil) != tt.error { t.Errorf("Prepare() error = %v, wantErr %v", err, tt.error) } }) @@ -59,19 +63,26 @@ func TestFactory_BuildUnsignedTx(t *testing.T) { name: "no error", txParams: TxParameters{ chainID: "demo", + AccountConfig: AccountConfig{ + address: addr, + }, }, msgs: []transaction.Msg{ &countertypes.MsgIncreaseCounter{ - Signer: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + Signer: signer, Count: 0, }, }, }, { - name: "chainId not provided", - txParams: TxParameters{}, - msgs: []transaction.Msg{}, - error: true, + name: "chainId not provided", + txParams: TxParameters{ + AccountConfig: AccountConfig{ + address: addr, + }, + }, + msgs: []transaction.Msg{}, + error: true, }, { name: "offline and generateOnly with chainIde provided", @@ -81,6 +92,9 @@ func TestFactory_BuildUnsignedTx(t *testing.T) { offline: true, generateOnly: true, }, + AccountConfig: AccountConfig{ + address: addr, + }, }, msgs: []transaction.Msg{}, error: true, @@ -89,6 +103,9 @@ func TestFactory_BuildUnsignedTx(t *testing.T) { name: "fees and gas price provided", txParams: TxParameters{ chainID: "demo", + AccountConfig: AccountConfig{ + address: addr, + }, GasConfig: GasConfig{ gasPrices: []*base.DecCoin{ { @@ -141,13 +158,16 @@ func TestFactory_calculateGas(t *testing.T) { name: "no error", txParams: TxParameters{ chainID: "demo", + AccountConfig: AccountConfig{ + address: addr, + }, GasConfig: GasConfig{ gasAdjustment: 1, }, }, msgs: []transaction.Msg{ &countertypes.MsgIncreaseCounter{ - Signer: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + Signer: signer, Count: 0, }, }, @@ -156,13 +176,16 @@ func TestFactory_calculateGas(t *testing.T) { name: "offline mode", txParams: TxParameters{ chainID: "demo", + AccountConfig: AccountConfig{ + address: addr, + }, ExecutionOptions: ExecutionOptions{ offline: true, }, }, msgs: []transaction.Msg{ &countertypes.MsgIncreaseCounter{ - Signer: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + Signer: signer, Count: 0, }, }, @@ -196,13 +219,16 @@ func TestFactory_Simulate(t *testing.T) { name: "no error", txParams: TxParameters{ chainID: "demo", + AccountConfig: AccountConfig{ + address: addr, + }, GasConfig: GasConfig{ gasAdjustment: 1, }, }, msgs: []transaction.Msg{ &countertypes.MsgIncreaseCounter{ - Signer: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + Signer: signer, Count: 0, }, }, @@ -237,6 +263,9 @@ func TestFactory_BuildSimTx(t *testing.T) { name: "no error", txParams: TxParameters{ chainID: "demo", + AccountConfig: AccountConfig{ + address: addr, + }, }, }, } @@ -268,6 +297,7 @@ func TestFactory_Sign(t *testing.T) { chainID: "demo", AccountConfig: AccountConfig{ fromName: "alice", + address: addr, }, }, }, @@ -280,7 +310,7 @@ func TestFactory_Sign(t *testing.T) { builder, err := f.BuildUnsignedTx([]transaction.Msg{ &countertypes.MsgIncreaseCounter{ - Signer: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + Signer: signer, Count: 0, }, }...) @@ -317,12 +347,18 @@ func TestFactory_getSignBytesAdapter(t *testing.T) { txParams: TxParameters{ chainID: "demo", signMode: apitxsigning.SignMode_SIGN_MODE_DIRECT, + AccountConfig: AccountConfig{ + address: addr, + }, }, }, { name: "signMode not specified", txParams: TxParameters{ chainID: "demo", + AccountConfig: AccountConfig{ + address: addr, + }, }, error: true, }, @@ -335,7 +371,7 @@ func TestFactory_getSignBytesAdapter(t *testing.T) { txb, err := f.BuildUnsignedTx([]transaction.Msg{ &countertypes.MsgIncreaseCounter{ - Signer: "cosmos1zglwfu6xjzvzagqcmvzewyzjp9xwqw5qwrr8n9", + Signer: signer, Count: 0, }, }...) @@ -408,8 +444,12 @@ func TestFactory_WithFunctions(t *testing.T) { checkFunc func(*Factory) bool }{ { - name: "with gas", - txParams: TxParameters{}, + name: "with gas", + txParams: TxParameters{ + AccountConfig: AccountConfig{ + address: addr, + }, + }, withFunc: func(f *Factory) { f.WithGas(1000) }, @@ -418,8 +458,12 @@ func TestFactory_WithFunctions(t *testing.T) { }, }, { - name: "with sequence", - txParams: TxParameters{}, + name: "with sequence", + txParams: TxParameters{ + AccountConfig: AccountConfig{ + address: addr, + }, + }, withFunc: func(f *Factory) { f.WithSequence(10) }, @@ -428,8 +472,12 @@ func TestFactory_WithFunctions(t *testing.T) { }, }, { - name: "with account number", - txParams: TxParameters{}, + name: "with account number", + txParams: TxParameters{ + AccountConfig: AccountConfig{ + address: addr, + }, + }, withFunc: func(f *Factory) { f.WithAccountNumber(123) }, diff --git a/client/v2/tx/tx.go b/client/v2/tx/tx.go index a0c64dc30c00..e10abfc099f3 100644 --- a/client/v2/tx/tx.go +++ b/client/v2/tx/tx.go @@ -223,11 +223,6 @@ func generateAuxSignerData(ctx client.Context, txf Factory, msgs ...transaction. // It first calls Prepare on the transaction factory to set up any necessary pre-conditions. // If preparation is successful, it generates an unsigned transaction string using the provided messages. func generateOnly(ctx client.Context, txf Factory, msgs ...transaction.Msg) error { - err := txf.Prepare() - if err != nil { - return err - } - uTx, err := txf.UnsignedTxString(msgs...) if err != nil { return err @@ -243,11 +238,6 @@ func dryRun(txf Factory, msgs ...transaction.Msg) error { return errors.New("dry-run: cannot use offline mode") } - err := txf.Prepare() - if err != nil { - return err - } - _, gas, err := txf.Simulate(msgs...) if err != nil { return err @@ -272,13 +262,8 @@ func SimulateTx(ctx client.Context, flagSet *pflag.FlagSet, msgs ...transaction. // 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 ...transaction.Msg) error { - err := txf.Prepare() - if err != nil { - return err - } - if txf.simulateAndExecute() { - err = txf.calculateGas(msgs...) + err := txf.calculateGas(msgs...) if err != nil { return err } From 78bb01ec6baef3eefb199f8ea8ab954eca2a654f Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Wed, 21 Aug 2024 10:19:15 +0200 Subject: [PATCH 29/36] update: remove generateOnly from params --- client/v2/tx/factory.go | 62 ++++++++++++++---- client/v2/tx/factory_test.go | 25 -------- client/v2/tx/tx.go | 121 ++--------------------------------- client/v2/tx/types.go | 78 +++++++++++++++++++++- 4 files changed, 133 insertions(+), 153 deletions(-) diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index 7c85c8a61aa5..96efa38dfed3 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -4,6 +4,8 @@ import ( "context" "errors" "fmt" + flags2 "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/spf13/pflag" "math/big" "strings" @@ -44,14 +46,29 @@ type Factory struct { cachedTx TxBuilder } +func NewFactoryFromFlagSet(flags *pflag.FlagSet, keybase keyring.Keyring, cdc codec.BinaryCodec, accRetriever account.AccountRetriever, + txConfig TxConfig, ac address.Codec, conn gogogrpc.ClientConn) (Factory, error) { + if err := validateFlagSet(flags); err != nil { + return Factory{}, err + } + + params, err := txParamsFromFlagSet(flags, keybase, ac) + if err != nil { + return Factory{}, err + } + + params, err = prepareTxParams(params, accRetriever) + if err != nil { + return Factory{}, err + } + + return NewFactory(keybase, cdc, accRetriever, txConfig, ac, conn, params) +} + // NewFactory returns a new instance of Factory. func NewFactory(keybase keyring.Keyring, cdc codec.BinaryCodec, accRetriever account.AccountRetriever, txConfig TxConfig, ac address.Codec, conn gogogrpc.ClientConn, parameters TxParameters, ) (Factory, error) { - parameters, err := prepareTxParams(parameters, accRetriever) - if err != nil { - return Factory{}, err - } return Factory{ keybase: keybase, @@ -64,6 +81,29 @@ func NewFactory(keybase keyring.Keyring, cdc codec.BinaryCodec, accRetriever acc }, nil } +// validateFlagSet checks the provided flags for consistency and requirements based on the operation mode. +func validateFlagSet(flags *pflag.FlagSet) error { + offline, _ := flags.GetBool(flags2.FlagOffline) + if offline { + if !flags.Changed(flags2.FlagAccountNumber) || !flags.Changed(flags2.FlagSequence) { + return errors.New("account-number and sequence must be set in offline mode") + } + } + + generateOnly, _ := flags.GetBool(flags2.FlagGenerateOnly) + chainID, _ := flags.GetString(flags2.FlagChainID) + if offline && generateOnly { + if chainID != "" { + return errors.New("chain ID cannot be used when offline and generate-only flags are set") + } + } + if chainID == "" { + return errors.New("chain ID required but not specified") + } + + return nil +} + // prepareTxParams 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. @@ -97,13 +137,13 @@ func prepareTxParams(parameters TxParameters, accRetriever account.AccountRetrie // 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 ...transaction.Msg) (TxBuilder, error) { - if f.txParams.offline && f.txParams.generateOnly { - if f.txParams.chainID != "" { - return nil, errors.New("chain ID cannot be used when offline and generate-only flags are set") - } - } else if f.txParams.chainID == "" { - return nil, errors.New("chain ID required but not specified") - } + //if f.txParams.offline && f.txParams.generateOnly { + // if f.txParams.chainID != "" { + // return nil, errors.New("chain ID cannot be used when offline and generate-only flags are set") + // } + //} else if f.txParams.chainID == "" { + // return nil, errors.New("chain ID required but not specified") + //} fees := f.txParams.fees diff --git a/client/v2/tx/factory_test.go b/client/v2/tx/factory_test.go index a900a98f29b1..42327061f78d 100644 --- a/client/v2/tx/factory_test.go +++ b/client/v2/tx/factory_test.go @@ -74,31 +74,6 @@ func TestFactory_BuildUnsignedTx(t *testing.T) { }, }, }, - { - name: "chainId not provided", - txParams: TxParameters{ - AccountConfig: AccountConfig{ - address: addr, - }, - }, - msgs: []transaction.Msg{}, - error: true, - }, - { - name: "offline and generateOnly with chainIde provided", - txParams: TxParameters{ - chainID: "demo", - ExecutionOptions: ExecutionOptions{ - offline: true, - generateOnly: true, - }, - AccountConfig: AccountConfig{ - address: addr, - }, - }, - msgs: []transaction.Msg{}, - error: true, - }, { name: "fees and gas price provided", txParams: TxParameters{ diff --git a/client/v2/tx/tx.go b/client/v2/tx/tx.go index e10abfc099f3..60fa30ee3550 100644 --- a/client/v2/tx/tx.go +++ b/client/v2/tx/tx.go @@ -3,19 +3,15 @@ package tx import ( "bufio" "context" + apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + apitx "cosmossdk.io/api/cosmos/tx/v1beta1" + "cosmossdk.io/client/v2/internal/account" + "cosmossdk.io/core/transaction" "errors" "fmt" "github.com/cosmos/gogoproto/proto" "github.com/spf13/pflag" "os" - "time" - - apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" - apitx "cosmossdk.io/api/cosmos/tx/v1beta1" - keyring2 "cosmossdk.io/client/v2/autocli/keyring" - "cosmossdk.io/client/v2/internal/account" - "cosmossdk.io/core/address" - "cosmossdk.io/core/transaction" "github.com/cosmos/cosmos-sdk/client" flags2 "github.com/cosmos/cosmos-sdk/client/flags" @@ -23,111 +19,9 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keyring" ) -// txParamsFromFlagSet extracts the transaction parameters from the provided FlagSet. -func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring, ac address.Codec) (params TxParameters, err error) { - //timeout, _ := flags.GetUint64(flags2.FlagTimeoutHeight) - timestampUnix, _ := flags.GetInt64(flags2.FlagTimeoutTimestamp) - timeoutTimestamp := time.Unix(timestampUnix, 0) - chainID, _ := flags.GetString(flags2.FlagChainID) - memo, _ := flags.GetString(flags2.FlagNote) - signMode, _ := flags.GetString(flags2.FlagSignMode) - - accNumber, _ := flags.GetUint64(flags2.FlagAccountNumber) - sequence, _ := flags.GetUint64(flags2.FlagSequence) - from, _ := flags.GetString(flags2.FlagFrom) - - var fromName, fromAddress string - var addr []byte - isDryRun, _ := flags.GetBool(flags2.FlagDryRun) - if isDryRun { - addr, err = ac.StringToBytes(from) - } else { - fromName, fromAddress, _, err = keybase.KeyInfo(from) - if err == nil { - addr, err = ac.StringToBytes(fromAddress) - } - } - if err != nil { - return params, err - } - - gas, _ := flags.GetString(flags2.FlagGas) - gasSetting, _ := flags2.ParseGasSetting(gas) - gasAdjustment, _ := flags.GetFloat64(flags2.FlagGasAdjustment) - gasPrices, _ := flags.GetString(flags2.FlagGasPrices) - - fees, _ := flags.GetString(flags2.FlagFees) - feePayer, _ := flags.GetString(flags2.FlagFeePayer) - feeGrater, _ := flags.GetString(flags2.FlagFeeGranter) - - unordered, _ := flags.GetBool(flags2.FlagUnordered) - offline, _ := flags.GetBool(flags2.FlagOffline) - generateOnly, _ := flags.GetBool(flags2.FlagGenerateOnly) - - gasConfig, err := NewGasConfig(gasSetting.Gas, gasAdjustment, gasPrices) - if err != nil { - return params, err - } - feeConfig, err := NewFeeConfig(fees, feePayer, feeGrater) - if err != nil { - return params, err - } - - txParams := TxParameters{ - timeoutTimestamp: timeoutTimestamp, - chainID: chainID, - memo: memo, - signMode: getSignMode(signMode), - AccountConfig: AccountConfig{ - accountNumber: accNumber, - sequence: sequence, - fromName: fromName, - fromAddress: fromAddress, - address: addr, - }, - GasConfig: gasConfig, - FeeConfig: feeConfig, - ExecutionOptions: ExecutionOptions{ - unordered: unordered, - offline: offline, - offChain: false, - generateOnly: generateOnly, - simulateAndExecute: gasSetting.Simulate, - }, - } - - return txParams, nil -} - -// validate checks the provided flags for consistency and requirements based on the operation mode. -func validate(flags *pflag.FlagSet) error { - offline, _ := flags.GetBool(flags2.FlagOffline) - if offline { - if !flags.Changed(flags2.FlagAccountNumber) || !flags.Changed(flags2.FlagSequence) { - return errors.New("account-number and sequence must be set in offline mode") - } - } - - generateOnly, _ := flags.GetBool(flags2.FlagGenerateOnly) - chainID, _ := flags.GetString(flags2.FlagChainID) - if offline && generateOnly { - if chainID != "" { - return errors.New("chain ID cannot be used when offline and generate-only flags are set") - } - } - if chainID == "" { - return errors.New("chain ID required but not specified") - } - - return nil -} - // GenerateOrBroadcastTxCLI will either generate and print an unsigned transaction // or sign it and broadcast it returning an error upon failure. func GenerateOrBroadcastTxCLI(ctx client.Context, flagSet *pflag.FlagSet, msgs ...transaction.Msg) error { - if err := validate(flagSet); err != nil { - return err - } if err := validateMessages(msgs...); err != nil { return err @@ -165,11 +59,6 @@ func newFactory(ctx client.Context, flagSet *pflag.FlagSet) (Factory, error) { return Factory{}, err } - params, err := txParamsFromFlagSet(flagSet, k, ctx.AddressCodec) - if err != nil { - return Factory{}, err - } - txConfig, err := NewTxConfig(ConfigOptions{ AddressCodec: ctx.AddressCodec, Cdc: ctx.Codec, @@ -182,7 +71,7 @@ func newFactory(ctx client.Context, flagSet *pflag.FlagSet) (Factory, error) { accRetriever := account.NewAccountRetriever(ctx.AddressCodec, ctx, ctx.InterfaceRegistry) - txf, err := NewFactory(k, ctx.Codec, accRetriever, txConfig, ctx.AddressCodec, ctx, params) + txf, err := NewFactoryFromFlagSet(flagSet, k, ctx.Codec, accRetriever, txConfig, ctx.AddressCodec, ctx) if err != nil { return Factory{}, err } diff --git a/client/v2/tx/types.go b/client/v2/tx/types.go index a951efdb3041..3a54b51eea28 100644 --- a/client/v2/tx/types.go +++ b/client/v2/tx/types.go @@ -1,7 +1,11 @@ package tx import ( + keyring2 "cosmossdk.io/client/v2/autocli/keyring" + "cosmossdk.io/core/address" "fmt" + flags2 "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/spf13/pflag" "time" base "cosmossdk.io/api/cosmos/base/v1beta1" @@ -104,7 +108,6 @@ type ExecutionOptions struct { unordered bool // unordered indicates if the transaction execution order is not guaranteed. offline bool // offline specifies if the transaction should be prepared for offline signing. offChain bool // offChain indicates if the transaction should be executed off the blockchain. - generateOnly bool // generateOnly specifies if the transaction should only be generated and not executed. simulateAndExecute bool // simulateAndExecute indicates if the transaction should be simulated before execution. } @@ -128,3 +131,76 @@ type Tx interface { // GetSignatures fetches the signatures attached to the transaction. GetSignatures() ([]Signature, error) } + +// txParamsFromFlagSet extracts the transaction parameters from the provided FlagSet. +func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring, ac address.Codec) (params TxParameters, err error) { + timestampUnix, _ := flags.GetInt64(flags2.FlagTimeoutTimestamp) + timeoutTimestamp := time.Unix(timestampUnix, 0) + chainID, _ := flags.GetString(flags2.FlagChainID) + memo, _ := flags.GetString(flags2.FlagNote) + signMode, _ := flags.GetString(flags2.FlagSignMode) + + accNumber, _ := flags.GetUint64(flags2.FlagAccountNumber) + sequence, _ := flags.GetUint64(flags2.FlagSequence) + from, _ := flags.GetString(flags2.FlagFrom) + + var fromName, fromAddress string + var addr []byte + isDryRun, _ := flags.GetBool(flags2.FlagDryRun) + if isDryRun { + addr, err = ac.StringToBytes(from) + } else { + fromName, fromAddress, _, err = keybase.KeyInfo(from) + if err == nil { + addr, err = ac.StringToBytes(fromAddress) + } + } + if err != nil { + return params, err + } + + gas, _ := flags.GetString(flags2.FlagGas) + gasSetting, _ := flags2.ParseGasSetting(gas) + gasAdjustment, _ := flags.GetFloat64(flags2.FlagGasAdjustment) + gasPrices, _ := flags.GetString(flags2.FlagGasPrices) + + fees, _ := flags.GetString(flags2.FlagFees) + feePayer, _ := flags.GetString(flags2.FlagFeePayer) + feeGrater, _ := flags.GetString(flags2.FlagFeeGranter) + + unordered, _ := flags.GetBool(flags2.FlagUnordered) + offline, _ := flags.GetBool(flags2.FlagOffline) + + gasConfig, err := NewGasConfig(gasSetting.Gas, gasAdjustment, gasPrices) + if err != nil { + return params, err + } + feeConfig, err := NewFeeConfig(fees, feePayer, feeGrater) + if err != nil { + return params, err + } + + txParams := TxParameters{ + timeoutTimestamp: timeoutTimestamp, + chainID: chainID, + memo: memo, + signMode: getSignMode(signMode), + AccountConfig: AccountConfig{ + accountNumber: accNumber, + sequence: sequence, + fromName: fromName, + fromAddress: fromAddress, + address: addr, + }, + GasConfig: gasConfig, + FeeConfig: feeConfig, + ExecutionOptions: ExecutionOptions{ + unordered: unordered, + offline: offline, + offChain: false, + simulateAndExecute: gasSetting.Simulate, + }, + } + + return txParams, nil +} From 1c76296ea73c58ea4c9a3c9931edd73a18a1c591 Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Wed, 21 Aug 2024 10:20:18 +0200 Subject: [PATCH 30/36] update: remove offchain from params --- client/v2/tx/factory.go | 2 +- client/v2/tx/types.go | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index 96efa38dfed3..718075a67dcc 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -108,7 +108,7 @@ func validateFlagSet(flags *pflag.FlagSet) error { // 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. func prepareTxParams(parameters TxParameters, accRetriever account.AccountRetriever) (TxParameters, error) { - if parameters.ExecutionOptions.offline || parameters.ExecutionOptions.offChain { + if parameters.ExecutionOptions.offline { return parameters, nil } diff --git a/client/v2/tx/types.go b/client/v2/tx/types.go index 3a54b51eea28..eb915d879450 100644 --- a/client/v2/tx/types.go +++ b/client/v2/tx/types.go @@ -107,7 +107,6 @@ func NewFeeConfig(fees, feePayer, feeGranter string) (FeeConfig, error) { type ExecutionOptions struct { unordered bool // unordered indicates if the transaction execution order is not guaranteed. offline bool // offline specifies if the transaction should be prepared for offline signing. - offChain bool // offChain indicates if the transaction should be executed off the blockchain. simulateAndExecute bool // simulateAndExecute indicates if the transaction should be simulated before execution. } @@ -197,7 +196,6 @@ func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring, ac addr ExecutionOptions: ExecutionOptions{ unordered: unordered, offline: offline, - offChain: false, simulateAndExecute: gasSetting.Simulate, }, } From 4c8ee9b8d9967eda2de6e99585f8409f49c25c7a Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Wed, 21 Aug 2024 10:49:19 +0200 Subject: [PATCH 31/36] update: remove offline from params --- client/v2/tx/factory.go | 40 ++++++++++++++++++++++-------------- client/v2/tx/factory_test.go | 2 +- client/v2/tx/tx.go | 17 +++++++-------- client/v2/tx/types.go | 3 --- 4 files changed, 35 insertions(+), 27 deletions(-) diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index 718075a67dcc..b356f9c91f53 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -4,11 +4,12 @@ import ( "context" "errors" "fmt" - flags2 "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/spf13/pflag" "math/big" "strings" + flags2 "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/spf13/pflag" + "github.com/cosmos/go-bip39" gogogrpc "github.com/cosmos/gogoproto/grpc" "google.golang.org/protobuf/types/known/anypb" @@ -48,7 +49,8 @@ type Factory struct { func NewFactoryFromFlagSet(flags *pflag.FlagSet, keybase keyring.Keyring, cdc codec.BinaryCodec, accRetriever account.AccountRetriever, txConfig TxConfig, ac address.Codec, conn gogogrpc.ClientConn) (Factory, error) { - if err := validateFlagSet(flags); err != nil { + offline, _ := flags.GetBool(flags2.FlagOffline) + if err := validateFlagSet(flags, offline); err != nil { return Factory{}, err } @@ -57,7 +59,7 @@ func NewFactoryFromFlagSet(flags *pflag.FlagSet, keybase keyring.Keyring, cdc co return Factory{}, err } - params, err = prepareTxParams(params, accRetriever) + params, err = prepareTxParams(params, accRetriever, offline) if err != nil { return Factory{}, err } @@ -82,33 +84,41 @@ func NewFactory(keybase keyring.Keyring, cdc codec.BinaryCodec, accRetriever acc } // validateFlagSet checks the provided flags for consistency and requirements based on the operation mode. -func validateFlagSet(flags *pflag.FlagSet) error { - offline, _ := flags.GetBool(flags2.FlagOffline) +func validateFlagSet(flags *pflag.FlagSet, offline bool) error { if offline { if !flags.Changed(flags2.FlagAccountNumber) || !flags.Changed(flags2.FlagSequence) { return errors.New("account-number and sequence must be set in offline mode") } + + gas, _ := flags.GetString(flags2.FlagGas) + gasSetting, _ := flags2.ParseGasSetting(gas) + if gasSetting.Simulate { + return errors.New("simulate and offline flags cannot be set at the same time") + } } generateOnly, _ := flags.GetBool(flags2.FlagGenerateOnly) chainID, _ := flags.GetString(flags2.FlagChainID) - if offline && generateOnly { - if chainID != "" { - return errors.New("chain ID cannot be used when offline and generate-only flags are set") - } + if offline && generateOnly && chainID != "" { + return errors.New("chain ID cannot be used when offline and generate-only flags are set") } if chainID == "" { return errors.New("chain ID required but not specified") } + dryRun, _ := flags.GetBool(flags2.FlagDryRun) + if offline && dryRun { + return errors.New("dry-run: cannot use offline mode") + } + return nil } // prepareTxParams 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. -func prepareTxParams(parameters TxParameters, accRetriever account.AccountRetriever) (TxParameters, error) { - if parameters.ExecutionOptions.offline { +func prepareTxParams(parameters TxParameters, accRetriever account.AccountRetriever, offline bool) (TxParameters, error) { + if offline { return parameters, nil } @@ -214,9 +224,9 @@ func (f *Factory) BuildsSignedTx(ctx context.Context, msgs ...transaction.Msg) ( // calculateGas calculates the gas required for the given messages. func (f *Factory) calculateGas(msgs ...transaction.Msg) error { - if f.txParams.offline { - return errors.New("cannot simulate in offline mode") - } + //if f.txParams.offline { + // return errors.New("cannot simulate in offline mode") + //} _, adjusted, err := f.Simulate(msgs...) if err != nil { return err diff --git a/client/v2/tx/factory_test.go b/client/v2/tx/factory_test.go index 42327061f78d..68c83084a8dc 100644 --- a/client/v2/tx/factory_test.go +++ b/client/v2/tx/factory_test.go @@ -44,7 +44,7 @@ func TestFactory_prepareTxParams(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var err error - tt.txParams, err = prepareTxParams(tt.txParams, mockAccountRetriever{}) + tt.txParams, err = prepareTxParams(tt.txParams, mockAccountRetriever{}, false) if (err != nil) != tt.error { t.Errorf("Prepare() error = %v, wantErr %v", err, tt.error) } diff --git a/client/v2/tx/tx.go b/client/v2/tx/tx.go index 60fa30ee3550..aedcc60cdccb 100644 --- a/client/v2/tx/tx.go +++ b/client/v2/tx/tx.go @@ -34,7 +34,8 @@ func GenerateOrBroadcastTxCLI(ctx client.Context, flagSet *pflag.FlagSet, msgs . isAux, _ := flagSet.GetBool(flags2.FlagAux) if isAux { - return generateAuxSignerData(ctx, txf, msgs...) + offline, _ := flagSet.GetBool(flags2.FlagOffline) + return generateAuxSignerData(ctx, txf, offline, msgs...) } genOnly, _ := flagSet.GetBool(flags2.FlagGenerateOnly) @@ -99,8 +100,8 @@ func validateMessages(msgs ...transaction.Msg) error { } // generateAuxSignerData simply generates and prints the AuxSignerData. -func generateAuxSignerData(ctx client.Context, txf Factory, msgs ...transaction.Msg) error { - auxSignerData, err := makeAuxSignerData(txf, msgs...) +func generateAuxSignerData(ctx client.Context, txf Factory, offline bool, msgs ...transaction.Msg) error { + auxSignerData, err := makeAuxSignerData(txf, offline, msgs...) if err != nil { return err } @@ -123,9 +124,9 @@ func generateOnly(ctx client.Context, txf Factory, msgs ...transaction.Msg) erro // dryRun performs a dry run of the transaction to estimate the gas required. // It prepares the transaction factory and simulates the transaction with the provided messages. func dryRun(txf Factory, msgs ...transaction.Msg) error { - if txf.txParams.offline { - return errors.New("dry-run: cannot use offline mode") - } + //if txf.txParams.offline { + // return errors.New("dry-run: cannot use offline mode") + //} _, gas, err := txf.Simulate(msgs...) if err != nil { @@ -214,11 +215,11 @@ func BroadcastTx(clientCtx client.Context, txf Factory, msgs ...transaction.Msg) } // makeAuxSignerData generates an AuxSignerData from the client inputs. -func makeAuxSignerData(f Factory, msgs ...transaction.Msg) (*apitx.AuxSignerData, error) { +func makeAuxSignerData(f Factory, offline bool, msgs ...transaction.Msg) (*apitx.AuxSignerData, error) { b := NewAuxTxBuilder() b.SetAddress(f.txParams.fromAddress) - if f.txParams.offline { + if offline { b.SetAccountNumber(f.accountNumber()) b.SetSequence(f.sequence()) } else { diff --git a/client/v2/tx/types.go b/client/v2/tx/types.go index eb915d879450..16e5770cd1ba 100644 --- a/client/v2/tx/types.go +++ b/client/v2/tx/types.go @@ -106,7 +106,6 @@ func NewFeeConfig(fees, feePayer, feeGranter string) (FeeConfig, error) { // ExecutionOptions defines the settings for transaction execution. type ExecutionOptions struct { unordered bool // unordered indicates if the transaction execution order is not guaranteed. - offline bool // offline specifies if the transaction should be prepared for offline signing. simulateAndExecute bool // simulateAndExecute indicates if the transaction should be simulated before execution. } @@ -168,7 +167,6 @@ func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring, ac addr feeGrater, _ := flags.GetString(flags2.FlagFeeGranter) unordered, _ := flags.GetBool(flags2.FlagUnordered) - offline, _ := flags.GetBool(flags2.FlagOffline) gasConfig, err := NewGasConfig(gasSetting.Gas, gasAdjustment, gasPrices) if err != nil { @@ -195,7 +193,6 @@ func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring, ac addr FeeConfig: feeConfig, ExecutionOptions: ExecutionOptions{ unordered: unordered, - offline: offline, simulateAndExecute: gasSetting.Simulate, }, } From d849302eddf1c2dc2f2184b2900a9d0f5c9de00e Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Wed, 21 Aug 2024 11:09:17 +0200 Subject: [PATCH 32/36] del: remove default gas --- client/v2/tx/factory.go | 13 +------------ client/v2/tx/factory_test.go | 19 ------------------- client/v2/tx/tx.go | 5 ----- client/v2/tx/types.go | 6 ------ 4 files changed, 1 insertion(+), 42 deletions(-) diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index b356f9c91f53..28c551b59e4b 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -110,7 +110,7 @@ func validateFlagSet(flags *pflag.FlagSet, offline bool) error { if offline && dryRun { return errors.New("dry-run: cannot use offline mode") } - + return nil } @@ -147,14 +147,6 @@ func prepareTxParams(parameters TxParameters, accRetriever account.AccountRetrie // 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 ...transaction.Msg) (TxBuilder, error) { - //if f.txParams.offline && f.txParams.generateOnly { - // if f.txParams.chainID != "" { - // return nil, errors.New("chain ID cannot be used when offline and generate-only flags are set") - // } - //} else if f.txParams.chainID == "" { - // return nil, errors.New("chain ID required but not specified") - //} - fees := f.txParams.fees isGasPriceZero, err := coins.IsZero(f.txParams.gasPrices) @@ -224,9 +216,6 @@ func (f *Factory) BuildsSignedTx(ctx context.Context, msgs ...transaction.Msg) ( // calculateGas calculates the gas required for the given messages. func (f *Factory) calculateGas(msgs ...transaction.Msg) error { - //if f.txParams.offline { - // return errors.New("cannot simulate in offline mode") - //} _, adjusted, err := f.Simulate(msgs...) if err != nil { return err diff --git a/client/v2/tx/factory_test.go b/client/v2/tx/factory_test.go index 68c83084a8dc..44c8b92bca40 100644 --- a/client/v2/tx/factory_test.go +++ b/client/v2/tx/factory_test.go @@ -147,25 +147,6 @@ func TestFactory_calculateGas(t *testing.T) { }, }, }, - { - name: "offline mode", - txParams: TxParameters{ - chainID: "demo", - AccountConfig: AccountConfig{ - address: addr, - }, - ExecutionOptions: ExecutionOptions{ - offline: true, - }, - }, - msgs: []transaction.Msg{ - &countertypes.MsgIncreaseCounter{ - Signer: signer, - Count: 0, - }, - }, - error: true, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/client/v2/tx/tx.go b/client/v2/tx/tx.go index aedcc60cdccb..d8d85722fc06 100644 --- a/client/v2/tx/tx.go +++ b/client/v2/tx/tx.go @@ -22,7 +22,6 @@ import ( // GenerateOrBroadcastTxCLI will either generate and print an unsigned transaction // or sign it and broadcast it returning an error upon failure. func GenerateOrBroadcastTxCLI(ctx client.Context, flagSet *pflag.FlagSet, msgs ...transaction.Msg) error { - if err := validateMessages(msgs...); err != nil { return err } @@ -124,10 +123,6 @@ func generateOnly(ctx client.Context, txf Factory, msgs ...transaction.Msg) erro // dryRun performs a dry run of the transaction to estimate the gas required. // It prepares the transaction factory and simulates the transaction with the provided messages. func dryRun(txf Factory, msgs ...transaction.Msg) error { - //if txf.txParams.offline { - // return errors.New("dry-run: cannot use offline mode") - //} - _, gas, err := txf.Simulate(msgs...) if err != nil { return err diff --git a/client/v2/tx/types.go b/client/v2/tx/types.go index 16e5770cd1ba..98f591f7823d 100644 --- a/client/v2/tx/types.go +++ b/client/v2/tx/types.go @@ -16,8 +16,6 @@ import ( cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" ) -const defaultGas = 200000 - // HasValidateBasic is a copy of types.HasValidateBasic to avoid sdk import. type HasValidateBasic interface { // ValidateBasic does a simple validation check that @@ -64,10 +62,6 @@ type GasConfig struct { // If the provided gas value is zero, it defaults to a predefined value (defaultGas). // The gasPrices string is parsed into a slice of DecCoin. func NewGasConfig(gas uint64, gasAdjustment float64, gasPrices string) (GasConfig, error) { - if gas == 0 { - gas = defaultGas - } - parsedGasPrices, err := coins.ParseDecCoins(gasPrices) if err != nil { return GasConfig{}, err From 7ffed78016c17086256069aa5a1dc23f8f6798dc Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Wed, 21 Aug 2024 11:18:45 +0200 Subject: [PATCH 33/36] lint --- client/v2/tx/factory.go | 9 ++++----- client/v2/tx/tx.go | 12 +++++++----- client/v2/tx/types.go | 9 +++++---- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index 28c551b59e4b..222330a96817 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -7,11 +7,9 @@ import ( "math/big" "strings" - flags2 "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/spf13/pflag" - "github.com/cosmos/go-bip39" gogogrpc "github.com/cosmos/gogoproto/grpc" + "github.com/spf13/pflag" "google.golang.org/protobuf/types/known/anypb" base "cosmossdk.io/api/cosmos/base/v1beta1" @@ -26,6 +24,7 @@ import ( "cosmossdk.io/math" "cosmossdk.io/x/tx/signing" + flags2 "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" @@ -48,7 +47,8 @@ type Factory struct { } func NewFactoryFromFlagSet(flags *pflag.FlagSet, keybase keyring.Keyring, cdc codec.BinaryCodec, accRetriever account.AccountRetriever, - txConfig TxConfig, ac address.Codec, conn gogogrpc.ClientConn) (Factory, error) { + txConfig TxConfig, ac address.Codec, conn gogogrpc.ClientConn, +) (Factory, error) { offline, _ := flags.GetBool(flags2.FlagOffline) if err := validateFlagSet(flags, offline); err != nil { return Factory{}, err @@ -71,7 +71,6 @@ func NewFactoryFromFlagSet(flags *pflag.FlagSet, keybase keyring.Keyring, cdc co func NewFactory(keybase keyring.Keyring, cdc codec.BinaryCodec, accRetriever account.AccountRetriever, txConfig TxConfig, ac address.Codec, conn gogogrpc.ClientConn, parameters TxParameters, ) (Factory, error) { - return Factory{ keybase: keybase, cdc: cdc, diff --git a/client/v2/tx/tx.go b/client/v2/tx/tx.go index d8d85722fc06..885345083987 100644 --- a/client/v2/tx/tx.go +++ b/client/v2/tx/tx.go @@ -3,15 +3,17 @@ package tx import ( "bufio" "context" - apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" - apitx "cosmossdk.io/api/cosmos/tx/v1beta1" - "cosmossdk.io/client/v2/internal/account" - "cosmossdk.io/core/transaction" "errors" "fmt" + "os" + "github.com/cosmos/gogoproto/proto" "github.com/spf13/pflag" - "os" + + apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + apitx "cosmossdk.io/api/cosmos/tx/v1beta1" + "cosmossdk.io/client/v2/internal/account" + "cosmossdk.io/core/transaction" "github.com/cosmos/cosmos-sdk/client" flags2 "github.com/cosmos/cosmos-sdk/client/flags" diff --git a/client/v2/tx/types.go b/client/v2/tx/types.go index 98f591f7823d..d81341359183 100644 --- a/client/v2/tx/types.go +++ b/client/v2/tx/types.go @@ -1,18 +1,19 @@ package tx import ( - keyring2 "cosmossdk.io/client/v2/autocli/keyring" - "cosmossdk.io/core/address" "fmt" - flags2 "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/spf13/pflag" "time" + "github.com/spf13/pflag" + base "cosmossdk.io/api/cosmos/base/v1beta1" apitxsigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + keyring2 "cosmossdk.io/client/v2/autocli/keyring" "cosmossdk.io/client/v2/internal/coins" + "cosmossdk.io/core/address" "cosmossdk.io/core/transaction" + flags2 "github.com/cosmos/cosmos-sdk/client/flags" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" ) From 4a51df03bb485ac9cb86b338e9c936f086b33431 Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Thu, 19 Sep 2024 09:47:38 +0200 Subject: [PATCH 34/36] avoid flags2 --- client/v2/internal/account/retriever.go | 2 +- client/v2/internal/grpc/client.go | 21 ---------- client/v2/tx/README.md | 2 - client/v2/tx/builder.go | 11 ----- client/v2/tx/flags.go | 53 +++++++++++++++++++++++++ client/v2/tx/tx.go | 11 +++-- client/v2/tx/types.go | 38 +++++++++--------- 7 files changed, 77 insertions(+), 61 deletions(-) delete mode 100644 client/v2/internal/grpc/client.go create mode 100644 client/v2/tx/flags.go diff --git a/client/v2/internal/account/retriever.go b/client/v2/internal/account/retriever.go index 045f36781f29..2cef69f92cf8 100644 --- a/client/v2/internal/account/retriever.go +++ b/client/v2/internal/account/retriever.go @@ -12,11 +12,11 @@ import ( "google.golang.org/grpc/status" "cosmossdk.io/core/address" - authtypes "cosmossdk.io/x/auth/types" codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" ) // GRPCBlockHeightHeader represents the gRPC header for block height. diff --git a/client/v2/internal/grpc/client.go b/client/v2/internal/grpc/client.go deleted file mode 100644 index ef671260075c..000000000000 --- a/client/v2/internal/grpc/client.go +++ /dev/null @@ -1,21 +0,0 @@ -package grpc - -import ( - "context" - "fmt" - - gogogrpc "github.com/cosmos/gogoproto/grpc" - "google.golang.org/grpc" -) - -var _ gogogrpc.ClientConn = ClientConn{} - -type ClientConn struct{} - -func (c ClientConn) Invoke(ctx context.Context, method string, args, reply interface{}, opts ...grpc.CallOption) error { - panic("implement me") -} - -func (c ClientConn) NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) { - return nil, fmt.Errorf("streaming rpc not supported") -} diff --git a/client/v2/tx/README.md b/client/v2/tx/README.md index e0e5a4842770..b0456d8afa5c 100644 --- a/client/v2/tx/README.md +++ b/client/v2/tx/README.md @@ -158,7 +158,6 @@ classDiagram class TxBuilderProvider { <> NewTxBuilder() TxBuilder - WrapTxBuilder(*apitx.Tx) (TxBuilder, error) } class BuilderProvider { @@ -166,7 +165,6 @@ classDiagram decoder Decoder codec codec.BinaryCodec NewTxBuilder() TxBuilder - WrapTxBuilder(*apitx.Tx) (TxBuilder, error) } TxBuilder <|.. txBuilder : implements diff --git a/client/v2/tx/builder.go b/client/v2/tx/builder.go index 5df762d69d73..e3ae6e707cb9 100644 --- a/client/v2/tx/builder.go +++ b/client/v2/tx/builder.go @@ -43,7 +43,6 @@ type TxBuilder interface { // TxBuilderProvider provides a TxBuilder. type TxBuilderProvider interface { NewTxBuilder() TxBuilder - WrapTxBuilder(*apitx.Tx) (TxBuilder, error) } // BuilderProvider implements TxBuilderProvider. @@ -67,16 +66,6 @@ func (b BuilderProvider) NewTxBuilder() TxBuilder { return newTxBuilder(b.addressCodec, b.decoder, b.codec) } -// WrapTxBuilder -// TODO: is this necessary -func (b BuilderProvider) WrapTxBuilder(tx *apitx.Tx) (TxBuilder, error) { - return &txBuilder{ - addressCodec: b.addressCodec, - decoder: b.decoder, - codec: b.codec, - }, nil -} - type txBuilder struct { addressCodec address.Codec decoder Decoder diff --git a/client/v2/tx/flags.go b/client/v2/tx/flags.go new file mode 100644 index 000000000000..46383da166d2 --- /dev/null +++ b/client/v2/tx/flags.go @@ -0,0 +1,53 @@ +package tx + +import ( + "fmt" + "strconv" +) + +// Flag constants for transaction-related flags +const ( + defaultGasLimit = 200000 + gasFlagAuto = "auto" + + flagTimeoutTimestamp = "timeout-timestamp" + flagChainID = "chain-id" + flagNote = "note" + flagSignMode = "sign-mode" + flagAccountNumber = "account-number" + flagSequence = "sequence" + flagFrom = "from" + flagDryRun = "dry-run" + flagGas = "gas" + flagGasAdjustment = "gas-adjustment" + flagGasPrices = "gas-prices" + flagFees = "fees" + flagFeePayer = "fee-payer" + flagFeeGranter = "fee-granter" + flagUnordered = "unordered" + flagAux = "aux" + flagOffline = "offline" + flagGenerateOnly = "generate-only" +) + +// parseGasSetting parses a string gas value. The value may either be 'auto', +// which indicates a transaction should be executed in simulate mode to +// automatically find a sufficient gas value, or a string integer. It returns an +// error if a string integer is provided which cannot be parsed. +func parseGasSetting(gasStr string) (bool, uint64, error) { + switch gasStr { + case "": + return false, defaultGasLimit, nil + + case gasFlagAuto: + return true, 0, nil + + default: + gas, err := strconv.ParseUint(gasStr, 10, 64) + if err != nil { + return false, 0, fmt.Errorf("gas must be either integer or %s", gasFlagAuto) + } + + return false, gas, nil + } +} diff --git a/client/v2/tx/tx.go b/client/v2/tx/tx.go index 885345083987..564f5a133d1b 100644 --- a/client/v2/tx/tx.go +++ b/client/v2/tx/tx.go @@ -16,7 +16,6 @@ import ( "cosmossdk.io/core/transaction" "github.com/cosmos/cosmos-sdk/client" - flags2 "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/input" "github.com/cosmos/cosmos-sdk/crypto/keyring" ) @@ -33,18 +32,18 @@ func GenerateOrBroadcastTxCLI(ctx client.Context, flagSet *pflag.FlagSet, msgs . return err } - isAux, _ := flagSet.GetBool(flags2.FlagAux) + isAux, _ := flagSet.GetBool(flagAux) if isAux { - offline, _ := flagSet.GetBool(flags2.FlagOffline) + offline, _ := flagSet.GetBool(flagOffline) return generateAuxSignerData(ctx, txf, offline, msgs...) } - genOnly, _ := flagSet.GetBool(flags2.FlagGenerateOnly) + genOnly, _ := flagSet.GetBool(flagGenerateOnly) if genOnly { return generateOnly(ctx, txf, msgs...) } - isDryRun, _ := flagSet.GetBool(flags2.FlagDryRun) + isDryRun, _ := flagSet.GetBool(flagDryRun) if isDryRun { return dryRun(txf, msgs...) } @@ -65,7 +64,7 @@ func newFactory(ctx client.Context, flagSet *pflag.FlagSet) (Factory, error) { AddressCodec: ctx.AddressCodec, Cdc: ctx.Codec, ValidatorAddressCodec: ctx.ValidatorAddressCodec, - // EnablesSignModes: ctx.TxConfig.SignModeHandler().SupportedModes(), + EnablesSignModes: ctx.TxConfig.SignModeHandler().SupportedModes(), }) if err != nil { return Factory{}, err diff --git a/client/v2/tx/types.go b/client/v2/tx/types.go index d81341359183..4abb74d4d1c5 100644 --- a/client/v2/tx/types.go +++ b/client/v2/tx/types.go @@ -13,7 +13,6 @@ import ( "cosmossdk.io/core/address" "cosmossdk.io/core/transaction" - flags2 "github.com/cosmos/cosmos-sdk/client/flags" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" ) @@ -98,7 +97,6 @@ func NewFeeConfig(fees, feePayer, feeGranter string) (FeeConfig, error) { } // ExecutionOptions defines the transaction execution options ran by the client -// ExecutionOptions defines the settings for transaction execution. type ExecutionOptions struct { unordered bool // unordered indicates if the transaction execution order is not guaranteed. simulateAndExecute bool // simulateAndExecute indicates if the transaction should be simulated before execution. @@ -127,19 +125,19 @@ type Tx interface { // txParamsFromFlagSet extracts the transaction parameters from the provided FlagSet. func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring, ac address.Codec) (params TxParameters, err error) { - timestampUnix, _ := flags.GetInt64(flags2.FlagTimeoutTimestamp) + timestampUnix, _ := flags.GetInt64(flagTimeoutTimestamp) timeoutTimestamp := time.Unix(timestampUnix, 0) - chainID, _ := flags.GetString(flags2.FlagChainID) - memo, _ := flags.GetString(flags2.FlagNote) - signMode, _ := flags.GetString(flags2.FlagSignMode) + chainID, _ := flags.GetString(flagChainID) + memo, _ := flags.GetString(flagNote) + signMode, _ := flags.GetString(flagSignMode) - accNumber, _ := flags.GetUint64(flags2.FlagAccountNumber) - sequence, _ := flags.GetUint64(flags2.FlagSequence) - from, _ := flags.GetString(flags2.FlagFrom) + accNumber, _ := flags.GetUint64(flagAccountNumber) + sequence, _ := flags.GetUint64(flagSequence) + from, _ := flags.GetString(flagFrom) var fromName, fromAddress string var addr []byte - isDryRun, _ := flags.GetBool(flags2.FlagDryRun) + isDryRun, _ := flags.GetBool(flagDryRun) if isDryRun { addr, err = ac.StringToBytes(from) } else { @@ -152,18 +150,18 @@ func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring, ac addr return params, err } - gas, _ := flags.GetString(flags2.FlagGas) - gasSetting, _ := flags2.ParseGasSetting(gas) - gasAdjustment, _ := flags.GetFloat64(flags2.FlagGasAdjustment) - gasPrices, _ := flags.GetString(flags2.FlagGasPrices) + gas, _ := flags.GetString(flagGas) + simulate, gasValue, _ := parseGasSetting(gas) + gasAdjustment, _ := flags.GetFloat64(flagGasAdjustment) + gasPrices, _ := flags.GetString(flagGasPrices) - fees, _ := flags.GetString(flags2.FlagFees) - feePayer, _ := flags.GetString(flags2.FlagFeePayer) - feeGrater, _ := flags.GetString(flags2.FlagFeeGranter) + fees, _ := flags.GetString(flagFees) + feePayer, _ := flags.GetString(flagFeePayer) + feeGrater, _ := flags.GetString(flagFeeGranter) - unordered, _ := flags.GetBool(flags2.FlagUnordered) + unordered, _ := flags.GetBool(flagUnordered) - gasConfig, err := NewGasConfig(gasSetting.Gas, gasAdjustment, gasPrices) + gasConfig, err := NewGasConfig(gasValue, gasAdjustment, gasPrices) if err != nil { return params, err } @@ -188,7 +186,7 @@ func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring, ac addr FeeConfig: feeConfig, ExecutionOptions: ExecutionOptions{ unordered: unordered, - simulateAndExecute: gasSetting.Simulate, + simulateAndExecute: simulate, }, } From 9153602b6811c386926888846c0c7541dc34d470 Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Thu, 19 Sep 2024 09:55:44 +0200 Subject: [PATCH 35/36] lint-happy --- client/v2/tx/aux_builder_test.go | 1 - client/v2/tx/factory.go | 5 ----- 2 files changed, 6 deletions(-) diff --git a/client/v2/tx/aux_builder_test.go b/client/v2/tx/aux_builder_test.go index a560b2f111cb..5c11ccd41400 100644 --- a/client/v2/tx/aux_builder_test.go +++ b/client/v2/tx/aux_builder_test.go @@ -211,7 +211,6 @@ func TestAuxTxBuilder(t *testing.T) { } for _, tc := range testcases { - tc := tc t.Run(tc.name, func(t *testing.T) { b = NewAuxTxBuilder() err := tc.malleate() diff --git a/client/v2/tx/factory.go b/client/v2/tx/factory.go index 222330a96817..75a127c88109 100644 --- a/client/v2/tx/factory.go +++ b/client/v2/tx/factory.go @@ -42,8 +42,6 @@ type Factory struct { conn gogogrpc.ClientConn txConfig TxConfig txParams TxParameters - - cachedTx TxBuilder } func NewFactoryFromFlagSet(flags *pflag.FlagSet, keybase keyring.Keyring, cdc codec.BinaryCodec, accRetriever account.AccountRetriever, @@ -475,9 +473,6 @@ func (f *Factory) sequence() uint64 { return f.txParams.sequence } // gasAdjustment returns the gas adjustment value. func (f *Factory) gasAdjustment() float64 { return f.txParams.gasAdjustment } -// keyring returns the keyring. -func (f *Factory) keyring() keyring.Keyring { return f.keybase } - // simulateAndExecute returns whether to simulate and execute. func (f *Factory) simulateAndExecute() bool { return f.txParams.simulateAndExecute } From 954749971c41a839774c52baed86570e953906a8 Mon Sep 17 00:00:00 2001 From: Julian Toledano Date: Thu, 19 Sep 2024 09:59:01 +0200 Subject: [PATCH 36/36] tidy --- client/v2/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/v2/go.mod b/client/v2/go.mod index 64655bbfc946..fe96c6d70879 100644 --- a/client/v2/go.mod +++ b/client/v2/go.mod @@ -52,7 +52,7 @@ require ( github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/cosmos-db v1.0.3-0.20240911104526-ddc3f09bfc22 // indirect github.com/cosmos/crypto v0.1.2 // indirect - github.com/cosmos/go-bip39 v1.0.0 // indirect + github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/gogogateway v1.2.0 // indirect github.com/cosmos/gogoproto v1.7.0 github.com/cosmos/iavl v1.3.0 // indirect