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

Handle consumed balance #59

Merged
merged 5 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions testscommon/txcachemocks/selectionSessionMock.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import (
type SelectionSessionMock struct {
mutex sync.Mutex

AccountStateByAddress map[string]*types.AccountState
GetAccountStateCalled func(address []byte) (*types.AccountState, error)
IsBadlyGuardedCalled func(tx data.TransactionHandler) bool
AccountStateByAddress map[string]*types.AccountState
GetAccountStateCalled func(address []byte) (*types.AccountState, error)
IsBadlyGuardedCalled func(tx data.TransactionHandler) bool
GetTransferredValueCalled func(tx data.TransactionHandler) *big.Int
}

// NewSelectionSessionMock -
Expand Down Expand Up @@ -78,6 +79,15 @@ func (mock *SelectionSessionMock) IsBadlyGuarded(tx data.TransactionHandler) boo
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
Expand Down
1 change: 1 addition & 0 deletions txcache/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type TxGasHandler interface {
type SelectionSession interface {
GetAccountState(accountKey []byte) (*types.AccountState, error)
IsBadlyGuarded(tx data.TransactionHandler) bool
GetTransferredValue(tx data.TransactionHandler) *big.Int
IsInterfaceNil() bool
}

Expand Down
2 changes: 1 addition & 1 deletion txcache/selection.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
7 changes: 7 additions & 0 deletions txcache/testutils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package txcache

import (
"encoding/binary"
"math/big"
"math/rand"
"sync"
"time"
Expand Down Expand Up @@ -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))
Expand Down
26 changes: 15 additions & 11 deletions txcache/transactionsHeapItem.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type transactionsHeapItem struct {
latestSelectedTransaction *WrappedTransaction
latestSelectedTransactionNonce uint64

accumulatedFee *big.Int
consumedBalance *big.Int
}

func newTransactionsHeapItem(bunch bunchOfTransactions) (*transactionsHeapItem, error) {
Expand All @@ -40,26 +40,29 @@ 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

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 {
Expand Down Expand Up @@ -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
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don't detect invalid transactions. We always assume that the value to be transferred is actually transferred (even if it isn't, actually, during processing).

We'll stop selecting transactions from a given sender once we detect (under the assumption above) that she doesn't have enough balance to pay the fee for the "currentTransaction".

if willFeeExceedBalance {
logSelect.Trace("transactionsHeapItem.detectWillFeeExceedBalance",
"tx", item.currentTransaction.TxHash,
"sender", item.sender,
"balance", item.senderState.Balance,
"accumulatedFee", item.accumulatedFee,
"consumedBalance", item.consumedBalance,
)
}

Expand Down
62 changes: 52 additions & 10 deletions txcache/transactionsHeapItem_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)

Expand All @@ -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())
})
}
Expand Down