From 8fc9f76329dd2433d9b258a867500de419522619 Mon Sep 17 00:00:00 2001 From: Amaury <1293565+amaurym@users.noreply.github.com> Date: Thu, 11 Nov 2021 12:25:13 +0100 Subject: [PATCH] feat: Add AuxTxBuilder (#10455) ## Description Closes: #10443 For creating an intermediary/auxiliary tx (e.g. by the tipper in tipped transactions), using the existing `client.TxBuilder` is awkward. We propose a new client-side builder for this purpose. API Usage (e.g. how the tipper would programmtically use this): ```go // Note: there's no need to use clientCtx.TxConfig anymore! bldr := clienttx.NewAuxTxBuilder() err := bldr.SetMsgs(msgs...) bldr.SetAddress("cosmos1...") bldr.SetMemo(...) bldr.SetTip(...) bldr.SetPubKey(...) err := bldr.SetSignMode(...) // DIRECT_AUX or AMINO, or else error // ... other setters are available // Get the bytes to sign. signBz, err := bldr.GetSignBytes() // Sign the bz using your favorite method. sig, err := privKey.sign(signBz) // Set the signature bldr.SetSig(sig) // Get the final auxSignerData to be sent to the fee payer auxSignerData, err:= bldr.GetAuxSignerData() ``` auxSignerData is a protobuf message, whose JSON reprensentation looks like: ```json { "address": "cosmos1...", "mode": "SIGN_MODE_{DIRECT_AUX,LEGACY_AMINO_JSON}", "sign_doc": { "body_bytes": "{base64 bytes}", "public_key": { "@type": "cosmos.sepc256k1.PubKey", "key": "{base64 bytes}" }, "chain_id": "...", "account_number": 24, "sequence": 42, "tip": { "amount": [{ "denom": "uregen", "amount": 1000 }], "tipper": "cosmos1..." } }, "sig": "{base64 bytes}" } ``` Then the fee payer would use the TxBuilder to construct the final TX, with a new helper method: `AddAuxSignerData`: ```go // get auxSignerData from AuxTxBuilder auxSignerData := ... txBuilder := txConfig.NewTxBuilder() err := txBuilder.AddAuxSignerData(auxSignerData) // Set fee payer data txBuilder.SetFee() txBuilder.SetGasLimit() txBuilder.SetFeePayer() sigs, err := txBuilder.GetSignaturesV2() auxSig := sigs[0] // the aux signer's signature // Set all signer infos (1st round of calling SetSignatures) txBuilder.SetSignatures( auxSig, // The aux SignatureV2 signing.SignatureV2{...}, // The feePayer's SignatureV2 ) // Sign signBz, err = encCfg.TxConfig.SignModeHandler().GetSignBytes(...) feepayerSig, err := feepayerPriv.Sign(signBz) // Set all signatures (2nd round of calling SetSignatures) txBuilder.SetSignatures( auxSig, // The aux SignatureV2 signing.SignatureV2{feepayerSig, ...}, // The feePayer's SignatureV2 ) ``` --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] added `!` to the type prefix if API or client breaking change - [ ] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] provided a link to the relevant issue or specification - [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules) - [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [ ] added a changelog entry to `CHANGELOG.md` - [ ] included comments for [documenting Go code](https://blog.golang.org/godoc) - [ ] updated the relevant documentation or specification - [ ] reviewed "Files changed" and left comments if necessary - [ ] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable) --- client/tx/aux_builder.go | 212 +++++++++ client/tx/aux_builder_test.go | 254 +++++++++++ client/tx/legacy_test.go | 7 +- client/tx_config.go | 2 + docs/core/proto-docs.md | 24 + proto/cosmos/group/v1beta1/types.proto | 1 - proto/cosmos/tx/v1beta1/tx.proto | 21 + types/tx/aux.go | 54 +++ types/tx/aux_test.go | 83 ++++ types/tx/tx.pb.go | 458 +++++++++++++++++--- x/auth/migrations/legacytx/stdtx_builder.go | 8 + x/auth/tx/aux_test.go | 147 +++++++ x/auth/tx/builder.go | 70 ++- x/auth/tx/builder_test.go | 8 +- x/auth/tx/config.go | 2 +- x/auth/tx/encode_decode_test.go | 2 +- x/auth/tx/legacy_amino_json_test.go | 8 +- 17 files changed, 1287 insertions(+), 74 deletions(-) create mode 100644 client/tx/aux_builder.go create mode 100644 client/tx/aux_builder_test.go create mode 100644 types/tx/aux.go create mode 100644 types/tx/aux_test.go create mode 100644 x/auth/tx/aux_test.go diff --git a/client/tx/aux_builder.go b/client/tx/aux_builder.go new file mode 100644 index 000000000000..1e0c1a91a77c --- /dev/null +++ b/client/tx/aux_builder.go @@ -0,0 +1,212 @@ +package tx + +import ( + "github.com/gogo/protobuf/proto" + + 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" + "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" +) + +// 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 []sdk.Msg + body *tx.TxBody + auxSignerData *tx.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 +} + +// SetTimeoutHeight sets a timeout height in the tx. +func (b *AuxTxBuilder) SetTimeoutHeight(height uint64) { + b.checkEmptyFields() + + b.body.TimeoutHeight = height +} + +// SetMsgs sets an array of Msgs in the tx. +func (b *AuxTxBuilder) SetMsgs(msgs ...sdk.Msg) error { + anys := make([]*codectypes.Any, len(msgs)) + for i, msg := range msgs { + var err error + anys[i], err = codectypes.NewAnyWithValue(msg) + if err != nil { + return err + } + } + + b.checkEmptyFields() + + b.msgs = msgs + b.body.Messages = anys + + 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 { + any, err := codectypes.NewAnyWithValue(pk) + if err != nil { + return err + } + + b.checkEmptyFields() + + b.auxSignerData.SignDoc.PublicKey = any + + 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 signing.SignMode) error { + switch mode { + case signing.SignMode_SIGN_MODE_DIRECT_AUX, signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON: + default: + return sdkerrors.ErrInvalidRequest.Wrapf("AuxTxBuilder can only sign with %s or %s", signing.SignMode_SIGN_MODE_DIRECT_AUX, signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON) + } + + b.auxSignerData.Mode = mode + + return nil +} + +// SetTip sets an optional tip. +func (b *AuxTxBuilder) SetTip(tip *tx.Tip) { + b.checkEmptyFields() + + b.auxSignerData.SignDoc.Tip = tip +} + +// SetSignature sets the aux signer's signature. +func (b *AuxTxBuilder) SetSignature(sig []byte) { + b.checkEmptyFields() + + b.auxSignerData.Sig = sig +} + +// 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 := b.auxSignerData.SignDoc.ValidateBasic(); err != nil { + return nil, err + } + + var signBz []byte + switch b.auxSignerData.Mode { + case signing.SignMode_SIGN_MODE_DIRECT_AUX: + { + signBz, err = proto.Marshal(b.auxSignerData.SignDoc) + if err != nil { + return nil, err + } + } + case signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON: + { + signBz = legacytx.StdSignBytes( + b.auxSignerData.SignDoc.ChainId, b.auxSignerData.SignDoc.AccountNumber, + b.auxSignerData.SignDoc.Sequence, b.body.TimeoutHeight, + // 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 + legacytx.StdFee{}, + b.msgs, b.body.Memo, b.auxSignerData.SignDoc.Tip, + ) + } + 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() (tx.AuxSignerData, error) { + if err := b.auxSignerData.ValidateBasic(); err != nil { + return tx.AuxSignerData{}, err + } + + return *b.auxSignerData, nil +} + +func (b *AuxTxBuilder) checkEmptyFields() { + if b.body == nil { + b.body = &tx.TxBody{} + } + + if b.auxSignerData == nil { + b.auxSignerData = &tx.AuxSignerData{} + if b.auxSignerData.SignDoc == nil { + b.auxSignerData.SignDoc = &tx.SignDocDirectAux{} + } + } +} diff --git a/client/tx/aux_builder_test.go b/client/tx/aux_builder_test.go new file mode 100644 index 000000000000..385c98f4c456 --- /dev/null +++ b/client/tx/aux_builder_test.go @@ -0,0 +1,254 @@ +package tx_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/simapp" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + typestx "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/cosmos/cosmos-sdk/types/tx/signing" +) + +func TestAuxTxBuilder(t *testing.T) { + encCfg := simapp.MakeTestEncodingConfig() + testdata.RegisterInterfaces(encCfg.InterfaceRegistry) + + 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(signing.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 { + b.SetMsgs(msg1) + + _, err := b.GetSignBytes() + return err + }, + true, "public key cannot be empty: invalid pubkey", + }, + { + "GetSignBytes invalid sign mode", + func() error { + b.SetMsgs(msg1) + b.SetPubKey(pub1) + + _, err := b.GetSignBytes() + return err + }, + true, "got unknown sign mode SIGN_MODE_UNSPECIFIED", + }, + { + "GetSignBytes tipper should not be nil (if tip is set)", + func() error { + b.SetMsgs(msg1) + b.SetPubKey(pub1) + b.SetTip(&typestx.Tip{}) + err := b.SetSignMode(signing.SignMode_SIGN_MODE_DIRECT_AUX) + require.NoError(t, err) + + _, err = b.GetSignBytes() + return err + }, + true, "tipper cannot be empty", + }, + { + "GetSignBytes works for DIRECT_AUX", + func() error { + b.SetMsgs(msg1) + b.SetPubKey(pub1) + b.SetTip(tip) + err := b.SetSignMode(signing.SignMode_SIGN_MODE_DIRECT_AUX) + require.NoError(t, err) + + _, err = b.GetSignBytes() + return err + }, + false, "", + }, + { + "GetAuxSignerData address should not be empty", + func() error { + b.SetMsgs(msg1) + b.SetPubKey(pub1) + b.SetTip(tip) + err := b.SetSignMode(signing.SignMode_SIGN_MODE_DIRECT_AUX) + require.NoError(t, err) + + _, 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 { + b.SetMsgs(msg1) + b.SetPubKey(pub1) + b.SetTip(tip) + b.SetAddress(addr1.String()) + err := b.SetSignMode(signing.SignMode_SIGN_MODE_DIRECT_AUX) + require.NoError(t, err) + + _, 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) + b.SetMsgs(msg1) + b.SetPubKey(pub1) + b.SetTip(tip) + b.SetAddress(addr1.String()) + err := b.SetSignMode(signing.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, encCfg.Codec, auxSignerData, signing.SignMode_SIGN_MODE_DIRECT_AUX) + + return err + }, + false, "", + }, + { + "GetSignBytes works for LEGACY_AMINO_JSON", + func() error { + b.SetMsgs(msg1) + b.SetPubKey(pub1) + b.SetTip(tip) + b.SetAddress(addr1.String()) + err := b.SetSignMode(signing.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) + b.SetMsgs(msg1) + b.SetPubKey(pub1) + b.SetTip(tip) + b.SetAddress(addr1.String()) + err := b.SetSignMode(signing.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, encCfg.Codec, 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 typestx.AuxSignerData, signMode signing.SignMode) { + 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, tip, auxSignerData.SignDoc.Tip) + require.Equal(t, signMode, auxSignerData.Mode) + require.Equal(t, rawSig, auxSignerData.Sig) +} diff --git a/client/tx/legacy_test.go b/client/tx/legacy_test.go index 65949a038fa1..8d5b451b7c09 100644 --- a/client/tx/legacy_test.go +++ b/client/tx/legacy_test.go @@ -13,6 +13,7 @@ import ( "github.com/cosmos/cosmos-sdk/simapp/params" "github.com/cosmos/cosmos-sdk/testutil/testdata" "github.com/cosmos/cosmos-sdk/types" + typestx "github.com/cosmos/cosmos-sdk/types/tx" signing2 "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" "github.com/cosmos/cosmos-sdk/x/auth/tx" @@ -29,15 +30,19 @@ var ( fee = types.NewCoins(types.NewInt64Coin("bam", 100)) _, pub1, addr1 = testdata.KeyTestPubAddr() _, _, addr2 = testdata.KeyTestPubAddr() + rawSig = []byte("dummy") sig = signing2.SignatureV2{ PubKey: pub1, Data: &signing2.SingleSignatureData{ SignMode: signing2.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, - Signature: []byte("dummy"), + Signature: rawSig, }, } msg0 = banktypes.NewMsgSend(addr1, addr2, types.NewCoins(types.NewInt64Coin("wack", 1))) msg1 = banktypes.NewMsgSend(addr1, addr2, types.NewCoins(types.NewInt64Coin("wack", 2))) + + chainID = "test-chain" + tip = &typestx.Tip{Tipper: addr1.String(), Amount: testdata.NewTestFeeAmount()} ) func buildTestTx(t *testing.T, builder client.TxBuilder) { diff --git a/client/tx_config.go b/client/tx_config.go index c9db30ff0d7d..cb82a06e6686 100644 --- a/client/tx_config.go +++ b/client/tx_config.go @@ -44,6 +44,8 @@ type ( SetGasLimit(limit uint64) SetTip(tip *tx.Tip) SetTimeoutHeight(height uint64) + SetFeePayer(feeGranter sdk.AccAddress) SetFeeGranter(feeGranter sdk.AccAddress) + AddAuxSignerData(tx.AuxSignerData) error } ) diff --git a/docs/core/proto-docs.md b/docs/core/proto-docs.md index 4fb7c12d4524..af45ac474ab3 100644 --- a/docs/core/proto-docs.md +++ b/docs/core/proto-docs.md @@ -661,6 +661,7 @@ - [cosmos/tx/v1beta1/tx.proto](#cosmos/tx/v1beta1/tx.proto) - [AuthInfo](#cosmos.tx.v1beta1.AuthInfo) + - [AuxSignerData](#cosmos.tx.v1beta1.AuxSignerData) - [Fee](#cosmos.tx.v1beta1.Fee) - [ModeInfo](#cosmos.tx.v1beta1.ModeInfo) - [ModeInfo.Multi](#cosmos.tx.v1beta1.ModeInfo.Multi) @@ -9475,6 +9476,29 @@ Since: cosmos-sdk 0.45 | + + +### AuxSignerData +AuxSignerData is the intermediary format that an auxiliary signer (e.g. a +tipper) builds and sends to the fee payer (who will build and broadcast the +actual tx). AuxSignerData is not a valid tx in itself, and will be rejected +by the node if sent directly as-is. + +Since: cosmos-sdk 0.45 + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `address` | [string](#string) | | address is the bech32-encoded address of the auxiliary signer. If using AuxSignerData across different chains, the bech32 prefix of the target chain (where the final transaction is broadcasted) should be used. | +| `sign_doc` | [SignDocDirectAux](#cosmos.tx.v1beta1.SignDocDirectAux) | | sign_doc is the SIGN_MOD_DIRECT_AUX sign doc that the auxiliary signer signs. Note: we use the same sign doc even if we're signing with LEGACY_AMINO_JSON. | +| `mode` | [cosmos.tx.signing.v1beta1.SignMode](#cosmos.tx.signing.v1beta1.SignMode) | | mode is the signing mode of the single signer | +| `sig` | [bytes](#bytes) | | sig is the signature of the sign doc. | + + + + + + ### Fee diff --git a/proto/cosmos/group/v1beta1/types.proto b/proto/cosmos/group/v1beta1/types.proto index 7ef8ad43bd06..5c4a8f99dd47 100644 --- a/proto/cosmos/group/v1beta1/types.proto +++ b/proto/cosmos/group/v1beta1/types.proto @@ -9,7 +9,6 @@ import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; import "cosmos_proto/cosmos.proto"; import "google/protobuf/any.proto"; -import "cosmos_proto/cosmos.proto"; // Member represents a group member with an account address, // non-zero weight and metadata. diff --git a/proto/cosmos/tx/v1beta1/tx.proto b/proto/cosmos/tx/v1beta1/tx.proto index a26cad5d54df..5f63b9364bdd 100644 --- a/proto/cosmos/tx/v1beta1/tx.proto +++ b/proto/cosmos/tx/v1beta1/tx.proto @@ -221,3 +221,24 @@ message Tip { // tipper is the address of the account paying for the tip string tipper = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"]; } + +// AuxSignerData is the intermediary format that an auxiliary signer (e.g. a +// tipper) builds and sends to the fee payer (who will build and broadcast the +// actual tx). AuxSignerData is not a valid tx in itself, and will be rejected +// by the node if sent directly as-is. +// +// Since: cosmos-sdk 0.45 +message AuxSignerData { + // address is the bech32-encoded address of the auxiliary signer. If using + // AuxSignerData across different chains, the bech32 prefix of the target + // chain (where the final transaction is broadcasted) should be used. + string address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + // sign_doc is the SIGN_MOD_DIRECT_AUX sign doc that the auxiliary signer + // signs. Note: we use the same sign doc even if we're signing with + // LEGACY_AMINO_JSON. + SignDocDirectAux sign_doc = 2; + // mode is the signing mode of the single signer + cosmos.tx.signing.v1beta1.SignMode mode = 3; + // sig is the signature of the sign doc. + bytes sig = 4; +} diff --git a/types/tx/aux.go b/types/tx/aux.go new file mode 100644 index 000000000000..2c9a71262252 --- /dev/null +++ b/types/tx/aux.go @@ -0,0 +1,54 @@ +package tx + +import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/tx/signing" +) + +// ValidateBasic performs stateless validation of the sign doc. +func (s *SignDocDirectAux) ValidateBasic() error { + if len(s.BodyBytes) == 0 { + return sdkerrors.ErrInvalidRequest.Wrap("body bytes cannot be empty") + } + + if s.PublicKey == nil { + return sdkerrors.ErrInvalidPubKey.Wrap("public key cannot be empty") + } + + if s.Tip != nil { + if s.Tip.Tipper == "" { + return sdkerrors.ErrInvalidRequest.Wrap("tipper cannot be empty") + } + } + + return nil +} + +// UnpackInterfaces implements the UnpackInterfaceMessages.UnpackInterfaces method +func (s *SignDocDirectAux) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + return unpacker.UnpackAny(s.PublicKey, new(cryptotypes.PubKey)) +} + +// ValidateBasic performs stateless validation of the auxiliary tx. +func (a *AuxSignerData) ValidateBasic() error { + if a.Address == "" { + return sdkerrors.ErrInvalidRequest.Wrapf("address cannot be empty") + } + + if a.Mode != signing.SignMode_SIGN_MODE_DIRECT_AUX && a.Mode != signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON { + return sdkerrors.ErrInvalidRequest.Wrapf("AuxTxBuilder can only sign with %s or %s", signing.SignMode_SIGN_MODE_DIRECT_AUX, signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON) + } + + if len(a.Sig) == 0 { + return sdkerrors.ErrNoSignatures.Wrap("signature cannot be empty") + } + + return a.GetSignDoc().ValidateBasic() +} + +// UnpackInterfaces implements the UnpackInterfaceMessages.UnpackInterfaces method +func (a *AuxSignerData) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + return a.GetSignDoc().UnpackInterfaces(unpacker) +} diff --git a/types/tx/aux_test.go b/types/tx/aux_test.go new file mode 100644 index 000000000000..02b39f0f356e --- /dev/null +++ b/types/tx/aux_test.go @@ -0,0 +1,83 @@ +package tx_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/cosmos/cosmos-sdk/types/tx/signing" +) + +func TestSignDocDirectAux(t *testing.T) { + bodyBz := []byte{42} + _, pk, addr := testdata.KeyTestPubAddr() + pkAny, err := codectypes.NewAnyWithValue(pk) + require.NoError(t, err) + + testcases := []struct { + name string + sd tx.SignDocDirectAux + expErr bool + }{ + {"empty bodyBz", tx.SignDocDirectAux{}, true}, + {"empty pubkey", tx.SignDocDirectAux{BodyBytes: bodyBz}, true}, + {"empty tipper", tx.SignDocDirectAux{BodyBytes: bodyBz, PublicKey: pkAny, Tip: &tx.Tip{Amount: testdata.NewTestFeeAmount()}}, true}, + {"happy case w/o tip", tx.SignDocDirectAux{BodyBytes: bodyBz, PublicKey: pkAny}, false}, + {"happy case w/ tip", tx.SignDocDirectAux{ + BodyBytes: bodyBz, + PublicKey: pkAny, + Tip: &tx.Tip{Tipper: addr.String(), Amount: testdata.NewTestFeeAmount()}, + }, false}, + } + + for _, tc := range testcases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := tc.sd.ValidateBasic() + + if tc.expErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestAuxSignerData(t *testing.T) { + bodyBz := []byte{42} + _, pk, addr := testdata.KeyTestPubAddr() + pkAny, err := codectypes.NewAnyWithValue(pk) + require.NoError(t, err) + sig := []byte{42} + sd := &tx.SignDocDirectAux{BodyBytes: bodyBz, PublicKey: pkAny} + + testcases := []struct { + name string + sd tx.AuxSignerData + expErr bool + }{ + {"empty address", tx.AuxSignerData{}, true}, + {"empty sign mode", tx.AuxSignerData{Address: addr.String()}, true}, + {"SIGN_MODE_DIRECT", tx.AuxSignerData{Address: addr.String(), Mode: signing.SignMode(signing.SignMode_SIGN_MODE_DIRECT)}, true}, + {"no sig", tx.AuxSignerData{Address: addr.String(), Mode: signing.SignMode(signing.SignMode_SIGN_MODE_DIRECT_AUX)}, true}, + {"happy case WITH DIRECT_AUX", tx.AuxSignerData{Address: addr.String(), Mode: signing.SignMode_SIGN_MODE_DIRECT_AUX, SignDoc: sd, Sig: sig}, false}, + {"happy case WITH DIRECT_AUX", tx.AuxSignerData{Address: addr.String(), Mode: signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, SignDoc: sd, Sig: sig}, false}, + } + + for _, tc := range testcases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := tc.sd.ValidateBasic() + + if tc.expErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/types/tx/tx.pb.go b/types/tx/tx.pb.go index 6a7df6c004c3..f28f9fb6a227 100644 --- a/types/tx/tx.pb.go +++ b/types/tx/tx.pb.go @@ -907,6 +907,88 @@ func (m *Tip) GetTipper() string { return "" } +// AuxSignerData is the intermediary format that an auxiliary signer (e.g. a +// tipper) builds and sends to the fee payer (who will build and broadcast the +// actual tx). AuxSignerData is not a valid tx in itself, and will be rejected +// by the node if sent directly as-is. +// +// Since: cosmos-sdk 0.45 +type AuxSignerData struct { + // address is the bech32-encoded address of the auxiliary signer. If using + // AuxSignerData across different chains, the bech32 prefix of the target + // chain (where the final transaction is broadcasted) should be used. + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + // sign_doc is the SIGN_MOD_DIRECT_AUX sign doc that the auxiliary signer + // signs. Note: we use the same sign doc even if we're signing with + // LEGACY_AMINO_JSON. + SignDoc *SignDocDirectAux `protobuf:"bytes,2,opt,name=sign_doc,json=signDoc,proto3" json:"sign_doc,omitempty"` + // mode is the signing mode of the single signer + Mode signing.SignMode `protobuf:"varint,3,opt,name=mode,proto3,enum=cosmos.tx.signing.v1beta1.SignMode" json:"mode,omitempty"` + // sig is the signature of the sign doc. + Sig []byte `protobuf:"bytes,4,opt,name=sig,proto3" json:"sig,omitempty"` +} + +func (m *AuxSignerData) Reset() { *m = AuxSignerData{} } +func (m *AuxSignerData) String() string { return proto.CompactTextString(m) } +func (*AuxSignerData) ProtoMessage() {} +func (*AuxSignerData) Descriptor() ([]byte, []int) { + return fileDescriptor_96d1575ffde80842, []int{10} +} +func (m *AuxSignerData) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *AuxSignerData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_AuxSignerData.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *AuxSignerData) XXX_Merge(src proto.Message) { + xxx_messageInfo_AuxSignerData.Merge(m, src) +} +func (m *AuxSignerData) XXX_Size() int { + return m.Size() +} +func (m *AuxSignerData) XXX_DiscardUnknown() { + xxx_messageInfo_AuxSignerData.DiscardUnknown(m) +} + +var xxx_messageInfo_AuxSignerData proto.InternalMessageInfo + +func (m *AuxSignerData) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +func (m *AuxSignerData) GetSignDoc() *SignDocDirectAux { + if m != nil { + return m.SignDoc + } + return nil +} + +func (m *AuxSignerData) GetMode() signing.SignMode { + if m != nil { + return m.Mode + } + return signing.SignMode_SIGN_MODE_UNSPECIFIED +} + +func (m *AuxSignerData) GetSig() []byte { + if m != nil { + return m.Sig + } + return nil +} + func init() { proto.RegisterType((*Tx)(nil), "cosmos.tx.v1beta1.Tx") proto.RegisterType((*TxRaw)(nil), "cosmos.tx.v1beta1.TxRaw") @@ -920,72 +1002,77 @@ func init() { proto.RegisterType((*ModeInfo_Multi)(nil), "cosmos.tx.v1beta1.ModeInfo.Multi") proto.RegisterType((*Fee)(nil), "cosmos.tx.v1beta1.Fee") proto.RegisterType((*Tip)(nil), "cosmos.tx.v1beta1.Tip") + proto.RegisterType((*AuxSignerData)(nil), "cosmos.tx.v1beta1.AuxSignerData") } func init() { proto.RegisterFile("cosmos/tx/v1beta1/tx.proto", fileDescriptor_96d1575ffde80842) } var fileDescriptor_96d1575ffde80842 = []byte{ - // 957 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0x4f, 0x6f, 0x1b, 0x45, - 0x14, 0xf7, 0x66, 0x6d, 0xc7, 0x7e, 0x6d, 0xfa, 0x67, 0x14, 0x21, 0xc7, 0x51, 0xdd, 0x60, 0x54, - 0xf0, 0x25, 0xbb, 0x69, 0x7a, 0x68, 0x41, 0x48, 0x60, 0xb7, 0x54, 0xa9, 0x4a, 0x41, 0x9a, 0xe4, - 0xd4, 0xcb, 0x6a, 0x76, 0x3d, 0x59, 0x8f, 0xea, 0x9d, 0x59, 0x76, 0x66, 0xc1, 0xfb, 0x21, 0x90, - 0x2a, 0x24, 0xc4, 0x95, 0x33, 0x67, 0x3e, 0x44, 0x8f, 0x15, 0x27, 0x4e, 0x50, 0x25, 0x47, 0x24, - 0xbe, 0x02, 0x68, 0x66, 0x67, 0x37, 0x69, 0x09, 0x76, 0x25, 0x38, 0xed, 0xcc, 0x9b, 0xdf, 0xfb, - 0xcd, 0xef, 0xfd, 0xd9, 0x37, 0xd0, 0x8f, 0x84, 0x4c, 0x84, 0xf4, 0xd5, 0xc2, 0xff, 0xfa, 0x76, - 0x48, 0x15, 0xb9, 0xed, 0xab, 0x85, 0x97, 0x66, 0x42, 0x09, 0x74, 0xbd, 0x3c, 0xf3, 0xd4, 0xc2, - 0xb3, 0x67, 0xfd, 0xcd, 0x58, 0xc4, 0xc2, 0x9c, 0xfa, 0x7a, 0x55, 0x02, 0xfb, 0xbb, 0x96, 0x24, - 0xca, 0x8a, 0x54, 0x09, 0x3f, 0xc9, 0xe7, 0x8a, 0x49, 0x16, 0xd7, 0x8c, 0x95, 0xc1, 0xc2, 0x07, - 0x16, 0x1e, 0x12, 0x49, 0x6b, 0x4c, 0x24, 0x18, 0xb7, 0xe7, 0x1f, 0x9c, 0x69, 0x92, 0x2c, 0xe6, - 0x8c, 0x9f, 0x31, 0xd9, 0xbd, 0x05, 0x6e, 0xc5, 0x42, 0xc4, 0x73, 0xea, 0x9b, 0x5d, 0x98, 0x1f, - 0xfb, 0x84, 0x17, 0xd5, 0x51, 0xc9, 0x11, 0x94, 0x5a, 0x6d, 0x20, 0x66, 0x33, 0xfc, 0xd6, 0x81, - 0xb5, 0xa3, 0x05, 0xda, 0x85, 0x66, 0x28, 0xa6, 0x45, 0xcf, 0xd9, 0x71, 0x46, 0x97, 0xf6, 0xb7, - 0xbc, 0x7f, 0x04, 0xeb, 0x1d, 0x2d, 0x26, 0x62, 0x5a, 0x60, 0x03, 0x43, 0xf7, 0xa0, 0x4b, 0x72, - 0x35, 0x0b, 0x18, 0x3f, 0x16, 0xbd, 0x35, 0xe3, 0xb3, 0x7d, 0x81, 0xcf, 0x38, 0x57, 0xb3, 0x47, - 0xfc, 0x58, 0xe0, 0x0e, 0xb1, 0x2b, 0x34, 0x00, 0xd0, 0xb2, 0x89, 0xca, 0x33, 0x2a, 0x7b, 0xee, - 0x8e, 0x3b, 0xba, 0x8c, 0xcf, 0x59, 0x86, 0x1c, 0x5a, 0x47, 0x0b, 0x4c, 0xbe, 0x41, 0x37, 0x00, - 0xf4, 0x55, 0x41, 0x58, 0x28, 0x2a, 0x8d, 0xae, 0xcb, 0xb8, 0xab, 0x2d, 0x13, 0x6d, 0x40, 0xef, - 0xc3, 0xd5, 0x5a, 0x81, 0xc5, 0xac, 0x19, 0xcc, 0x46, 0x75, 0x55, 0x89, 0x5b, 0x75, 0xdf, 0x77, - 0x0e, 0xac, 0x1f, 0xb2, 0x98, 0x3f, 0x10, 0xd1, 0xff, 0x75, 0xe5, 0x16, 0x74, 0xa2, 0x19, 0x61, - 0x3c, 0x60, 0xd3, 0x9e, 0xbb, 0xe3, 0x8c, 0xba, 0x78, 0xdd, 0xec, 0x1f, 0x4d, 0xd1, 0x2d, 0xb8, - 0x42, 0xa2, 0x48, 0xe4, 0x5c, 0x05, 0x3c, 0x4f, 0x42, 0x9a, 0xf5, 0x9a, 0x3b, 0xce, 0xa8, 0x89, - 0x37, 0xac, 0xf5, 0x0b, 0x63, 0x1c, 0xfe, 0xe9, 0xc0, 0x35, 0x2b, 0xea, 0x01, 0xcb, 0x68, 0xa4, - 0xc6, 0xf9, 0x62, 0x95, 0xba, 0x3b, 0x00, 0x69, 0x1e, 0xce, 0x59, 0x14, 0x3c, 0xa3, 0x85, 0xad, - 0xc9, 0xa6, 0x57, 0xf6, 0x84, 0x57, 0xf5, 0x84, 0x37, 0xe6, 0x05, 0xee, 0x96, 0xb8, 0xc7, 0xb4, - 0xf8, 0xef, 0x52, 0x51, 0x1f, 0x3a, 0x92, 0x7e, 0x95, 0x53, 0x1e, 0xd1, 0x5e, 0xcb, 0x00, 0xea, - 0x3d, 0x1a, 0x81, 0xab, 0x58, 0xda, 0x6b, 0x1b, 0x2d, 0xef, 0x5c, 0xd4, 0x53, 0x2c, 0xc5, 0x1a, - 0x32, 0xfc, 0x7e, 0x0d, 0xda, 0x65, 0x83, 0xa1, 0x3d, 0xe8, 0x24, 0x54, 0x4a, 0x12, 0x9b, 0x20, - 0xdd, 0x7f, 0x8d, 0xa2, 0x46, 0x21, 0x04, 0xcd, 0x84, 0x26, 0x65, 0x1f, 0x76, 0xb1, 0x59, 0x6b, - 0xf5, 0x8a, 0x25, 0x54, 0xe4, 0x2a, 0x98, 0x51, 0x16, 0xcf, 0x94, 0x09, 0xaf, 0x89, 0x37, 0xac, - 0xf5, 0xc0, 0x18, 0xd1, 0x04, 0xae, 0xd3, 0x85, 0xa2, 0x5c, 0x32, 0xc1, 0x03, 0x91, 0x2a, 0x26, - 0xb8, 0xec, 0xfd, 0xb5, 0xbe, 0xe4, 0xda, 0x6b, 0x35, 0xfe, 0xcb, 0x12, 0x8e, 0x9e, 0xc2, 0x80, - 0x0b, 0x1e, 0x44, 0x19, 0x53, 0x2c, 0x22, 0xf3, 0xe0, 0x02, 0xc2, 0xab, 0x4b, 0x08, 0xb7, 0xb9, - 0xe0, 0xf7, 0xad, 0xef, 0x67, 0x6f, 0x70, 0x0f, 0x7f, 0x74, 0xa0, 0x53, 0xfd, 0x44, 0xe8, 0x53, - 0xb8, 0xac, 0x1b, 0x97, 0x66, 0xa6, 0x03, 0xab, 0xec, 0xdc, 0xb8, 0x20, 0xaf, 0x87, 0x06, 0x66, - 0xfe, 0xbc, 0x4b, 0xb2, 0x5e, 0x4b, 0x5d, 0x90, 0x63, 0x4a, 0x6d, 0x73, 0x5c, 0x54, 0x90, 0x87, - 0x94, 0x62, 0x0d, 0xa9, 0x4a, 0xe7, 0xae, 0x2e, 0xdd, 0x0f, 0x0e, 0xc0, 0xd9, 0x7d, 0x6f, 0xb4, - 0xa1, 0xf3, 0x76, 0x6d, 0x78, 0x0f, 0xba, 0x89, 0x98, 0xd2, 0x55, 0xe3, 0xe4, 0x89, 0x98, 0xd2, - 0x72, 0x9c, 0x24, 0x76, 0xf5, 0x5a, 0xfb, 0xb9, 0xaf, 0xb7, 0xdf, 0xf0, 0xd5, 0x1a, 0x74, 0x2a, - 0x17, 0xf4, 0x31, 0xb4, 0x25, 0xe3, 0xf1, 0x9c, 0x5a, 0x4d, 0xc3, 0x25, 0xfc, 0xde, 0xa1, 0x41, - 0x1e, 0x34, 0xb0, 0xf5, 0x41, 0x1f, 0x42, 0xcb, 0x8c, 0x6d, 0x2b, 0xee, 0xdd, 0x65, 0xce, 0x4f, - 0x34, 0xf0, 0xa0, 0x81, 0x4b, 0x8f, 0xfe, 0x18, 0xda, 0x25, 0x1d, 0xba, 0x0b, 0x4d, 0xad, 0xdb, - 0x08, 0xb8, 0xb2, 0xff, 0xde, 0x39, 0x8e, 0x6a, 0x90, 0x9f, 0xaf, 0x9f, 0xe6, 0xc3, 0xc6, 0xa1, - 0xff, 0xdc, 0x81, 0x96, 0x61, 0x45, 0x8f, 0xa1, 0x13, 0x32, 0x45, 0xb2, 0x8c, 0x54, 0xb9, 0xf5, - 0x2b, 0x9a, 0xf2, 0xb9, 0xf1, 0xea, 0xd7, 0xa5, 0xe2, 0xba, 0x2f, 0x92, 0x94, 0x44, 0x6a, 0xc2, - 0xd4, 0x58, 0xbb, 0xe1, 0x9a, 0x00, 0x7d, 0x04, 0x50, 0x67, 0x5d, 0x8f, 0x32, 0x77, 0x55, 0xda, - 0xbb, 0x55, 0xda, 0xe5, 0xa4, 0x05, 0xae, 0xcc, 0x93, 0xe1, 0x1f, 0x0e, 0xb8, 0x0f, 0x29, 0x45, - 0x11, 0xb4, 0x49, 0xa2, 0xa7, 0x82, 0x6d, 0xca, 0xfa, 0x01, 0xd1, 0xaf, 0xda, 0x39, 0x29, 0x8c, - 0x4f, 0xf6, 0x5e, 0xfc, 0x76, 0xb3, 0xf1, 0xd3, 0xef, 0x37, 0x47, 0x31, 0x53, 0xb3, 0x3c, 0xf4, - 0x22, 0x91, 0xf8, 0xd5, 0x8b, 0x69, 0x3e, 0xbb, 0x72, 0xfa, 0xcc, 0x57, 0x45, 0x4a, 0xa5, 0x71, - 0x90, 0xd8, 0x52, 0xa3, 0x6d, 0xe8, 0xc6, 0x44, 0x06, 0x73, 0x96, 0x30, 0x65, 0x0a, 0xd1, 0xc4, - 0x9d, 0x98, 0xc8, 0xcf, 0xf5, 0x1e, 0x79, 0xd0, 0x4a, 0x49, 0x41, 0xb3, 0x72, 0x8c, 0x4d, 0x7a, - 0xbf, 0xfc, 0xbc, 0xbb, 0x69, 0x35, 0x8c, 0xa7, 0xd3, 0x8c, 0x4a, 0x79, 0xa8, 0x32, 0xc6, 0x63, - 0x5c, 0xc2, 0xd0, 0x3e, 0xac, 0xc7, 0x19, 0xe1, 0xca, 0xce, 0xb5, 0x65, 0x1e, 0x15, 0x70, 0x98, - 0x82, 0x7b, 0xc4, 0x52, 0x74, 0xf7, 0xed, 0x83, 0x6d, 0xea, 0x60, 0xeb, 0x00, 0xf6, 0xa0, 0xad, - 0x58, 0x9a, 0xd2, 0xac, 0x1c, 0x55, 0x4b, 0xae, 0xb4, 0xb8, 0xc9, 0x27, 0x2f, 0x4e, 0x06, 0xce, - 0xcb, 0x93, 0x81, 0xf3, 0xea, 0x64, 0xe0, 0x3c, 0x3f, 0x1d, 0x34, 0x5e, 0x9e, 0x0e, 0x1a, 0xbf, - 0x9e, 0x0e, 0x1a, 0x4f, 0x6f, 0xad, 0x4e, 0x9f, 0xaf, 0x16, 0x61, 0xdb, 0xfc, 0x72, 0x77, 0xfe, - 0x0e, 0x00, 0x00, 0xff, 0xff, 0x82, 0x38, 0x16, 0x01, 0xda, 0x08, 0x00, 0x00, + // 1018 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0x41, 0x6f, 0x1b, 0x45, + 0x14, 0xf6, 0x66, 0x6d, 0xc7, 0x7e, 0x4d, 0xda, 0x74, 0x14, 0x21, 0xc7, 0x51, 0xdd, 0xe0, 0xaa, + 0xe0, 0x4b, 0x76, 0xd3, 0xf4, 0xd0, 0x82, 0x10, 0x60, 0x37, 0x54, 0xa9, 0x4a, 0x41, 0x9a, 0xe4, + 0xd4, 0xcb, 0x6a, 0xbc, 0x9e, 0xac, 0x47, 0xf5, 0xce, 0x2c, 0x3b, 0xb3, 0x60, 0xff, 0x08, 0xa4, + 0x0a, 0x09, 0x71, 0xe5, 0xcc, 0x99, 0x1f, 0xd1, 0x13, 0xaa, 0x38, 0x71, 0x82, 0x2a, 0x39, 0x22, + 0xf1, 0x17, 0x40, 0x33, 0x3b, 0xbb, 0x49, 0x8b, 0x6b, 0x17, 0xd1, 0xd3, 0xce, 0xbc, 0xf9, 0xde, + 0x37, 0xdf, 0x9b, 0xf9, 0xf6, 0x0d, 0xb4, 0x43, 0x21, 0x63, 0x21, 0x7d, 0x35, 0xf5, 0xbf, 0xbe, + 0x35, 0xa4, 0x8a, 0xdc, 0xf2, 0xd5, 0xd4, 0x4b, 0x52, 0xa1, 0x04, 0xba, 0x9a, 0xaf, 0x79, 0x6a, + 0xea, 0xd9, 0xb5, 0xf6, 0x66, 0x24, 0x22, 0x61, 0x56, 0x7d, 0x3d, 0xca, 0x81, 0xed, 0x5d, 0x4b, + 0x12, 0xa6, 0xb3, 0x44, 0x09, 0x3f, 0xce, 0x26, 0x8a, 0x49, 0x16, 0x95, 0x8c, 0x45, 0xc0, 0xc2, + 0x3b, 0x16, 0x3e, 0x24, 0x92, 0x96, 0x98, 0x50, 0x30, 0x6e, 0xd7, 0xdf, 0x3f, 0xd7, 0x24, 0x59, + 0xc4, 0x19, 0x3f, 0x67, 0xb2, 0x73, 0x0b, 0xdc, 0x8a, 0x84, 0x88, 0x26, 0xd4, 0x37, 0xb3, 0x61, + 0x76, 0xe2, 0x13, 0x3e, 0x2b, 0x96, 0x72, 0x8e, 0x20, 0xd7, 0x6a, 0x0b, 0x31, 0x93, 0xee, 0xb7, + 0x0e, 0xac, 0x1c, 0x4f, 0xd1, 0x2e, 0x54, 0x87, 0x62, 0x34, 0x6b, 0x39, 0x3b, 0x4e, 0xef, 0xd2, + 0xfe, 0x96, 0xf7, 0xaf, 0x62, 0xbd, 0xe3, 0xe9, 0x40, 0x8c, 0x66, 0xd8, 0xc0, 0xd0, 0x5d, 0x68, + 0x92, 0x4c, 0x8d, 0x03, 0xc6, 0x4f, 0x44, 0x6b, 0xc5, 0xe4, 0x6c, 0xcf, 0xc9, 0xe9, 0x67, 0x6a, + 0xfc, 0x80, 0x9f, 0x08, 0xdc, 0x20, 0x76, 0x84, 0x3a, 0x00, 0x5a, 0x36, 0x51, 0x59, 0x4a, 0x65, + 0xcb, 0xdd, 0x71, 0x7b, 0x6b, 0xf8, 0x42, 0xa4, 0xcb, 0xa1, 0x76, 0x3c, 0xc5, 0xe4, 0x1b, 0x74, + 0x0d, 0x40, 0x6f, 0x15, 0x0c, 0x67, 0x8a, 0x4a, 0xa3, 0x6b, 0x0d, 0x37, 0x75, 0x64, 0xa0, 0x03, + 0xe8, 0x3d, 0xb8, 0x52, 0x2a, 0xb0, 0x98, 0x15, 0x83, 0x59, 0x2f, 0xb6, 0xca, 0x71, 0xcb, 0xf6, + 0xfb, 0xce, 0x81, 0xd5, 0x23, 0x16, 0xf1, 0x03, 0x11, 0xbe, 0xad, 0x2d, 0xb7, 0xa0, 0x11, 0x8e, + 0x09, 0xe3, 0x01, 0x1b, 0xb5, 0xdc, 0x1d, 0xa7, 0xd7, 0xc4, 0xab, 0x66, 0xfe, 0x60, 0x84, 0x6e, + 0xc2, 0x65, 0x12, 0x86, 0x22, 0xe3, 0x2a, 0xe0, 0x59, 0x3c, 0xa4, 0x69, 0xab, 0xba, 0xe3, 0xf4, + 0xaa, 0x78, 0xdd, 0x46, 0xbf, 0x30, 0xc1, 0xee, 0x5f, 0x0e, 0x6c, 0x58, 0x51, 0x07, 0x2c, 0xa5, + 0xa1, 0xea, 0x67, 0xd3, 0x65, 0xea, 0x6e, 0x03, 0x24, 0xd9, 0x70, 0xc2, 0xc2, 0xe0, 0x09, 0x9d, + 0xd9, 0x3b, 0xd9, 0xf4, 0x72, 0x4f, 0x78, 0x85, 0x27, 0xbc, 0x3e, 0x9f, 0xe1, 0x66, 0x8e, 0x7b, + 0x48, 0x67, 0xff, 0x5f, 0x2a, 0x6a, 0x43, 0x43, 0xd2, 0xaf, 0x32, 0xca, 0x43, 0xda, 0xaa, 0x19, + 0x40, 0x39, 0x47, 0x3d, 0x70, 0x15, 0x4b, 0x5a, 0x75, 0xa3, 0xe5, 0x9d, 0x79, 0x9e, 0x62, 0x09, + 0xd6, 0x90, 0xee, 0xf7, 0x2b, 0x50, 0xcf, 0x0d, 0x86, 0xf6, 0xa0, 0x11, 0x53, 0x29, 0x49, 0x64, + 0x8a, 0x74, 0x5f, 0x5b, 0x45, 0x89, 0x42, 0x08, 0xaa, 0x31, 0x8d, 0x73, 0x1f, 0x36, 0xb1, 0x19, + 0x6b, 0xf5, 0x8a, 0xc5, 0x54, 0x64, 0x2a, 0x18, 0x53, 0x16, 0x8d, 0x95, 0x29, 0xaf, 0x8a, 0xd7, + 0x6d, 0xf4, 0xd0, 0x04, 0xd1, 0x00, 0xae, 0xd2, 0xa9, 0xa2, 0x5c, 0x32, 0xc1, 0x03, 0x91, 0x28, + 0x26, 0xb8, 0x6c, 0xfd, 0xbd, 0xba, 0x60, 0xdb, 0x8d, 0x12, 0xff, 0x65, 0x0e, 0x47, 0x8f, 0xa1, + 0xc3, 0x05, 0x0f, 0xc2, 0x94, 0x29, 0x16, 0x92, 0x49, 0x30, 0x87, 0xf0, 0xca, 0x02, 0xc2, 0x6d, + 0x2e, 0xf8, 0x3d, 0x9b, 0xfb, 0xd9, 0x2b, 0xdc, 0xdd, 0x1f, 0x1d, 0x68, 0x14, 0x3f, 0x11, 0xfa, + 0x14, 0xd6, 0xb4, 0x71, 0x69, 0x6a, 0x1c, 0x58, 0x9c, 0xce, 0xb5, 0x39, 0xe7, 0x7a, 0x64, 0x60, + 0xe6, 0xcf, 0xbb, 0x24, 0xcb, 0xb1, 0xd4, 0x17, 0x72, 0x42, 0xa9, 0x35, 0xc7, 0xbc, 0x0b, 0xb9, + 0x4f, 0x29, 0xd6, 0x90, 0xe2, 0xea, 0xdc, 0xe5, 0x57, 0xf7, 0x83, 0x03, 0x70, 0xbe, 0xdf, 0x2b, + 0x36, 0x74, 0xde, 0xcc, 0x86, 0x77, 0xa1, 0x19, 0x8b, 0x11, 0x5d, 0xd6, 0x4e, 0x1e, 0x89, 0x11, + 0xcd, 0xdb, 0x49, 0x6c, 0x47, 0x2f, 0xd9, 0xcf, 0x7d, 0xd9, 0x7e, 0xdd, 0x17, 0x2b, 0xd0, 0x28, + 0x52, 0xd0, 0x47, 0x50, 0x97, 0x8c, 0x47, 0x13, 0x6a, 0x35, 0x75, 0x17, 0xf0, 0x7b, 0x47, 0x06, + 0x79, 0x58, 0xc1, 0x36, 0x07, 0x7d, 0x00, 0x35, 0xd3, 0xb6, 0xad, 0xb8, 0x77, 0x17, 0x25, 0x3f, + 0xd2, 0xc0, 0xc3, 0x0a, 0xce, 0x33, 0xda, 0x7d, 0xa8, 0xe7, 0x74, 0xe8, 0x0e, 0x54, 0xb5, 0x6e, + 0x23, 0xe0, 0xf2, 0xfe, 0x8d, 0x0b, 0x1c, 0x45, 0x23, 0xbf, 0x78, 0x7f, 0x9a, 0x0f, 0x9b, 0x84, + 0xf6, 0x53, 0x07, 0x6a, 0x86, 0x15, 0x3d, 0x84, 0xc6, 0x90, 0x29, 0x92, 0xa6, 0xa4, 0x38, 0x5b, + 0xbf, 0xa0, 0xc9, 0x9f, 0x1b, 0xaf, 0x7c, 0x5d, 0x0a, 0xae, 0x7b, 0x22, 0x4e, 0x48, 0xa8, 0x06, + 0x4c, 0xf5, 0x75, 0x1a, 0x2e, 0x09, 0xd0, 0x87, 0x00, 0xe5, 0xa9, 0xeb, 0x56, 0xe6, 0x2e, 0x3b, + 0xf6, 0x66, 0x71, 0xec, 0x72, 0x50, 0x03, 0x57, 0x66, 0x71, 0xf7, 0x4f, 0x07, 0xdc, 0xfb, 0x94, + 0xa2, 0x10, 0xea, 0x24, 0xd6, 0x5d, 0xc1, 0x9a, 0xb2, 0x7c, 0x40, 0xf4, 0xab, 0x76, 0x41, 0x0a, + 0xe3, 0x83, 0xbd, 0x67, 0xbf, 0x5f, 0xaf, 0xfc, 0xf4, 0xc7, 0xf5, 0x5e, 0xc4, 0xd4, 0x38, 0x1b, + 0x7a, 0xa1, 0x88, 0xfd, 0xe2, 0xc5, 0x34, 0x9f, 0x5d, 0x39, 0x7a, 0xe2, 0xab, 0x59, 0x42, 0xa5, + 0x49, 0x90, 0xd8, 0x52, 0xa3, 0x6d, 0x68, 0x46, 0x44, 0x06, 0x13, 0x16, 0x33, 0x65, 0x2e, 0xa2, + 0x8a, 0x1b, 0x11, 0x91, 0x9f, 0xeb, 0x39, 0xf2, 0xa0, 0x96, 0x90, 0x19, 0x4d, 0xf3, 0x36, 0x36, + 0x68, 0xfd, 0xfa, 0xf3, 0xee, 0xa6, 0xd5, 0xd0, 0x1f, 0x8d, 0x52, 0x2a, 0xe5, 0x91, 0x4a, 0x19, + 0x8f, 0x70, 0x0e, 0x43, 0xfb, 0xb0, 0x1a, 0xa5, 0x84, 0x2b, 0xdb, 0xd7, 0x16, 0x65, 0x14, 0xc0, + 0x6e, 0x02, 0xee, 0x31, 0x4b, 0xd0, 0x9d, 0x37, 0x2f, 0xb6, 0xaa, 0x8b, 0x2d, 0x0b, 0xd8, 0x83, + 0xba, 0x62, 0x49, 0x42, 0xd3, 0xbc, 0x55, 0x2d, 0xd8, 0xd2, 0xe2, 0xba, 0xbf, 0x38, 0xb0, 0xde, + 0xcf, 0xa6, 0xf9, 0xff, 0x75, 0x40, 0x14, 0xd1, 0xba, 0x49, 0x0e, 0x35, 0x06, 0x58, 0xa8, 0xdb, + 0x02, 0xd1, 0xc7, 0xd0, 0xd0, 0x0e, 0x0b, 0x46, 0x22, 0xb4, 0x06, 0xbe, 0xf1, 0x9a, 0xa6, 0x71, + 0xf1, 0xc1, 0xc1, 0xab, 0xd2, 0xbe, 0x8b, 0x85, 0x71, 0xdd, 0xff, 0x68, 0x5c, 0xb4, 0x01, 0xae, + 0x64, 0x91, 0x39, 0xe0, 0x35, 0xac, 0x87, 0x83, 0x4f, 0x9e, 0x9d, 0x76, 0x9c, 0xe7, 0xa7, 0x1d, + 0xe7, 0xc5, 0x69, 0xc7, 0x79, 0x7a, 0xd6, 0xa9, 0x3c, 0x3f, 0xeb, 0x54, 0x7e, 0x3b, 0xeb, 0x54, + 0x1e, 0xdf, 0x5c, 0xee, 0x07, 0x5f, 0x4d, 0x87, 0x75, 0xd3, 0x43, 0x6e, 0xff, 0x13, 0x00, 0x00, + 0xff, 0xff, 0x83, 0x68, 0x7c, 0x6c, 0xab, 0x09, 0x00, 0x00, } func (m *Tx) Marshal() (dAtA []byte, err error) { @@ -1655,6 +1742,60 @@ func (m *Tip) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *AuxSignerData) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AuxSignerData) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AuxSignerData) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Sig) > 0 { + i -= len(m.Sig) + copy(dAtA[i:], m.Sig) + i = encodeVarintTx(dAtA, i, uint64(len(m.Sig))) + i-- + dAtA[i] = 0x22 + } + if m.Mode != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.Mode)) + i-- + dAtA[i] = 0x18 + } + if m.SignDoc != nil { + { + size, err := m.SignDoc.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Address) > 0 { + i -= len(m.Address) + copy(dAtA[i:], m.Address) + i = encodeVarintTx(dAtA, i, uint64(len(m.Address))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintTx(dAtA []byte, offset int, v uint64) int { offset -= sovTx(v) base := offset @@ -1956,6 +2097,30 @@ func (m *Tip) Size() (n int) { return n } +func (m *AuxSignerData) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Address) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.SignDoc != nil { + l = m.SignDoc.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.Mode != 0 { + n += 1 + sovTx(uint64(m.Mode)) + } + l = len(m.Sig) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -3753,6 +3918,177 @@ func (m *Tip) Unmarshal(dAtA []byte) error { } return nil } +func (m *AuxSignerData) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AuxSignerData: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AuxSignerData: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Address = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SignDoc", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.SignDoc == nil { + m.SignDoc = &SignDocDirectAux{} + } + if err := m.SignDoc.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Mode", wireType) + } + m.Mode = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Mode |= signing.SignMode(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Sig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Sig = append(m.Sig[:0], dAtA[iNdEx:postIndex]...) + if m.Sig == nil { + m.Sig = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/auth/migrations/legacytx/stdtx_builder.go b/x/auth/migrations/legacytx/stdtx_builder.go index af848d13eb8c..0c0454034947 100644 --- a/x/auth/migrations/legacytx/stdtx_builder.go +++ b/x/auth/migrations/legacytx/stdtx_builder.go @@ -74,6 +74,14 @@ func (s *StdTxBuilder) SetTimeoutHeight(height uint64) { // SetFeeGranter does nothing for stdtx func (s *StdTxBuilder) SetFeeGranter(_ sdk.AccAddress) {} +// SetFeePayer does nothing for stdtx +func (s *StdTxBuilder) SetFeePayer(_ sdk.AccAddress) {} + +// AddAuxSignerData returns an error for StdTxBuilder. +func (s *StdTxBuilder) AddAuxSignerData(_ tx.AuxSignerData) error { + return sdkerrors.ErrLogic.Wrap("cannot use AuxSignerData with StdTxBuilder") +} + // StdTxConfig is a context.TxConfig for StdTx type StdTxConfig struct { Cdc *codec.LegacyAmino diff --git a/x/auth/tx/aux_test.go b/x/auth/tx/aux_test.go new file mode 100644 index 000000000000..72774712b3ce --- /dev/null +++ b/x/auth/tx/aux_test.go @@ -0,0 +1,147 @@ +package tx_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + clienttx "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/simapp" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + txtypes "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" +) + +// TestBuilderWithAux creates a tx with 2 aux signers: +// - 1st one is tipper, +// - 2nd one is just an aux signer. +// Then it tests integrating the 2 AuxSignerData into a +// client.TxBuilder created by the fee payer. +func TestBuilderWithAux(t *testing.T) { + encCfg := simapp.MakeTestEncodingConfig() + testdata.RegisterInterfaces(encCfg.InterfaceRegistry) + + // The final TX has 3 signers, in this order. + tipperPriv, tipperPk, tipperAddr := testdata.KeyTestPubAddr() + aux2Priv, aux2Pk, aux2Addr := testdata.KeyTestPubAddr() + feepayerPriv, feepayerPk, feepayerAddr := testdata.KeyTestPubAddr() + + msg := testdata.NewTestMsg(tipperAddr, aux2Addr) + memo := "test-memo" + tip := &txtypes.Tip{Tipper: tipperAddr.String(), Amount: sdk.NewCoins(sdk.NewCoin("tip-denom", sdk.NewIntFromUint64(123)))} + chainID := "test-chain" + gas := testdata.NewTestGasLimit() + fee := testdata.NewTestFeeAmount() + + // Create an AuxTxBuilder for tipper (1st signer) + tipperBuilder := clienttx.NewAuxTxBuilder() + tipperBuilder.SetAddress(tipperAddr.String()) + tipperBuilder.SetAccountNumber(1) + tipperBuilder.SetSequence(2) + tipperBuilder.SetTimeoutHeight(3) + tipperBuilder.SetMemo(memo) + tipperBuilder.SetChainID(chainID) + tipperBuilder.SetMsgs(msg) + tipperBuilder.SetPubKey(tipperPk) + tipperBuilder.SetTip(tip) + err := tipperBuilder.SetSignMode(signing.SignMode_SIGN_MODE_DIRECT_AUX) + require.NoError(t, err) + signBz, err := tipperBuilder.GetSignBytes() + require.NoError(t, err) + tipperSig, err := tipperPriv.Sign(signBz) + require.NoError(t, err) + tipperBuilder.SetSignature(tipperSig) + tipperSignerData, err := tipperBuilder.GetAuxSignerData() + require.NoError(t, err) + + // Create an AuxTxBuilder for aux2 (2nd signer) + aux2Builder := clienttx.NewAuxTxBuilder() + aux2Builder.SetAddress(aux2Addr.String()) + aux2Builder.SetAccountNumber(11) + aux2Builder.SetSequence(12) + aux2Builder.SetTimeoutHeight(3) + aux2Builder.SetMemo(memo) + aux2Builder.SetChainID(chainID) + aux2Builder.SetMsgs(msg) + aux2Builder.SetPubKey(aux2Pk) + aux2Builder.SetTip(tip) + err = aux2Builder.SetSignMode(signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON) + require.NoError(t, err) + signBz, err = aux2Builder.GetSignBytes() + require.NoError(t, err) + aux2Sig, err := aux2Priv.Sign(signBz) + require.NoError(t, err) + aux2Builder.SetSignature(aux2Sig) + aux2SignerData, err := aux2Builder.GetAuxSignerData() + require.NoError(t, err) + + // Fee payer (3rd and last signer) creates a TxBuilder. + w := encCfg.TxConfig.NewTxBuilder() + // Note: we're testing calling AddAuxSignerData in the wrong order. + err = w.AddAuxSignerData(aux2SignerData) + require.NoError(t, err) + err = w.AddAuxSignerData(tipperSignerData) + require.NoError(t, err) + w.SetFeePayer(feepayerAddr) + w.SetFeeAmount(fee) + w.SetGasLimit(gas) + sigs, err := w.(authsigning.SigVerifiableTx).GetSignaturesV2() + require.NoError(t, err) + tipperSigV2 := sigs[0] + aux2SigV2 := sigs[1] + // Set all signer infos. + w.SetSignatures(tipperSigV2, aux2SigV2, signing.SignatureV2{ + PubKey: feepayerPk, + Sequence: 15, + }) + signBz, err = encCfg.TxConfig.SignModeHandler().GetSignBytes( + signing.SignMode_SIGN_MODE_DIRECT, + authsigning.SignerData{Address: feepayerAddr.String(), ChainID: chainID, AccountNumber: 11, Sequence: 15, SignerIndex: 1}, + w.GetTx(), + ) + require.NoError(t, err) + feepayerSig, err := feepayerPriv.Sign(signBz) + require.NoError(t, err) + // Set all signatures. + w.SetSignatures(tipperSigV2, aux2SigV2, signing.SignatureV2{ + PubKey: feepayerPk, + Data: &signing.SingleSignatureData{ + SignMode: signing.SignMode_SIGN_MODE_DIRECT, + Signature: feepayerSig, + }, + Sequence: 22, + }) + + // Make sure tx is correct. + txBz, err := encCfg.TxConfig.TxEncoder()(w.GetTx()) + require.NoError(t, err) + tx, err := encCfg.TxConfig.TxDecoder()(txBz) + require.NoError(t, err) + require.Equal(t, tx.(sdk.FeeTx).FeePayer(), feepayerAddr) + require.Equal(t, tx.(sdk.FeeTx).GetFee(), fee) + require.Equal(t, tx.(sdk.FeeTx).GetGas(), gas) + require.Equal(t, tip, tx.(txtypes.TipTx).GetTip()) + require.Equal(t, msg, tx.GetMsgs()[0]) + require.Equal(t, memo, tx.(sdk.TxWithMemo).GetMemo()) + require.Equal(t, uint64(3), tx.(sdk.TxWithTimeoutHeight).GetTimeoutHeight()) + sigs, err = tx.(authsigning.Tx).GetSignaturesV2() + require.NoError(t, err) + require.Len(t, sigs, 3) + require.Equal(t, signing.SignatureV2{ + PubKey: tipperPk, + Data: &signing.SingleSignatureData{SignMode: signing.SignMode_SIGN_MODE_DIRECT_AUX, Signature: tipperSig}, + Sequence: 2, + }, sigs[0]) + require.Equal(t, signing.SignatureV2{ + PubKey: aux2Pk, + Data: &signing.SingleSignatureData{SignMode: signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, Signature: aux2Sig}, + Sequence: 12, + }, sigs[1]) + require.Equal(t, signing.SignatureV2{ + PubKey: feepayerPk, + Data: &signing.SingleSignatureData{SignMode: signing.SignMode_SIGN_MODE_DIRECT, Signature: feepayerSig}, + Sequence: 22, + }, sigs[2]) +} diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index d3b83d5018bb..91e3546c74d7 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -4,6 +4,7 @@ import ( "github.com/gogo/protobuf/proto" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -17,6 +18,8 @@ import ( // wrapper is a wrapper around the tx.Tx proto.Message which retain the raw // body and auth_info bytes. type wrapper struct { + cdc codec.Codec + tx *tx.Tx // bodyBz represents the protobuf encoding of TxBody. This should be encoding @@ -46,8 +49,9 @@ type ExtensionOptionsTxBuilder interface { SetNonCriticalExtensionOptions(...*codectypes.Any) } -func newBuilder() *wrapper { +func newBuilder(cdc codec.Codec) *wrapper { return &wrapper{ + cdc: cdc, tx: &tx.Tx{ Body: &tx.TxBody{}, AuthInfo: &tx.AuthInfo{ @@ -315,10 +319,28 @@ func (w *wrapper) setSignerInfos(infos []*tx.SignerInfo) { w.authInfoBz = nil } +func (w *wrapper) setSignerInfoAtIndex(index int, info *tx.SignerInfo) { + if w.tx.AuthInfo.SignerInfos == nil { + w.tx.AuthInfo.SignerInfos = make([]*tx.SignerInfo, len(w.GetSigners())) + } + + w.tx.AuthInfo.SignerInfos[index] = info + // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo + w.authInfoBz = nil +} + func (w *wrapper) setSignatures(sigs [][]byte) { w.tx.Signatures = sigs } +func (w *wrapper) setSignatureAtIndex(index int, sig []byte) { + if w.tx.Signatures == nil { + w.tx.Signatures = make([][]byte, len(w.GetSigners())) + } + + w.tx.Signatures[index] = sig +} + func (w *wrapper) GetTx() authsigning.Tx { return w } @@ -357,3 +379,49 @@ func (w *wrapper) SetNonCriticalExtensionOptions(extOpts ...*codectypes.Any) { w.tx.Body.NonCriticalExtensionOptions = extOpts w.bodyBz = nil } + +func (w *wrapper) AddAuxSignerData(data tx.AuxSignerData) error { + err := data.ValidateBasic() + if err != nil { + return err + } + + w.bodyBz = data.SignDoc.BodyBytes + + var body tx.TxBody + err = w.cdc.Unmarshal(w.bodyBz, &body) + if err != nil { + return err + } + + w.SetMemo(body.Memo) + w.SetTimeoutHeight(body.TimeoutHeight) + w.SetExtensionOptions(body.ExtensionOptions...) + w.SetNonCriticalExtensionOptions(body.NonCriticalExtensionOptions...) + msgs := make([]sdk.Msg, len(body.Messages)) + for i, msgAny := range body.Messages { + msgs[i] = msgAny.GetCachedValue().(sdk.Msg) + } + w.SetMsgs(msgs...) + w.SetTip(data.GetSignDoc().GetTip()) + + // Get the aux signer's index in GetSigners. + signerIndex := -1 + for i, signer := range w.GetSigners() { + if signer.String() == data.Address { + signerIndex = i + } + } + if signerIndex < 0 { + return sdkerrors.ErrLogic.Wrapf("address %s is not a signer", data.Address) + } + + w.setSignerInfoAtIndex(signerIndex, &tx.SignerInfo{ + PublicKey: data.SignDoc.PublicKey, + ModeInfo: &tx.ModeInfo{Sum: &tx.ModeInfo_Single_{Single: &tx.ModeInfo_Single{Mode: data.Mode}}}, + Sequence: data.SignDoc.Sequence, + }) + w.setSignatureAtIndex(signerIndex, data.Sig) + + return nil +} diff --git a/x/auth/tx/builder_test.go b/x/auth/tx/builder_test.go index e45a67116378..f7122dd39218 100644 --- a/x/auth/tx/builder_test.go +++ b/x/auth/tx/builder_test.go @@ -19,7 +19,7 @@ func TestTxBuilder(t *testing.T) { _, pubkey, addr := testdata.KeyTestPubAddr() marshaler := codec.NewProtoCodec(codectypes.NewInterfaceRegistry()) - txBuilder := newBuilder() + txBuilder := newBuilder(nil) memo := "sometestmemo" msgs := []sdk.Msg{testdata.NewTestMsg(addr)} @@ -136,7 +136,7 @@ func TestBuilderValidateBasic(t *testing.T) { // require to fail validation upon invalid fee badFeeAmount := testdata.NewTestFeeAmount() badFeeAmount[0].Amount = sdk.NewInt(-5) - txBuilder := newBuilder() + txBuilder := newBuilder(nil) var sig1, sig2 signing.SignatureV2 sig1 = signing.SignatureV2{ @@ -277,7 +277,7 @@ func TestBuilderFeePayer(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { // setup basic tx - txBuilder := newBuilder() + txBuilder := newBuilder(nil) err := txBuilder.SetMsgs(msgs...) require.NoError(t, err) txBuilder.SetGasLimit(200000) @@ -301,7 +301,7 @@ func TestBuilderFeeGranter(t *testing.T) { feeAmount := testdata.NewTestFeeAmount() msgs := []sdk.Msg{msg1} - txBuilder := newBuilder() + txBuilder := newBuilder(nil) err := txBuilder.SetMsgs(msgs...) require.NoError(t, err) txBuilder.SetGasLimit(200000) diff --git a/x/auth/tx/config.go b/x/auth/tx/config.go index 8402423dbf77..f850014b657d 100644 --- a/x/auth/tx/config.go +++ b/x/auth/tx/config.go @@ -35,7 +35,7 @@ func NewTxConfig(protoCodec codec.ProtoCodecMarshaler, enabledSignModes []signin } func (g config) NewTxBuilder() client.TxBuilder { - return newBuilder() + return newBuilder(g.protoCodec) } // WrapTxBuilder returns a builder from provided transaction diff --git a/x/auth/tx/encode_decode_test.go b/x/auth/tx/encode_decode_test.go index d4d5ad7554d0..5991f6653450 100644 --- a/x/auth/tx/encode_decode_test.go +++ b/x/auth/tx/encode_decode_test.go @@ -24,7 +24,7 @@ func TestDefaultTxDecoderError(t *testing.T) { encoder := DefaultTxEncoder() decoder := DefaultTxDecoder(cdc) - builder := newBuilder() + builder := newBuilder(nil) err := builder.SetMsgs(testdata.NewTestMsg()) require.NoError(t, err) diff --git a/x/auth/tx/legacy_amino_json_test.go b/x/auth/tx/legacy_amino_json_test.go index 9734e7704eea..7bb4770fa6bd 100644 --- a/x/auth/tx/legacy_amino_json_test.go +++ b/x/auth/tx/legacy_amino_json_test.go @@ -86,7 +86,7 @@ func TestLegacyAminoJSONHandler_GetSignBytes(t *testing.T) { for _, tc := range testcases { tc := tc t.Run(tc.name, func(t *testing.T) { - bldr := newBuilder() + bldr := newBuilder(nil) buildTx(t, bldr) tx := bldr.GetTx() tc.malleate(bldr) @@ -104,7 +104,7 @@ func TestLegacyAminoJSONHandler_GetSignBytes(t *testing.T) { }) } - bldr := newBuilder() + bldr := newBuilder(nil) buildTx(t, bldr) tx := bldr.GetTx() signingData := signing.SignerData{ @@ -120,7 +120,7 @@ func TestLegacyAminoJSONHandler_GetSignBytes(t *testing.T) { require.Error(t, err) // expect error with extension options - bldr = newBuilder() + bldr = newBuilder(nil) buildTx(t, bldr) any, err := cdctypes.NewAnyWithValue(testdata.NewTestMsg()) require.NoError(t, err) @@ -130,7 +130,7 @@ func TestLegacyAminoJSONHandler_GetSignBytes(t *testing.T) { require.Error(t, err) // expect error with non-critical extension options - bldr = newBuilder() + bldr = newBuilder(nil) buildTx(t, bldr) bldr.tx.Body.NonCriticalExtensionOptions = []*cdctypes.Any{any} tx = bldr.GetTx()