diff --git a/testscommon/txcachemocks/selectionSessionMock.go b/testscommon/txcachemocks/selectionSessionMock.go index db789b51..537584d1 100644 --- a/testscommon/txcachemocks/selectionSessionMock.go +++ b/testscommon/txcachemocks/selectionSessionMock.go @@ -15,6 +15,7 @@ type SelectionSessionMock struct { AccountStateByAddress map[string]*types.AccountState GetAccountStateCalled func(address []byte) (*types.AccountState, error) IsIncorrectlyGuardedCalled func(tx data.TransactionHandler) bool + GetTransferredValueCalled func(tx data.TransactionHandler) *big.Int } // NewSelectionSessionMock - @@ -78,6 +79,15 @@ func (mock *SelectionSessionMock) IsIncorrectlyGuarded(tx data.TransactionHandle return false } +// GetTransferredValue - +func (mock *SelectionSessionMock) GetTransferredValue(tx data.TransactionHandler) *big.Int { + if mock.GetTransferredValueCalled != nil { + return mock.GetTransferredValueCalled(tx) + } + + return tx.GetValue() +} + // IsInterfaceNil - func (mock *SelectionSessionMock) IsInterfaceNil() bool { return mock == nil diff --git a/txcache/interface.go b/txcache/interface.go index a77871fa..45cb1a49 100644 --- a/txcache/interface.go +++ b/txcache/interface.go @@ -17,6 +17,7 @@ type TxGasHandler interface { type SelectionSession interface { GetAccountState(accountKey []byte) (*types.AccountState, error) IsIncorrectlyGuarded(tx data.TransactionHandler) bool + GetTransferredValue(tx data.TransactionHandler) *big.Int IsInterfaceNil() bool } diff --git a/txcache/selection.go b/txcache/selection.go index f889a3a9..d495c7ba 100644 --- a/txcache/selection.go +++ b/txcache/selection.go @@ -80,7 +80,7 @@ func selectTransactionsFromBunches(session SelectionSession, bunches []bunchOfTr shouldSkipTransaction := detectSkippableTransaction(session, item) if !shouldSkipTransaction { accumulatedGas += gasLimit - selectedTransactions = append(selectedTransactions, item.selectCurrentTransaction()) + selectedTransactions = append(selectedTransactions, item.selectCurrentTransaction(session)) } // If there are more transactions in the same bunch (same sender as the popped item), diff --git a/txcache/testutils_test.go b/txcache/testutils_test.go index 165818be..2592a834 100644 --- a/txcache/testutils_test.go +++ b/txcache/testutils_test.go @@ -2,6 +2,7 @@ package txcache import ( "encoding/binary" + "math/big" "math/rand" "sync" "time" @@ -181,6 +182,12 @@ func (wrappedTx *WrappedTransaction) withGasLimit(gasLimit uint64) *WrappedTrans return wrappedTx } +func (wrappedTx *WrappedTransaction) withValue(value *big.Int) *WrappedTransaction { + tx := wrappedTx.Tx.(*transaction.Transaction) + tx.Value = value + return wrappedTx +} + func createFakeSenderAddress(senderTag int) []byte { bytes := make([]byte, 32) binary.LittleEndian.PutUint64(bytes, uint64(senderTag)) diff --git a/txcache/transactionsHeapItem.go b/txcache/transactionsHeapItem.go index 6b72cb79..97ce431a 100644 --- a/txcache/transactionsHeapItem.go +++ b/txcache/transactionsHeapItem.go @@ -19,7 +19,7 @@ type transactionsHeapItem struct { latestSelectedTransaction *WrappedTransaction latestSelectedTransactionNonce uint64 - accumulatedFee *big.Int + consumedBalance *big.Int } func newTransactionsHeapItem(bunch bunchOfTransactions) (*transactionsHeapItem, error) { @@ -40,12 +40,12 @@ func newTransactionsHeapItem(bunch bunchOfTransactions) (*transactionsHeapItem, currentTransactionNonce: firstTransaction.Tx.GetNonce(), latestSelectedTransaction: nil, - accumulatedFee: big.NewInt(0), + consumedBalance: big.NewInt(0), }, nil } -func (item *transactionsHeapItem) selectCurrentTransaction() *WrappedTransaction { - item.accumulateFee() +func (item *transactionsHeapItem) selectCurrentTransaction(session SelectionSession) *WrappedTransaction { + item.accumulateConsumedBalance(session) item.latestSelectedTransaction = item.currentTransaction item.latestSelectedTransactionNonce = item.currentTransactionNonce @@ -53,13 +53,16 @@ func (item *transactionsHeapItem) selectCurrentTransaction() *WrappedTransaction return item.currentTransaction } -func (item *transactionsHeapItem) accumulateFee() { +func (item *transactionsHeapItem) accumulateConsumedBalance(session SelectionSession) { fee := item.currentTransaction.Fee - if fee == nil { - return + if fee != nil { + item.consumedBalance.Add(item.consumedBalance, fee) } - item.accumulatedFee.Add(item.accumulatedFee, fee) + transferredValue := session.GetTransferredValue(item.currentTransaction.Tx) + if transferredValue != nil { + item.consumedBalance.Add(item.consumedBalance, transferredValue) + } } func (item *transactionsHeapItem) gotoNextTransaction() bool { @@ -123,16 +126,17 @@ func (item *transactionsHeapItem) detectWillFeeExceedBalance() bool { return false } - futureAccumulatedFee := new(big.Int).Add(item.accumulatedFee, fee) + // Here, we are not interested into an eventual transfer of value (we only check if there's enough balance to pay the transaction fee). + futureConsumedBalance := new(big.Int).Add(item.consumedBalance, fee) senderBalance := item.senderState.Balance - willFeeExceedBalance := futureAccumulatedFee.Cmp(senderBalance) > 0 + willFeeExceedBalance := futureConsumedBalance.Cmp(senderBalance) > 0 if willFeeExceedBalance { logSelect.Trace("transactionsHeapItem.detectWillFeeExceedBalance", "tx", item.currentTransaction.TxHash, "sender", item.sender, "balance", item.senderState.Balance, - "accumulatedFee", item.accumulatedFee, + "consumedBalance", item.consumedBalance, ) } diff --git a/txcache/transactionsHeapItem_test.go b/txcache/transactionsHeapItem_test.go index 0aeafe44..a1868cb3 100644 --- a/txcache/transactionsHeapItem_test.go +++ b/txcache/transactionsHeapItem_test.go @@ -33,12 +33,13 @@ func TestNewTransactionsHeapItem(t *testing.T) { require.Equal(t, bunch[0], item.currentTransaction) require.Equal(t, uint64(42), item.currentTransactionNonce) require.Nil(t, item.latestSelectedTransaction) - require.Equal(t, big.NewInt(0), item.accumulatedFee) + require.Equal(t, big.NewInt(0), item.consumedBalance) }) } func TestTransactionsHeapItem_selectTransaction(t *testing.T) { txGasHandler := txcachemocks.NewTxGasHandlerMock() + session := txcachemocks.NewSelectionSessionMock() a := createTx([]byte("tx-1"), "alice", 42) b := createTx([]byte("tx-2"), "alice", 43) @@ -48,20 +49,20 @@ func TestTransactionsHeapItem_selectTransaction(t *testing.T) { item, err := newTransactionsHeapItem(bunchOfTransactions{a, b}) require.NoError(t, err) - selected := item.selectCurrentTransaction() + selected := item.selectCurrentTransaction(session) require.Equal(t, a, selected) require.Equal(t, a, item.latestSelectedTransaction) require.Equal(t, 42, int(item.latestSelectedTransactionNonce)) - require.Equal(t, "50000000000000", item.accumulatedFee.String()) + require.Equal(t, "50000000000000", item.consumedBalance.String()) ok := item.gotoNextTransaction() require.True(t, ok) - selected = item.selectCurrentTransaction() + selected = item.selectCurrentTransaction(session) require.Equal(t, b, selected) require.Equal(t, b, item.latestSelectedTransaction) require.Equal(t, 43, int(item.latestSelectedTransactionNonce)) - require.Equal(t, "100000000000000", item.accumulatedFee.String()) + require.Equal(t, "100000000000000", item.consumedBalance.String()) ok = item.gotoNextTransaction() require.False(t, ok) @@ -133,22 +134,29 @@ func TestTransactionsHeapItem_detectMiddleGap(t *testing.T) { }) } -func TestTransactionsHeapItem_detectFeeExceededBalance(t *testing.T) { +func TestTransactionsHeapItem_detectWillFeeExceedBalance(t *testing.T) { txGasHandler := txcachemocks.NewTxGasHandlerMock() a := createTx([]byte("tx-1"), "alice", 42) b := createTx([]byte("tx-2"), "alice", 43) + c := createTx([]byte("tx-3"), "alice", 44).withValue(big.NewInt(1000000000000000000)) + d := createTx([]byte("tx-4"), "alice", 45) + a.precomputeFields(txGasHandler) b.precomputeFields(txGasHandler) + c.precomputeFields(txGasHandler) + d.precomputeFields(txGasHandler) t.Run("unknown", func(t *testing.T) { item, err := newTransactionsHeapItem(bunchOfTransactions{a, b}) - require.NoError(t, err) + require.NoError(t, err) require.False(t, item.detectWillFeeExceedBalance()) }) - t.Run("known, not exceeded, then exceeded", func(t *testing.T) { + t.Run("known, not exceeded, then exceeded (a)", func(t *testing.T) { + session := txcachemocks.NewSelectionSessionMock() + item, err := newTransactionsHeapItem(bunchOfTransactions{a, b}) require.NoError(t, err) @@ -158,10 +166,44 @@ func TestTransactionsHeapItem_detectFeeExceededBalance(t *testing.T) { require.False(t, item.detectWillFeeExceedBalance()) - _ = item.selectCurrentTransaction() + _ = item.selectCurrentTransaction(session) + _ = item.gotoNextTransaction() + + require.Equal(t, "50000000000000", item.consumedBalance.String()) + require.True(t, item.detectWillFeeExceedBalance()) + }) + + t.Run("known, not exceeded, then exceeded (b)", func(t *testing.T) { + session := txcachemocks.NewSelectionSessionMock() + + item, err := newTransactionsHeapItem(bunchOfTransactions{a, b, c, d}) + require.NoError(t, err) + + item.senderState = &types.AccountState{ + Balance: big.NewInt(1000000000000000000 + 2*50000000000000 + 1), + } + + require.False(t, item.detectWillFeeExceedBalance()) + + // Select "a", move to "b". + _ = item.selectCurrentTransaction(session) + _ = item.gotoNextTransaction() + + require.Equal(t, "50000000000000", item.consumedBalance.String()) + require.False(t, item.detectWillFeeExceedBalance()) + + // Select "b", move to "c". + _ = item.selectCurrentTransaction(session) + _ = item.gotoNextTransaction() + + require.Equal(t, "100000000000000", item.consumedBalance.String()) + require.False(t, item.detectWillFeeExceedBalance()) + + // Select "c", move to "d". + _ = item.selectCurrentTransaction(session) _ = item.gotoNextTransaction() - require.Equal(t, "50000000000000", item.accumulatedFee.String()) + require.Equal(t, "1000150000000000000", item.consumedBalance.String()) require.True(t, item.detectWillFeeExceedBalance()) }) }