-
Notifications
You must be signed in to change notification settings - Fork 3.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Add AuxTxBuilder #10455
feat: Add AuxTxBuilder #10455
Conversation
Codecov Report
@@ Coverage Diff @@
## master #10455 +/- ##
==========================================
- Coverage 64.33% 64.09% -0.24%
==========================================
Files 592 582 -10
Lines 56008 55299 -709
==========================================
- Hits 36034 35446 -588
+ Misses 17912 17835 -77
+ Partials 2062 2018 -44
|
God bless this API @AmauryM! How does the API look from the tx fee signer PoV? Assuming that I am the tipper, I get my tx bytes, now I forward them to someone who wants to pay the fees for me, how does the API look using aux tx builder? |
The fee signer will use DIRECT (or LEGACY_AMINO_JSON maybe), so he will use the existing Probably a good idea is to add a |
Probably Would be great to find a way to simplify the signing workflow too. How does this interact with pubkeys and sign modes? |
x/auth/tx/aux_test.go
Outdated
// Fee payer creates a TxBuilder. | ||
w := encCfg.TxConfig.NewTxBuilder() | ||
err = w.AddAuxSignerData(auxSignerData) | ||
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] | ||
// Set all signer infos. | ||
w.SetSignatures(tipperSigV2, 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, signing.SignatureV2{ | ||
PubKey: feepayerPk, | ||
Data: &signing.SingleSignatureData{ | ||
SignMode: signing.SignMode_SIGN_MODE_DIRECT, | ||
Signature: feepayerSig, | ||
}, | ||
Sequence: 15, | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fee payer POV using TxBuilder. It's still calling SetSignatures twice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks pretty close but left one comment to address
x/auth/tx/builder.go
Outdated
w.bodyBz = data.SignDoc.BodyBytes | ||
w.SetTip(data.GetSignDoc().GetTip()) | ||
|
||
w.setSignerInfos(append(w.tx.AuthInfo.SignerInfos, &tx.SignerInfo{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if this is the wrong order? This method should check the signers and insert it in the correct position I think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this in b9f3cf3. It required 2 changes:
- adding the interface registry to the wrapper, because it's needed to decode
data.SignDoc.BodyBytes
to get the Msgs.GetSigners() - each AuxSignerData has the signer's PubKey, but not address. We'll either need to use a bech32 converter (aka for now: the global sdk.AccAddress), or maybe we can add a new
address
field in AuxSignerData
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll try adding a new address
field in AuxSignerData
Edit: done in 0917b76
// 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Tx body_bytes is already in SignDocDirectAux. However, do we also want to copy it here as a top-level TxBody body = 3;
field?
Pros:
- If using JSON, then the fee payer can have human readable description of msgs, without needing to deserialize the body_bytes. Currently, we only see base64 bytes.
Cons:
- More data to be transmitted over the wire (as it's a duplicate of
sign_doc.body_bytes
) - only useful for JSON to be read by humans. In general, for machine-to-machine, proto binary should be preferred (more compact)
// LEGACY_AMINO_JSON. | ||
SignDocDirectAux sign_doc = 2; | ||
// mode is the signing mode of the single signer | ||
cosmos.tx.signing.v1beta1.SignMode mode = 3; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing I thought about looking at this: what if the aux signer is a legacy multisig? Do we want to support that?
If yes we should probably do:
cosmos.tx.signing.v1beta1.SignMode mode = 3; | |
cosmos.tx.signing.v1beta1.ModeInfo mode_info = 3; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How hard would it be to add this? I can't imagine multisigs using tips but there may be cases where it makes sense for them to do aux signing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Say we have a 2/2 multisig as tipper. The 2 signers will use AuxTxBuilder, and use a single mode info anyways.
Who should aggregate the signatures? Probably the fee payer I'd say? In which case I believe it's actually better API to keep cosmos.tx.signing.v1beta1.SignMode mode = 3;
here, and maybe add a AddMultisigAuxSignerData
on the fee payer's txbuilder
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some comments and questions!
// GetSignBytes returns the builder's sign bytes. | ||
func (b *AuxTxBuilder) GetSignBytes() ([]byte, error) { | ||
auxTx := b.auxSignerData | ||
if auxTx == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add additional/deeper checks in these initial guards? A call to a single setter that calls checkEmptyFields()
will make b.body
, b.auxSignerData
, and b.auxSignerData.SignDoc
non-nil but that doesn't mean they are properly set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The way I was thinking was to add some nil-guards here. Then, for actual validation, we call b.auxSignerData.SignDoc.ValidateBasic()
and b.auxSignerData.ValidateBasic()
.
tipperBuilder.SetAddress(tipperAddr.String()) | ||
tipperBuilder.SetAccountNumber(1) | ||
tipperBuilder.SetSequence(2) | ||
tipperBuilder.SetTimeoutHeight(3) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly a stupid question but what would happen if the tipper and the aux signer had different timeout heights? As far as I can tell this wouldn't throw any errors anywhere in this building process, and the final tx timeout will be whichever timeout is on the last signer data fed to AddAuxSignerData()
. Actually I think this question extends to all the signer data that is not being set at a specific index in AddAuxSignerData()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not a stupid question! I thought about this too, but I went for the lazy way, which is that signature verification will fail when sending this tx (with 2 different timeout heights) to the node.
But maybe it's better to catch this early. I'll add some basic checks in AddAuxSignerData
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. It may make sense to support multisigs, however. If it's not too hard I would vote yes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
<!-- The default pull request template is for types feat, fix, or refactor. For other templates, add one of the following parameters to the url: - template=docs.md - template=other.md --> ## Description Closes: cosmos#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)
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):
auxSignerData is a protobuf message, whose JSON reprensentation looks like:
Then the fee payer would use the TxBuilder to construct the final TX, with a new helper method:
AddAuxSignerData
: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...
!
to the type prefix if API or client breaking changeCHANGELOG.md
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...
!
in the type prefix if API or client breaking change