-
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
fix(auth): audit issues with unordered txs #23392
Changes from all commits
0603c13
9a7dbff
a54dadf
99f2f31
7f37647
3c075bc
7119934
7d722fd
9b61d0d
2f4c25d
5104784
a4ee4d9
a7541ed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ import ( | |
"fmt" | ||
"math/rand" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/require" | ||
|
||
|
@@ -192,3 +193,67 @@ func (s *MempoolTestSuite) TestTxNotFoundOnSender() { | |
err = mp.Remove(tx) | ||
require.Equal(t, mempool.ErrTxNotFound, err) | ||
} | ||
|
||
func (s *MempoolTestSuite) TestUnorderedTx() { | ||
t := s.T() | ||
|
||
ctx := sdk.NewContext(nil, false, log.NewNopLogger()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should be able to just use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need an sdk.Context here, because below we call ctx.WithPriority when calling mp.Insert |
||
accounts := simtypes.RandomAccounts(rand.New(rand.NewSource(0)), 2) | ||
sa := accounts[0].Address | ||
sb := accounts[1].Address | ||
|
||
mp := mempool.NewSenderNonceMempool(mempool.SenderNonceMaxTxOpt(5000)) | ||
|
||
now := time.Now() | ||
oneHour := now.Add(1 * time.Hour) | ||
thirtyMin := now.Add(30 * time.Minute) | ||
twoHours := now.Add(2 * time.Hour) | ||
fifteenMin := now.Add(15 * time.Minute) | ||
|
||
txs := []testTx{ | ||
{id: 0, address: sa, timeout: &oneHour, unordered: true}, | ||
{id: 1, address: sa, timeout: &thirtyMin, unordered: true}, | ||
{id: 2, address: sb, timeout: &twoHours, unordered: true}, | ||
{id: 3, address: sb, timeout: &fifteenMin, unordered: true}, | ||
} | ||
|
||
for _, tx := range txs { | ||
c := ctx.WithPriority(tx.priority) | ||
require.NoError(t, mp.Insert(c, tx)) | ||
} | ||
|
||
require.Equal(t, 4, mp.CountTx()) | ||
|
||
orderedTxs := fetchTxs(mp.Select(ctx, nil), 100000) | ||
require.Equal(t, len(txs), len(orderedTxs)) | ||
|
||
// Because the sender is selected randomly it can be any of these options | ||
acceptableOptions := [][]int{ | ||
{3, 1, 2, 0}, | ||
{3, 1, 0, 2}, | ||
{3, 2, 1, 0}, | ||
{1, 3, 0, 2}, | ||
{1, 3, 2, 0}, | ||
{1, 0, 3, 2}, | ||
} | ||
|
||
orderedTxsIds := make([]int, len(orderedTxs)) | ||
for i, tx := range orderedTxs { | ||
orderedTxsIds[i] = tx.(testTx).id | ||
} | ||
|
||
anyAcceptableOrder := false | ||
for _, option := range acceptableOptions { | ||
for i, tx := range orderedTxs { | ||
if tx.(testTx).id != txs[option[i]].id { | ||
break | ||
} | ||
|
||
if i == len(orderedTxs)-1 { | ||
anyAcceptableOrder = true | ||
} | ||
} | ||
} | ||
|
||
require.True(t, anyAcceptableOrder, "expected any of %v but got %v", acceptableOptions, orderedTxsIds) | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -6,6 +6,7 @@ import ( | |||||||||||||||||||||||||||||||
"fmt" | ||||||||||||||||||||||||||||||||
"strings" | ||||||||||||||||||||||||||||||||
"testing" | ||||||||||||||||||||||||||||||||
"time" | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
"github.com/stretchr/testify/require" | ||||||||||||||||||||||||||||||||
"go.uber.org/mock/gomock" | ||||||||||||||||||||||||||||||||
|
@@ -1384,3 +1385,34 @@ func TestAnteHandlerReCheck(t *testing.T) { | |||||||||||||||||||||||||||||||
_, err = suite.anteHandler(suite.ctx, tx, false) | ||||||||||||||||||||||||||||||||
require.NotNil(t, err, "antehandler on recheck did not fail once feePayer no longer has sufficient funds") | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
func TestAnteHandlerUnorderedTx(t *testing.T) { | ||||||||||||||||||||||||||||||||
suite := SetupTestSuite(t, false) | ||||||||||||||||||||||||||||||||
accs := suite.CreateTestAccounts(1) | ||||||||||||||||||||||||||||||||
msg := testdata.NewTestMsg(accs[0].acc.GetAddress()) | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// First send a normal sequential tx with sequence 0 | ||||||||||||||||||||||||||||||||
suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), accs[0].acc.GetAddress(), authtypes.FeeCollectorName, testdata.NewTestFeeAmount()).Return(nil).AnyTimes() | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
privs, accNums, accSeqs := []cryptotypes.PrivKey{accs[0].priv}, []uint64{1000}, []uint64{0} | ||||||||||||||||||||||||||||||||
_, err := suite.DeliverMsgs(t, privs, []sdk.Msg{msg}, testdata.NewTestFeeAmount(), testdata.NewTestGasLimit(), accNums, accSeqs, suite.ctx.ChainID(), false) | ||||||||||||||||||||||||||||||||
require.NoError(t, err) | ||||||||||||||||||||||||||||||||
Comment on lines
+1394
to
+1399
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add assertion for account sequence. The test should verify that the account sequence is incremented after the first transaction. _, err := suite.DeliverMsgs(t, privs, []sdk.Msg{msg}, testdata.NewTestFeeAmount(), testdata.NewTestGasLimit(), accNums, accSeqs, suite.ctx.ChainID(), false)
require.NoError(t, err)
+ // Verify sequence is incremented
+ acc := suite.accountKeeper.GetAccount(suite.ctx, accs[0].acc.GetAddress())
+ require.Equal(t, uint64(1), acc.GetSequence()) 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// we try to send another tx with the same sequence, it will fail | ||||||||||||||||||||||||||||||||
_, err = suite.DeliverMsgs(t, privs, []sdk.Msg{msg}, testdata.NewTestFeeAmount(), testdata.NewTestGasLimit(), accNums, accSeqs, suite.ctx.ChainID(), false) | ||||||||||||||||||||||||||||||||
require.Error(t, err) | ||||||||||||||||||||||||||||||||
Comment on lines
+1401
to
+1403
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Enhance error assertion. The error check should verify the specific error type for sequence mismatch. - require.Error(t, err)
+ require.ErrorIs(t, err, sdkerrors.ErrWrongSequence) 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// now we'll still use the same sequence but because it's unordered, it will be ignored and accepted anyway | ||||||||||||||||||||||||||||||||
msgs := []sdk.Msg{msg} | ||||||||||||||||||||||||||||||||
require.NoError(t, suite.txBuilder.SetMsgs(msgs...)) | ||||||||||||||||||||||||||||||||
suite.txBuilder.SetFeeAmount(testdata.NewTestFeeAmount()) | ||||||||||||||||||||||||||||||||
suite.txBuilder.SetGasLimit(testdata.NewTestGasLimit()) | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
tx, txErr := suite.CreateTestUnorderedTx(suite.ctx, privs, accNums, accSeqs, suite.ctx.ChainID(), apisigning.SignMode_SIGN_MODE_DIRECT, true, time.Now().Add(time.Minute)) | ||||||||||||||||||||||||||||||||
require.NoError(t, txErr) | ||||||||||||||||||||||||||||||||
txBytes, err := suite.clientCtx.TxConfig.TxEncoder()(tx) | ||||||||||||||||||||||||||||||||
bytesCtx := suite.ctx.WithTxBytes(txBytes) | ||||||||||||||||||||||||||||||||
require.NoError(t, err) | ||||||||||||||||||||||||||||||||
_, err = suite.anteHandler(bytesCtx, tx, false) | ||||||||||||||||||||||||||||||||
require.NoError(t, err) | ||||||||||||||||||||||||||||||||
Comment on lines
+1411
to
+1417
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add timeout validation test cases. The test should include cases for expired and future timeouts to ensure proper validation. + // Test expired timeout
+ expiredTx, _ := suite.CreateTestUnorderedTx(suite.ctx, privs, accNums, accSeqs, suite.ctx.ChainID(), apisigning.SignMode_SIGN_MODE_DIRECT, true, time.Now().Add(-time.Minute))
+ _, err = suite.anteHandler(bytesCtx, expiredTx, false)
+ require.ErrorIs(t, err, sdkerrors.ErrTxTimeoutHeight)
+
+ // Test far future timeout
+ futureTx, _ := suite.CreateTestUnorderedTx(suite.ctx, privs, accNums, accSeqs, suite.ctx.ChainID(), apisigning.SignMode_SIGN_MODE_DIRECT, true, time.Now().Add(24*time.Hour))
+ _, err = suite.anteHandler(bytesCtx, futureTx, false)
+ require.ErrorIs(t, err, sdkerrors.ErrInvalidTimeout)
|
||||||||||||||||||||||||||||||||
} |
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.
This was in the old code before but as you touched it ( ;-) ), it would make sense to have this block moved before
snm.senders
is set in L145. We should not add elements to the object before the error cases are handled