Skip to content
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

Merged
merged 23 commits into from
Nov 11, 2021
Merged

feat: Add AuxTxBuilder #10455

merged 23 commits into from
Nov 11, 2021

Conversation

amaury1093
Copy link
Contributor

@amaury1093 amaury1093 commented Oct 28, 2021

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):

// 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:

{
  "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:

// 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 in the PR title
  • added ! to the type prefix if API or client breaking change
  • targeted the correct branch (see PR Targeting)
  • provided a link to the relevant issue or specification
  • followed the guidelines for building modules
  • included the necessary unit and integration tests
  • added a changelog entry to CHANGELOG.md
  • included comments for documenting Go code
  • 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 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)

@github-actions github-actions bot added the C:CLI label Oct 28, 2021
client/tx/aux_builder.go Outdated Show resolved Hide resolved
@codecov
Copy link

codecov bot commented Oct 28, 2021

Codecov Report

Merging #10455 (2c68763) into master (2394266) will decrease coverage by 0.23%.
The diff coverage is 0.00%.

❗ Current head 2c68763 differs from pull request most recent head 825b286. Consider uploading reports for the commit 825b286 to get more accurate results
Impacted file tree graph

@@            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     
Impacted Files Coverage Δ
client/tx/aux_builder.go 0.00% <0.00%> (ø)
x/distribution/simulation/operations.go 80.54% <0.00%> (-9.73%) ⬇️
x/nft/keeper/class.go 73.33% <0.00%> (-6.67%) ⬇️
x/mint/module.go 52.54% <0.00%> (-3.02%) ⬇️
store/cachekv/store.go 77.63% <0.00%> (-1.32%) ⬇️
x/mint/types/genesis.go 0.00% <0.00%> (ø)
crypto/keys/secp256k1/secp256k1_nocgo.go 83.33% <0.00%> (ø)
cosmovisor/cmd/cosmovisor/cmd/root.go
cosmovisor/cmd/cosmovisor/cmd/help.go
cosmovisor/cmd/cosmovisor/cmd/run.go
... and 9 more

@amaury1093 amaury1093 changed the title feat: Add AuxTx and AuxTxBuilder feat: Add AuxTxBuilder Oct 28, 2021
@fdymylja
Copy link
Contributor

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?

@amaury1093
Copy link
Contributor Author

How does the API look from the tx fee signer PoV?

The fee signer will use DIRECT (or LEGACY_AMINO_JSON maybe), so he will use the existing client.TxBuilder (aka the wrapper).

Probably a good idea is to add a func (w *wrapper) SetAuxSignerData(asd tx.AuxSignerData) helper on client.TxBuilder, for the fee payer. I'll still need to see how this plays with its SetSignatures

@aaronc
Copy link
Member

aaronc commented Oct 28, 2021

How does the API look from the tx fee signer PoV?

The fee signer will use DIRECT (or LEGACY_AMINO_JSON maybe), so he will use the existing client.TxBuilder (aka the wrapper).

Probably a good idea is to add a func (w *wrapper) SetAuxSignerData(asd tx.AuxSignerData) helper on client.TxBuilder, for the fee payer. I'll still need to see how this plays with its SetSignatures

Probably AddAuxSignerData.

Would be great to find a way to simplify the signing workflow too. How does this interact with pubkeys and sign modes?

@amaury1093 amaury1093 marked this pull request as ready for review November 8, 2021 15:59
Comment on lines 50 to 81
// 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,
})
Copy link
Contributor Author

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.

@amaury1093
Copy link
Contributor Author

Marking as R4R. @fdymylja @aaronc I also added in OP the fee payer's TxBuilder DX.

Copy link
Member

@aaronc aaronc left a 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

w.bodyBz = data.SignDoc.BodyBytes
w.SetTip(data.GetSignDoc().GetTip())

w.setSignerInfos(append(w.tx.AuthInfo.SignerInfos, &tx.SignerInfo{
Copy link
Member

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

Copy link
Contributor Author

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

Copy link
Contributor Author

@amaury1093 amaury1093 Nov 9, 2021

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

@i-norden i-norden self-assigned this Nov 9, 2021
// 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;
Copy link
Contributor Author

@amaury1093 amaury1093 Nov 9, 2021

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;
Copy link
Contributor Author

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:

Suggested change
cosmos.tx.signing.v1beta1.SignMode mode = 3;
cosmos.tx.signing.v1beta1.ModeInfo mode_info = 3;

Copy link
Member

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

Copy link
Contributor Author

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

Copy link
Contributor

@i-norden i-norden left a 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!

client/tx/aux_builder.go Show resolved Hide resolved
client/tx/aux_builder.go Outdated Show resolved Hide resolved
// GetSignBytes returns the builder's sign bytes.
func (b *AuxTxBuilder) GetSignBytes() ([]byte, error) {
auxTx := b.auxSignerData
if auxTx == nil {
Copy link
Contributor

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.

Copy link
Contributor Author

@amaury1093 amaury1093 Nov 10, 2021

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().

client/tx/aux_builder.go Outdated Show resolved Hide resolved
client/tx/aux_builder_test.go Show resolved Hide resolved
client/tx/aux_builder_test.go Show resolved Hide resolved
client/tx/aux_builder_test.go Outdated Show resolved Hide resolved
types/tx/aux_test.go Outdated Show resolved Hide resolved
tipperBuilder.SetAddress(tipperAddr.String())
tipperBuilder.SetAccountNumber(1)
tipperBuilder.SetSequence(2)
tipperBuilder.SetTimeoutHeight(3)
Copy link
Contributor

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().

Copy link
Contributor Author

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

Copy link
Member

@aaronc aaronc left a 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.

Copy link
Contributor

@i-norden i-norden left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@tac0turtle tac0turtle added the A:automerge Automatically merge PR once all prerequisites pass. label Nov 11, 2021
@mergify mergify bot merged commit 8fc9f76 into master Nov 11, 2021
@mergify mergify bot deleted the am/10443-aux-builder branch November 11, 2021 11:25
blewater pushed a commit to e-money/cosmos-sdk that referenced this pull request Dec 8, 2021
<!--
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A:automerge Automatically merge PR once all prerequisites pass. C:CLI C:x/auth
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Explore a new TxBuilder for SIGN_MODE_DIRECT_AUX (for tipper)
6 participants