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

fix: skip same-sender non-sequential sequence and then add others txs #19119

Closed
wants to merge 1 commit into from

Conversation

ZiHengLee
Copy link
Contributor

@ZiHengLee ZiHengLee commented Jan 19, 2024

Description

Closes: #19097


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
  • confirmed ! in 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
  • reviewed "Files changed" and left comments if necessary
  • included the necessary unit and integration tests
  • added a changelog entry to CHANGELOG.md
  • updated the relevant documentation or specification, including comments for documenting Go code
  • 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 all author checklist items have been addressed
  • reviewed state machine logic, API design and naming, documentation is accurate, tests and test coverage

@ZiHengLee ZiHengLee requested a review from a team as a code owner January 19, 2024 08:31
Copy link
Contributor

coderabbitai bot commented Jan 19, 2024

Walkthrough

The overall change involves modifications to the transaction selection process in the Cosmos SDK, specifically addressing a bug in the SelectTxForProposal function that previously allowed non-sequential transactions from the same sender to be included in a proposal. The update introduces a new mechanism to ensure transactions are selected sequentially per sender, thereby preventing block rejection. Additionally, new tests have been added to validate this behavior, and functions supporting transaction signing have been included to facilitate testing.

Changes

File Pattern Change Summary
baseapp/abci_utils.go Modified PrepareProposalHandler to use signerExtAdapter and updated logic for tracking transactions.
baseapp/abci_utils_test.go Added TestDefaultProposalHandler_PriorityNonceMempoolTxSelection and buildMsg function for testing transaction selection.
baseapp/utils_test.go Added setTxSignatureWithSecret function for setting transaction signatures in tests.
types/mempool/... Added Sender() method to the Iterator interface in various mempool files.
CHANGELOG.md Documented bug fixes in the baseapp module related to transaction handling and error management.

Assessment against linked issues

Objective Addressed Explanation
Resolve the issue causing the selection of non-sequential sequence transactions from the same sender in SelectTxForProposal (#19097).
Ensure sequential sequence number selection for transactions from the same sender to prevent block rejection (#19097).
Provide a test case to reproduce the issue and validate the fix (#19097).

The provided code changes address the key objectives specified in the linked issue #19097. The PrepareProposalHandler method has been updated to select transactions with sequential sequence numbers, preventing the selection of non-sequential transactions from the same sender. The addition of a test case specifically designed to reproduce the issue and validate the fix further confirms that the objectives have been met.

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share

Tips

Chat

There are 3 ways to chat with CodeRabbit:

Note: Auto-reply has been disabled for this repository by the repository owner. The CodeRabbit bot will not respond to your comments unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit tests for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository from git and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit tests.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • The JSON schema for the configuration file is available here.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/coderabbit-overrides.v2.json

CodeRabbit Discord Community

Join our Discord Community to get help, request features, and share feedback.

Comment on lines 227 to 267
}

iterator := h.mempool.Select(ctx, req.Txs)
skipSenders := make(map[string]any)
var selectedTxsNums int
for iterator != nil {
memTx := iterator.Tx()

sender := iterator.Sender()
if _, ok := skipSenders[sender]; ok {
iterator = iterator.Next()
continue
}
// NOTE: Since transaction verification was already executed in CheckTx,
// which calls mempool.Insert, in theory everything in the pool should be
// valid. But some mempool implementations may insert invalid txs, so we
Copy link
Contributor

Choose a reason for hiding this comment

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

📝 NOTE
This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [230-258]

The logic introduced in the PrepareProposalHandler method to skip transactions from senders with non-sequential transactions is consistent with the PR objectives. The use of the skipSenders map to track senders whose transactions should be skipped and the selectedTxsNums variable to count the number of selected transactions are appropriate for the intended functionality. However, there is a potential issue with the logic that determines when to skip a sender's transactions.

- if txsLen == selectedTxsNums {
+ if txsLen <= selectedTxsNums {
  skipSenders[sender] = nil
}

The condition should check if the length of the selected transactions has not increased (<=) after attempting to add a new transaction, indicating that the transaction was not added and the sender should be skipped.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

okay

Comment on lines 419 to 527
func (s *ABCIUtilsTestSuite) TestDefaultProposalHandler_PriorityNonceMempoolTxSelection() {
cdc := codectestutil.CodecOptions{}.NewCodec()
baseapptestutil.RegisterInterfaces(cdc.InterfaceRegistry())
txConfig := authtx.NewTxConfig(cdc, authtx.DefaultSignModes)
ctrl := gomock.NewController(s.T())
app := mock.NewMockProposalTxVerifier(ctrl)
mp := mempool.NewPriorityMempool(
mempool.PriorityNonceMempoolConfig[int64]{
TxPriority: mempool.NewDefaultTxPriority(),
MaxTx: 0,
SignerExtractor: mempool.NewDefaultSignerExtractionAdapter(),
},
)
ph := baseapp.NewDefaultProposalHandler(mp, app)
handler := ph.PrepareProposalHandler()
var (
secret1 = []byte("secret1")
secret2 = []byte("secret2")
tx1 = buildMsg(s.T(), txConfig, []byte(`1`), secret1, 1)
ctx1 = s.ctx.WithPriority(10)
tx2 = buildMsg(s.T(), txConfig, []byte(`12345678910`), secret1, 2)
tx3 = buildMsg(s.T(), txConfig, []byte(`12`), secret1, 3)

ctx2 = s.ctx.WithPriority(8)
tx4 = buildMsg(s.T(), txConfig, []byte(`12`), secret2, 1)
)
mp.Insert(ctx1, tx1)
mp.Insert(ctx1, tx2)
mp.Insert(ctx1, tx3)
mp.Insert(ctx2, tx4)

txBz1, err := txConfig.TxEncoder()(tx1)
s.Require().NoError(err)
txBz2, err := txConfig.TxEncoder()(tx2)
s.Require().NoError(err)
txBz3, err := txConfig.TxEncoder()(tx3)
s.Require().NoError(err)
txBz4, err := txConfig.TxEncoder()(tx4)
s.Require().NoError(err)

app.EXPECT().PrepareProposalVerifyTx(tx1).Return(txBz1, nil).AnyTimes()
app.EXPECT().PrepareProposalVerifyTx(tx2).Return(txBz2, nil).AnyTimes()
app.EXPECT().PrepareProposalVerifyTx(tx3).Return(txBz3, nil).AnyTimes()
app.EXPECT().PrepareProposalVerifyTx(tx4).Return(txBz4, nil).AnyTimes()

txDataSize1 := int(cmttypes.ComputeProtoSizeForTxs([]cmttypes.Tx{txBz1}))
txDataSize2 := int(cmttypes.ComputeProtoSizeForTxs([]cmttypes.Tx{txBz2}))
txDataSize3 := int(cmttypes.ComputeProtoSizeForTxs([]cmttypes.Tx{txBz3}))
txDataSize4 := int(cmttypes.ComputeProtoSizeForTxs([]cmttypes.Tx{txBz4}))
s.Require().Equal(txDataSize1, 111)
s.Require().Equal(txDataSize2, 121)
s.Require().Equal(txDataSize3, 112)
s.Require().Equal(txDataSize4, 112)

testCases := map[string]struct {
ctx sdk.Context
req *abci.RequestPrepareProposal
expectedTxs [][]byte
}{
"skip same-sender non-sequential sequence and then add others txs": {
ctx: s.ctx,
req: &abci.RequestPrepareProposal{
Txs: [][]byte{txBz1, txBz2, txBz3, txBz4},
MaxTxBytes: 111 + 112 + 1,
},
expectedTxs: [][]byte{txBz1, txBz4},
},
}

for name, tc := range testCases {
s.Run(name, func() {
resp, err := handler(tc.ctx, tc.req)
s.Require().NoError(err)
s.Require().EqualValues(resp.Txs, tc.expectedTxs)
})
}
}

func buildMsg(t *testing.T, txConfig client.TxConfig, value, secret []byte, nonce uint64) sdk.Tx {
builder := txConfig.NewTxBuilder()
builder.SetMsgs(
&baseapptestutil.MsgKeyValue{Value: value},
)
setTxSignatureWithSecret(t, builder, nonce, secret)
return builder.GetTx()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The new test TestDefaultProposalHandler_PriorityNonceMempoolTxSelection is well-structured and covers the scenario where transactions from the same sender with non-sequential nonce values are correctly skipped. The test ensures that the proposal handler selects and prepares transactions based on priority and nonce as expected. The use of the buildMsg helper function simplifies the creation of test transactions.

However, there is a minor issue with the expectedTxs slice in the test case. The MaxTxBytes is set to 111 + 112 + 1, which seems to be intended to allow only two transactions (txBz1 and txBz4) based on their data sizes. But the + 1 is unnecessary and could be misleading since the data sizes already account for the full size of the transactions.

- MaxTxBytes: 111 + 112 + 1,
+ MaxTxBytes: 111 + 112,

This change would make the intention clearer and avoid any confusion about the calculation of MaxTxBytes.


Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
func (s *ABCIUtilsTestSuite) TestDefaultProposalHandler_PriorityNonceMempoolTxSelection() {
cdc := codectestutil.CodecOptions{}.NewCodec()
baseapptestutil.RegisterInterfaces(cdc.InterfaceRegistry())
txConfig := authtx.NewTxConfig(cdc, authtx.DefaultSignModes)
ctrl := gomock.NewController(s.T())
app := mock.NewMockProposalTxVerifier(ctrl)
mp := mempool.NewPriorityMempool(
mempool.PriorityNonceMempoolConfig[int64]{
TxPriority: mempool.NewDefaultTxPriority(),
MaxTx: 0,
SignerExtractor: mempool.NewDefaultSignerExtractionAdapter(),
},
)
ph := baseapp.NewDefaultProposalHandler(mp, app)
handler := ph.PrepareProposalHandler()
var (
secret1 = []byte("secret1")
secret2 = []byte("secret2")
tx1 = buildMsg(s.T(), txConfig, []byte(`1`), secret1, 1)
ctx1 = s.ctx.WithPriority(10)
tx2 = buildMsg(s.T(), txConfig, []byte(`12345678910`), secret1, 2)
tx3 = buildMsg(s.T(), txConfig, []byte(`12`), secret1, 3)
ctx2 = s.ctx.WithPriority(8)
tx4 = buildMsg(s.T(), txConfig, []byte(`12`), secret2, 1)
)
mp.Insert(ctx1, tx1)
mp.Insert(ctx1, tx2)
mp.Insert(ctx1, tx3)
mp.Insert(ctx2, tx4)
txBz1, err := txConfig.TxEncoder()(tx1)
s.Require().NoError(err)
txBz2, err := txConfig.TxEncoder()(tx2)
s.Require().NoError(err)
txBz3, err := txConfig.TxEncoder()(tx3)
s.Require().NoError(err)
txBz4, err := txConfig.TxEncoder()(tx4)
s.Require().NoError(err)
app.EXPECT().PrepareProposalVerifyTx(tx1).Return(txBz1, nil).AnyTimes()
app.EXPECT().PrepareProposalVerifyTx(tx2).Return(txBz2, nil).AnyTimes()
app.EXPECT().PrepareProposalVerifyTx(tx3).Return(txBz3, nil).AnyTimes()
app.EXPECT().PrepareProposalVerifyTx(tx4).Return(txBz4, nil).AnyTimes()
txDataSize1 := int(cmttypes.ComputeProtoSizeForTxs([]cmttypes.Tx{txBz1}))
txDataSize2 := int(cmttypes.ComputeProtoSizeForTxs([]cmttypes.Tx{txBz2}))
txDataSize3 := int(cmttypes.ComputeProtoSizeForTxs([]cmttypes.Tx{txBz3}))
txDataSize4 := int(cmttypes.ComputeProtoSizeForTxs([]cmttypes.Tx{txBz4}))
s.Require().Equal(txDataSize1, 111)
s.Require().Equal(txDataSize2, 121)
s.Require().Equal(txDataSize3, 112)
s.Require().Equal(txDataSize4, 112)
testCases := map[string]struct {
ctx sdk.Context
req *abci.RequestPrepareProposal
expectedTxs [][]byte
}{
"skip same-sender non-sequential sequence and then add others txs": {
ctx: s.ctx,
req: &abci.RequestPrepareProposal{
Txs: [][]byte{txBz1, txBz2, txBz3, txBz4},
MaxTxBytes: 111 + 112 + 1,
},
expectedTxs: [][]byte{txBz1, txBz4},
},
}
for name, tc := range testCases {
s.Run(name, func() {
resp, err := handler(tc.ctx, tc.req)
s.Require().NoError(err)
s.Require().EqualValues(resp.Txs, tc.expectedTxs)
})
}
}
func buildMsg(t *testing.T, txConfig client.TxConfig, value, secret []byte, nonce uint64) sdk.Tx {
builder := txConfig.NewTxBuilder()
builder.SetMsgs(
&baseapptestutil.MsgKeyValue{Value: value},
)
setTxSignatureWithSecret(t, builder, nonce, secret)
return builder.GetTx()
}
"skip same-sender non-sequential sequence and then add others txs": {
ctx: s.ctx,
req: &abci.RequestPrepareProposal{
Txs: [][]byte{txBz1, txBz2, txBz3, txBz4},
MaxTxBytes: 111 + 112,
},
expectedTxs: [][]byte{txBz1, txBz4},
},

Copy link
Contributor Author

Choose a reason for hiding this comment

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

okay,

@alexanderbez
Copy link
Contributor

Hi @ZiHengLee, please update the PR description with a changelog or what exactly this PR does and why. This would be helpful to provide context to reviewers.

@ZiHengLee
Copy link
Contributor Author

Hi @ZiHengLee, please update the PR description with a changelog or what exactly this PR does and why. This would be helpful to provide context to reviewers.

done

@alexanderbez
Copy link
Contributor

@facundomedica I believe you have a similar PR in the works. Should we compare and contract solutions?

@ZiHengLee
Copy link
Contributor Author

@facundomedica I believe you have a similar PR in the works. Should we compare and contract solutions?

okay,but i think this pr more bette,our project is running normally so far

@ZiHengLee
Copy link
Contributor Author

@facundomedica I believe you have a similar PR in the works. Should we compare and contract solutions?

okay,but i think this pr more better,our project is running normally so far

@facundomedica
Copy link
Member

The solution I was thinking about is pretty much the same as yours, with the main difference being that I encapsulated it into the tx selector (https://github.com/cosmos/cosmos-sdk/compare/facu/fix-samesender-nonseq?expand=1)

@ZiHengLee
Copy link
Contributor Author

ZiHengLee commented Jan 23, 2024

The solution I was thinking about is pretty much the same as yours, with the main difference being that I encapsulated it into the tx selector (https://github.com/cosmos/cosmos-sdk/compare/facu/fix-samesender-nonseq?expand=1)

i consider this solution before,but it'll leading to poor performance.
many invalid txs will execute again here
txBz, err := h.txVerifier.PrepareProposalVerifyTx(memTx)
so i did skipsenders judgment before this function to avoid invalid tx

@ZiHengLee
Copy link
Contributor Author

The solution I was thinking about is pretty much the same as yours, with the main difference being that I encapsulated it into the tx selector (https://github.com/cosmos/cosmos-sdk/compare/facu/fix-samesender-nonseq?expand=1)

how about my new solution,merging our two ideas(solved multi signdata) and better performance
#19177

and i fund a bug in your new solution,muilti signdata,only one signer seq is true,u just add tx

	for _, signer := range signerData {
		seq, ok := ts.selectedTxsSignersSeqs[string(signer.Signer)]
		if !ok {
			ts.selectedTxsSignersSeqs[string(signer.Signer)] = signer.Sequence
			shouldAdd = true
			continue
		}

		// if we have seen this signer before we check if the sequence we just got is seq+1 and if it is we update the
		// sequence and return true so this tx gets selected. If it isn't seq+1 we return false so this tx doesn't get
		// selected (it could be the same sequence or seq+2 which are invalid).
		if seq+1 == signer.Sequence {
			ts.selectedTxsSignersSeqs[string(signer.Signer)] = signer.Sequence
			shouldAdd = true
		}
	}

@facundomedica
Copy link
Member

Let's continue the discussion on the latest PR (#19177), and if you need to make changes do it on that same one. Do not open another PR, thank you! 🙏

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.

[Bug]: function SelectTxForProposal cause same sender non-sequential sequence
3 participants