-
Notifications
You must be signed in to change notification settings - Fork 219
Supernova TxPool Part2: Fixed eviction consistency #7352
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
Changes from 4 commits
090ee54
4216d71
427145b
bf23bf6
dd489da
f7686e9
7afad8a
3a5548b
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 |
|---|---|---|
|
|
@@ -79,14 +79,11 @@ func (cache *TxCache) RemoveSweepableTxs(accountsProvider common.AccountNoncePro | |
| // we want to remove transactions with nonces < lastCommittedNonce | ||
| targetNonce := accountNonce - 1 | ||
|
|
||
| evicted = append(evicted, sender.removeSweepableTransactionsReturnHashes(targetNonce)...) | ||
| evicted = append(evicted, sender.removeSweepableTransactionsReturnHashes(targetNonce, cache.tracker)...) | ||
| } | ||
|
|
||
| if len(evicted) > 0 { | ||
| txs := cache.txByHash.GetTxsBulk(evicted) | ||
|
|
||
| untrackedEvicted := cache.tracker.GetBulkOfUntrackedTransactions(txs) | ||
| cache.txByHash.RemoveTxsBulk(untrackedEvicted) | ||
| cache.txByHash.RemoveTxsBulk(evicted) | ||
| } | ||
|
|
||
| logRemove.Debug("TxCache.RemoveSweepableTxs end", | ||
|
|
@@ -134,7 +131,7 @@ func shuffleSendersAddresses(senders []string, randomness uint64) { | |
| }) | ||
| } | ||
|
|
||
| func (listForSender *txListForSender) removeSweepableTransactionsReturnHashes(targetNonce uint64) [][]byte { | ||
| func (listForSender *txListForSender) removeSweepableTransactionsReturnHashes(targetNonce uint64, tracker *selectionTracker) [][]byte { | ||
| txHashesToEvict := make([][]byte, 0) | ||
|
|
||
| // We don't allow concurrent goroutines to mutate a given sender's list | ||
|
|
@@ -144,6 +141,7 @@ func (listForSender *txListForSender) removeSweepableTransactionsReturnHashes(ta | |
| for element := listForSender.items.Front(); element != nil; { | ||
| // finds transactions with lower nonces | ||
| tx := element.Value.(*WrappedTransaction) | ||
|
|
||
| txNonce := tx.Tx.GetNonce() | ||
|
|
||
| // nonces are sorted ascending, so we can stop as soon as we find a nonce that is higher | ||
|
|
@@ -158,12 +156,16 @@ func (listForSender *txListForSender) removeSweepableTransactionsReturnHashes(ta | |
| ) | ||
|
|
||
| nextElement := element.Next() | ||
| _ = listForSender.items.Remove(element) | ||
| listForSender.onRemovedListElement(element) | ||
| element = nextElement | ||
|
|
||
| // Keep track of removed transactions | ||
| txHashesToEvict = append(txHashesToEvict, tx.TxHash) | ||
| if !tracker.IsTransactionTracked(tx) { | ||
| _ = listForSender.items.Remove(element) | ||
| listForSender.onRemovedListElement(element) | ||
|
|
||
| // Keep track of removed transactions | ||
| txHashesToEvict = append(txHashesToEvict, tx.TxHash) | ||
| } | ||
|
|
||
| element = nextElement | ||
|
Comment on lines
+160
to
+168
|
||
| } | ||
|
|
||
| return txHashesToEvict | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -88,16 +88,17 @@ func TestTxCache_GetDeterministicallyShuffledSenders_Dummy(t *testing.T) { | |
|
|
||
| func Test_RemoveSweepableTransactionsReturnHashes_Dummy(t *testing.T) { | ||
| t.Run("with lower nonces", func(t *testing.T) { | ||
| txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, math.MaxUint32) | ||
| list := newUnconstrainedListToTest() | ||
| list.AddTx(createTx([]byte("a"), ".", 1)) | ||
| list.AddTx(createTx([]byte("b"), ".", 3)) | ||
| list.AddTx(createTx([]byte("c"), ".", 4)) | ||
| list.AddTx(createTx([]byte("d"), ".", 2)) | ||
| list.AddTx(createTx([]byte("e"), ".", 5)) | ||
| list.AddTx(createTx([]byte("a"), ".", 1), txCache.tracker) | ||
| list.AddTx(createTx([]byte("b"), ".", 3), txCache.tracker) | ||
| list.AddTx(createTx([]byte("c"), ".", 4), txCache.tracker) | ||
| list.AddTx(createTx([]byte("d"), ".", 2), txCache.tracker) | ||
| list.AddTx(createTx([]byte("e"), ".", 5), txCache.tracker) | ||
|
|
||
| hashesBeforeEviction := list.getTxHashesAsStrings() | ||
|
|
||
| list.removeSweepableTransactionsReturnHashes(uint64(3)) | ||
| list.removeSweepableTransactionsReturnHashes(uint64(3), txCache.tracker) | ||
|
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. shouldn't we also check autoclean for tracked transactions and also consistency between the 2 maps? |
||
| hashesAfterEviction := list.getTxHashesAsStrings() | ||
| require.Equal(t, []string{"c", "e"}, hashesAfterEviction) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -555,25 +555,9 @@ func (st *selectionTracker) getVirtualNonceOfAccountWithRootHash( | |
| return breadcrumb.lastNonce.Value + 1, st.latestRootHash, nil | ||
| } | ||
|
|
||
| // GetBulkOfUntrackedTransactions returns the hashes of the untracked transactions | ||
|
Contributor
Author
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. not used anymore |
||
| func (st *selectionTracker) GetBulkOfUntrackedTransactions(transactions []*WrappedTransaction) [][]byte { | ||
| untrackedTransactions := make([][]byte, 0) | ||
| for _, tx := range transactions { | ||
| if tx == nil || tx.Tx == nil { | ||
| continue | ||
| } | ||
|
|
||
| if !st.isTransactionTracked(tx) { | ||
| untrackedTransactions = append(untrackedTransactions, tx.TxHash) | ||
| } | ||
| } | ||
|
|
||
| return untrackedTransactions | ||
| } | ||
|
|
||
| // isTransactionTracked checks if a transaction is still in the tracked blocks of the SelectionTracker. | ||
| // However, in the case of forks, isTransactionTracked might return inaccurate results. | ||
| func (st *selectionTracker) isTransactionTracked(transaction *WrappedTransaction) bool { | ||
| // IsTransactionTracked checks if a transaction is still in the tracked blocks of the SelectionTracker. | ||
| // However, in the case of forks, IsTransactionTracked might return inaccurate results. | ||
| func (st *selectionTracker) IsTransactionTracked(transaction *WrappedTransaction) bool { | ||
| if transaction == nil || transaction.Tx == nil { | ||
| return false | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -117,21 +117,81 @@ func Test_AddNilTx_DoesNothing(t *testing.T) { | |
| } | ||
|
|
||
| func Test_AddTx_AppliesSizeConstraintsPerSenderForNumTransactions(t *testing.T) { | ||
| cache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) | ||
| t.Run("with untracked txs", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| cache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) | ||
|
|
||
| cache.AddTx(createTx([]byte("tx-alice-1"), "alice", 1)) | ||
| cache.AddTx(createTx([]byte("tx-alice-2"), "alice", 2)) | ||
| cache.AddTx(createTx([]byte("tx-alice-4"), "alice", 4)) | ||
| cache.AddTx(createTx([]byte("tx-bob-1"), "bob", 1)) | ||
| cache.AddTx(createTx([]byte("tx-bob-2"), "bob", 2)) | ||
| require.Equal(t, []string{"tx-alice-1", "tx-alice-2", "tx-alice-4"}, cache.getHashesForSender("alice")) | ||
| require.Equal(t, []string{"tx-bob-1", "tx-bob-2"}, cache.getHashesForSender("bob")) | ||
| require.True(t, cache.areInternalMapsConsistent()) | ||
|
|
||
| cache.AddTx(createTx([]byte("tx-alice-3"), "alice", 3)) | ||
| require.Equal(t, []string{"tx-alice-1", "tx-alice-2", "tx-alice-3"}, cache.getHashesForSender("alice")) | ||
| require.Equal(t, []string{"tx-bob-1", "tx-bob-2"}, cache.getHashesForSender("bob")) | ||
| require.True(t, cache.areInternalMapsConsistent()) | ||
| }) | ||
|
|
||
| cache.AddTx(createTx([]byte("tx-alice-1"), "alice", 1)) | ||
| cache.AddTx(createTx([]byte("tx-alice-2"), "alice", 2)) | ||
| cache.AddTx(createTx([]byte("tx-alice-4"), "alice", 4)) | ||
| cache.AddTx(createTx([]byte("tx-bob-1"), "bob", 1)) | ||
| cache.AddTx(createTx([]byte("tx-bob-2"), "bob", 2)) | ||
| require.Equal(t, []string{"tx-alice-1", "tx-alice-2", "tx-alice-4"}, cache.getHashesForSender("alice")) | ||
| require.Equal(t, []string{"tx-bob-1", "tx-bob-2"}, cache.getHashesForSender("bob")) | ||
| require.True(t, cache.areInternalMapsConsistent()) | ||
| t.Run("with tracked txs", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| cache.AddTx(createTx([]byte("tx-alice-3"), "alice", 3)) | ||
| require.Equal(t, []string{"tx-alice-1", "tx-alice-2", "tx-alice-3"}, cache.getHashesForSender("alice")) | ||
| require.Equal(t, []string{"tx-bob-1", "tx-bob-2"}, cache.getHashesForSender("bob")) | ||
| require.True(t, cache.areInternalMapsConsistent()) | ||
| cache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) | ||
|
|
||
| accountsProvider := &txcachemocks.AccountNonceAndBalanceProviderMock{ | ||
| GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { | ||
| return 1, big.NewInt(3 * 1500000 * oneBillion), true, nil | ||
| }, | ||
| } | ||
|
|
||
| cache.AddTx(createTx([]byte("tx-alice-1"), "alice", 1).withGasLimit(50000)) | ||
| cache.AddTx(createTx([]byte("tx-alice-2"), "alice", 2).withGasLimit(1500000)) | ||
| cache.AddTx(createTx([]byte("tx-alice-4"), "alice", 3).withGasLimit(1500000)) | ||
| require.Equal(t, []string{"tx-alice-1", "tx-alice-2", "tx-alice-4"}, cache.getHashesForSender("alice")) | ||
| require.True(t, cache.areInternalMapsConsistent()) | ||
|
|
||
| err := cache.OnProposedBlock( | ||
| []byte("blockHash1"), | ||
| &block.Body{ | ||
| MiniBlocks: []*block.MiniBlock{ | ||
| { | ||
| TxHashes: [][]byte{ | ||
| []byte("tx-alice-1"), | ||
| []byte("tx-alice-2"), | ||
| []byte("tx-alice-4"), | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| &block.Header{ | ||
| Nonce: 1, | ||
| PrevHash: []byte("blockHash0"), | ||
| }, | ||
| accountsProvider, | ||
| []byte("blockHash0"), | ||
| ) | ||
| require.Nil(t, err) | ||
|
|
||
| // TODO analyze if this behaviour is ok. Even if the limit of txs per sender is exceeded, no tx is removed because the specific nonce is tracked. | ||
| cache.AddTx(createTx([]byte("tx-alice-3"), "alice", 3).withGasLimit(1500000)) | ||
| require.Equal(t, []string{"tx-alice-1", "tx-alice-2", "tx-alice-3", "tx-alice-4"}, cache.getHashesForSender("alice")) | ||
|
Contributor
Author
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. because the nonce=3 is tracked for alice, no tx is removed from the list. |
||
| require.True(t, cache.areInternalMapsConsistent()) | ||
|
|
||
| err = cache.OnExecutedBlock( | ||
| &block.Header{ | ||
| Nonce: 1, | ||
| PrevHash: []byte("blockHash0"), | ||
| }) | ||
| require.Nil(t, err) | ||
|
|
||
| cache.AddTx(createTx([]byte("tx-alice-5"), "alice", 3).withGasLimit(1500000)) | ||
| require.Equal(t, []string{"tx-alice-1", "tx-alice-2", "tx-alice-3"}, cache.getHashesForSender("alice")) | ||
| require.True(t, cache.areInternalMapsConsistent()) | ||
| }) | ||
| } | ||
|
|
||
| func Test_AddTx_AppliesSizeConstraintsPerSenderForNumBytes(t *testing.T) { | ||
|
|
@@ -490,7 +550,7 @@ func TestTxCache_TransactionIsAdded_EvenWhenInternalMapsAreInconsistent(t *testi | |
| cache.Clear() | ||
|
|
||
| // Setup inconsistency: transaction already exists in map by sender, but not in map by hash | ||
| cache.txListBySender.addTxReturnEvicted(createTx([]byte("alice-x"), "alice", 42)) | ||
| cache.txListBySender.addTxReturnEvicted(createTx([]byte("alice-x"), "alice", 42), cache.tracker) | ||
|
|
||
| require.False(t, cache.Has([]byte("alice-x"))) | ||
| ok, added = cache.AddTx(createTx([]byte("alice-x"), "alice", 42)) | ||
|
|
||
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.
remove only if the transaction is not tracked