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

Multiple messages #1266

Merged
merged 42 commits into from
Jun 21, 2018
Merged

Multiple messages #1266

merged 42 commits into from
Jun 21, 2018

Conversation

AdityaSripal
Copy link
Member

@AdityaSripal AdityaSripal commented Jun 15, 2018

  • Updated all relevant documentation in docs
  • Updated all code comments where relevant
  • Wrote tests
  • Updated CHANGELOG.md
  • Updated Gaia/Examples
  • Squashed all commits, uses message "Merge pull request #XYZ: [title]" (coding standards)

Addresses issues in #669 .

Currently all tests in types and x/auth pass.

@AdityaSripal AdityaSripal requested a review from ebuchman as a code owner June 15, 2018 06:39
@cwgoes
Copy link
Contributor

cwgoes commented Jun 16, 2018

Also ref #903 I believe.

@AdityaSripal AdityaSripal changed the title WIP: Multiple messages Multiple messages Jun 18, 2018
@AdityaSripal
Copy link
Member Author

Current way multiple messages are handled:

Consider a transaction with 4 messages where the second message fails.

Currently the first message will successfully update the state, and once the second msg fails, msgs 3 and 4 are ignored. Thus, only the first message will successfully change the state and Deliver(tx) will return the error result from the second msg's handler.

I'm not sure if this is the behavior we want. We could also just refuse to update the state unless all 4 msgs are successful.

Thoughts?

@cwgoes
Copy link
Contributor

cwgoes commented Jun 19, 2018

Currently the first message will successfully update the state, and once the second msg fails, msgs 3 and 4 are ignored. Thus, only the first message will successfully change the state and Deliver(tx) will return the error result from the second msg's handler.

I do not think this is the behavior we want; I think we want all transactions, including multi-Msg transactions, to be atomic: either all Msgs are successful and are applied to the state, or any fail and the state is unchanged.

As I understand it, the primary use case for multi-Msg transactions (besides just convenience) is atomicity: perhaps I want to finalize a governance proposal and spend funds (which the proposal will release) only if it passes, or trade a DEX order (in the future) and send tokens I just purchased to pay a friend, but not if the DEX order failed to match.

Alternatively, the transaction itself could include a flag to determine what happens when one Msg errors - either "stop execution but leave existing state changes", "continue anyways", or "revert previous state changes". I don't have particular use cases for the first two in mind but that would be more flexible.

@AdityaSripal
Copy link
Member Author

Got it. Transaction handling is now atomic on latest commit.

@cwgoes
Copy link
Contributor

cwgoes commented Jun 19, 2018

Got it. Transaction handling is now atomic on latest commit.

That was a stunning turnaround time. I think that's fine for now unless anyone has a compelling use-case for a different execution strategy.

cwgoes
cwgoes previously requested changes Jun 20, 2018
Copy link
Contributor

@cwgoes cwgoes left a comment

Choose a reason for hiding this comment

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

Thanks! Quickly reviewed, needs a rebase against develop, some changes to multi-Msg result construction, and tests for all the multi-signer functionality.

var msg = tx.GetMsg()
if msg == nil {
var msgs = tx.GetMsgs()
if msgs == nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

or len(msgs) == 0, I think, no reason to allow transactions with zero messages


// Set gas utilized
result.GasUsed = ctx.GasMeter().GasConsumed()
result = handler(ctx, msg)
Copy link
Contributor

Choose a reason for hiding this comment

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

We need to combine the results in some reasonably intelligent manner:

  • Return all codes / data / logs / info / tags
  • Add up gas used, return total used at the end

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point.

Seems like we can just append data and logs together, possibly with some seperator in between individual msg data/logs.

Tags and ValidatorUpdates should also be appended but dropping redundant elements.

Where is GasWanted set? Do we have to change its behavior with multiple msgs?

The code should only be nonzero if the msg fails, right?

Maybe we should do this:

Since we abort running the tx on the first message that fails, the result on a failed transaction should just be the result of the first failed message.

In the case where the tx successfully executes, we accumulate data/logs/info/tags in the way described above and return the result with code 0.

I think this makes it much easier to parse a failed result of a transaction since the user only cares about which message caused the failure.

Thoughts?

Copy link
Contributor

@cwgoes cwgoes Jun 20, 2018

Choose a reason for hiding this comment

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

Seems like we can just append data and logs together, possibly with some seperator in between individual msg data/logs.

I think the invariant always needs to hold that running a multi-Msg transaction has the same effect on the state as running the Msgs individually in separate transactions:

  1. Tags should be appended, we do not want to drop elements, this may block on Switch to list of lists of tags for transaction results, begin block, and end block tendermint/abci#273
  2. ValidatorUpdates need to be combined, not dropping redundant elements but rather adding/removing power, the updates should be cumulative
  3. Not sure about GasWanted, I don't understand why this field is in Result anyways. For now this can probably be set to the sum of GasWanted for all the individual results.

Since we abort running the tx on the first message that fails, the result on a failed transaction should just be the result of the first failed message. In the case where the tx successfully executes, we accumulate data/logs/info/tags in the way described above and return the result with code 0.

I think this is OK (to follow ABCI, we have to do this anyways); it would be nice to log that the previous messages succeeded somehow for ease of debugging.

Copy link
Member Author

Choose a reason for hiding this comment

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

Quick question:

In ValidatorUpdates, do we expect address and pubkey to be set correctly. Or do we expect just one to be set correctly like we currently do in x/auth/BaseAccount?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think the invariant always needs to hold that running a multi-Msg transaction has the same effect on the consensus state as running the Msgs individually in separate transactions

This currently is not respected with the way I handle transaction handling. If msgs are run in individual transactions, then the sequence number will increment each time.

If 5 msgs in the same transaction have the same signer, when that transaction is processed, the sequence number for the signer address is only incremented once.
This is because signer addresses are accumulated. (See stdTx.GetSigners() in x/auth/std_tx.go)

I think this is the behavior we want, but it will break the invariant.

Copy link
Contributor

Choose a reason for hiding this comment

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

In ValidatorUpdates, do we expect address and pubkey to be set correctly. Or do we expect just one to be set correctly like we currently do in x/auth/BaseAccount?

Send the address, I think we're getting rid of pubkeys in expectation of large future pubkeys (e.g. BLS).

If 5 msgs in the same transaction have the same signer, when that transaction is processed, the sequence number for the signer address is only incremented once.
This is because signer addresses are accumulated. (See stdTx.GetSigners() in x/auth/std_tx.go)

I stand corrected, this is an exception (should be the only one I think).

@@ -638,6 +639,318 @@ func TestValidatorChange(t *testing.T) {

//----------------------------------------

// Use burn and send msg types to test multiple msgs in one tx
type testBurnMsg struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

Will now need a GetMemo() function (which can return an empty string); rebase against develop (#1280 has since been merged).

@@ -15,7 +15,7 @@ The design of the Cosmos SDK is based on the principles of "capabilities systems
## Tx & Msg

The SDK distinguishes between transactions (Tx) and messages
(Msg). A Tx is a Msg wrapped with authentication and fee data.
(Msg). A Tx is a list of Msg's wrapped with authentication and fee data.
Copy link
Contributor

Choose a reason for hiding this comment

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

Msgs, no apostrophe

func newTestTx(ctx sdk.Context, msg sdk.Msg, privs []crypto.PrivKey, accNums []int64, seqs []int64, fee StdFee) sdk.Tx {
signBytes := StdSignBytes(ctx.ChainID(), accNums, seqs, fee, msg)
return newTestTxWithSignBytes(msg, privs, accNums, seqs, fee, signBytes)
func newTestTx(ctx sdk.Context, msgs []sdk.Msg, privs []crypto.PrivKey, accNums []int64, seqs []int64, fee StdFee) sdk.Tx {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add a new test transaction type with multiple required signers (with different account numbers / sequences) and test all the signature validity checking for that case?

Copy link
Member Author

Choose a reason for hiding this comment

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

Don't believe we need a new test transaction type for that? The signers, account numbers, and sequences are accumulated from the msgs passed in.

Tests for this were strewn across the test file. In my latest commit, I have a seperate function that tests multimsg signer functionality with different account numbers/sequences.

@AdityaSripal
Copy link
Member Author

I think all requested changes have been addressed. @cwgoes

@AdityaSripal AdityaSripal dismissed cwgoes’s stale review June 21, 2018 19:27

Addressed comments

cwgoes
cwgoes previously requested changes Jun 21, 2018
Copy link
Contributor

@cwgoes cwgoes left a comment

Choose a reason for hiding this comment

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

See comment re: validator set updates

logs = append(logs, fmt.Sprintf("Msg %d: %s", i + 1, finalResult.Log))

// Cumulate Validator updates
for _, val := range result.ValidatorUpdates {
Copy link
Contributor

Choose a reason for hiding this comment

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

  • I think this logic isn't quite right. Need to deal with the case of a power decrease too (which will have a lower power, not a negative power). Look at what Tendermint does to compute the change.
  • Can we separate this out into a function and add a few unit tests? Clearer & might be useful elsewhere.

Copy link
Contributor

Choose a reason for hiding this comment

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

Haha, nevermind - ValidatorSetUpdates should never have been in the result type to begin with.

Copy link
Member Author

Choose a reason for hiding this comment

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

So deliverTx should never change the validator set, right? It all happens in EndBlocker?

@AdityaSripal AdityaSripal dismissed cwgoes’s stale review June 21, 2018 21:16

Addressed comments

AccountNumber: accnum,
Sequence: sequence,
FeeBytes: fee.Bytes(),
MsgsBytes: msgBytes,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why did we remove the memo?

@cwgoes
Copy link
Contributor

cwgoes commented Jun 21, 2018

Also needs a changelog update

Copy link
Contributor

@cwgoes cwgoes left a comment

Choose a reason for hiding this comment

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

utACK

We need to clean up baseapp a bit, although this PR doesn't need to block on it - #1329.

@cwgoes
Copy link
Contributor

cwgoes commented Jun 22, 2018

Note on the result.ValidatorUpdates removal:

The sdk.Result type previously had a ValidatorUpdates field for accumulated Tendermint validator updates. This isn't a part of ABCI - it was just a convenience field exposed by the baseapp API to allow transactions to change the validator set directly.

As part of this PR, we discovered that there isn't a well-defined way to accumulate validator set updates for multi-Msg transactions - depending on whether modules track updates in their own store or track a diff from the validator set sent by Tendermint in abci.RequestBeginBlock, a different accumulation function would need to be used (and the existing accumulation function in baseapp was wrong in both cases).

Gaia didn't set ValidatorUpdates on any DeliverTx results anyways, since all updates need to be handled through the staking keeper, which updates the validator store and accumulates the necessary changes to send to Tendermint at the end of the block.

We thought restricting ValidatorUpdates to EndBlock was thus preferable, since it makes what modules need to do more obvious and avoids the ambiguous case for multi-Msg transactions - so this PR removed ValidatorUpdates from the sdk.Result type.

adrianbrink pushed a commit that referenced this pull request Jul 2, 2018
* Started work on multiple msgs, types and x/auth tests pass
* Fix issues in x, examples, and baseapp
* Added baseapp tests for multiple msgs
* Documentation fixes
* Fix baseapp tests with sdk.Int
* Modify test
* Transaction handling is now atomic
* Fix test comment
* Minor doc fixes and code cleanup
* Added baseapp result changes
* Use address in validator update accumulation
* Started work on multiple msgs, types and x/auth tests pass
* Fix issues in x, examples, and baseapp
* Added baseapp tests for multiple msgs
* Documentation fixes
* Fix baseapp tests with sdk.Int
* Modify test
* Transaction handling is now atomic
* Fix test comment
* Minor doc fixes and code cleanup
* Added baseapp result changes
* Use address in validator update accumulation
* Added ante tests for multisigner
* Remove validatorUpdates from tx result
* Better error logs
* Put Memo in StdSignBytes and formatting
* Updated changelog
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants