diff --git a/core/tx_pool.go b/core/tx_pool.go index 16cd7076b02e778b294a4e435b823f66cc167781..60ae3d72f1d17791976cc9805b9fe01dd635e937 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -589,6 +589,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { // whitelisted, preventing any associated transaction from being dropped out of the pool // due to pricing constraints. func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err error) { + tx.SetReceivedTime(time.Now()) // If the transaction is already known, discard it hash := tx.Hash() if pool.all.Get(hash) != nil { diff --git a/core/types/transaction.go b/core/types/transaction.go index 00fb6feb740d67d0fa0e058f69d7398f72a82b90..f4673b5c3e6c3178b9272a82105afdcdf79312b4 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -22,6 +22,7 @@ import ( "io" "math/big" "sync/atomic" + "time" "github.com/holiman/uint256" @@ -39,6 +40,9 @@ var ( type Transaction struct { data txdata + + // Time when the transaction was added to the txPool + receivedTime *time.Time // caches hash atomic.Value size atomic.Value @@ -181,6 +185,13 @@ func (tx *Transaction) GasPriceIntCmp(other *uint256.Int) int { func (tx *Transaction) Value() *uint256.Int { return new(uint256.Int).Set(&tx.data.Amount) } func (tx *Transaction) Nonce() uint64 { return tx.data.AccountNonce } func (tx *Transaction) CheckNonce() bool { return true } +func (tx *Transaction) ReceivedTime() (time.Time, bool) { + if tx.receivedTime == nil { + return time.Time{}, false + } + + return *tx.receivedTime, true +} // To returns the recipient address of the transaction. // It returns nil if the transaction is a contract creation. @@ -272,6 +283,11 @@ func (tx *Transaction) RawSignatureValues() (v, r, s *uint256.Int) { return &tx.data.V, &tx.data.R, &tx.data.S } +// SetReceivedTime sets the time that this transaction was received at. +func (tx *Transaction) SetReceivedTime(time time.Time) { + tx.receivedTime = &time +} + // Transactions is a Transaction slice type for basic sorting. type Transactions []*Transaction @@ -314,19 +330,35 @@ func (s TxByNonce) Len() int { return len(s) } func (s TxByNonce) Less(i, j int) bool { return s[i].data.AccountNonce < s[j].data.AccountNonce } func (s TxByNonce) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -// TxByPrice implements both the sort and the heap interface, making it useful +// TxByPriceAndReceiveTime implements both the sort and the heap interface, making it useful // for all at once sorting as well as individually adding and removing elements. -type TxByPrice Transactions +type TxByPriceAndReceiveTime Transactions + +func (s TxByPriceAndReceiveTime) Len() int { return len(s) } +func (s TxByPriceAndReceiveTime) Less(i, j int) bool { + // If the price is equal, use the time the tx was received for deterministic sorting + if s[i].data.Price.Cmp(s[j].data.Price) == 0 { + recvI, ok := s[i].ReceivedTime() + if !ok { + return true + } -func (s TxByPrice) Len() int { return len(s) } -func (s TxByPrice) Less(i, j int) bool { return s[i].data.Price.Gt(&s[j].data.Price) } -func (s TxByPrice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + recvJ, ok := s[j].ReceivedTime() + if !ok { + return true + } + return recvI.UnixNano() < recvJ.UnixNano() + } + + return s[i].data.Price.Cmp(s[j].data.Price) > 0 +} +func (s TxByPriceAndReceiveTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s *TxByPrice) Push(x interface{}) { +func (s *TxByPriceAndReceiveTime) Push(x interface{}) { *s = append(*s, x.(*Transaction)) } -func (s *TxByPrice) Pop() interface{} { +func (s *TxByPriceAndReceiveTime) Pop() interface{} { old := *s n := len(old) x := old[n-1] @@ -339,7 +371,7 @@ func (s *TxByPrice) Pop() interface{} { // entire batches of transactions for non-executable accounts. type TransactionsByPriceAndNonce struct { txs map[common.Address]Transactions // Per account nonce-sorted list of transactions - heads TxByPrice // Next transaction for each unique account (price heap) + heads TxByPriceAndReceiveTime // Next transaction for each unique account (price heap) signer Signer // Signer for the set of transactions } @@ -349,8 +381,8 @@ type TransactionsByPriceAndNonce struct { // Note, the input map is reowned so the caller should not interact any more with // if after providing it to the constructor. func NewTransactionsByPriceAndNonce(signer Signer, txs map[common.Address]Transactions) *TransactionsByPriceAndNonce { - // Initialize a price based heap with the head transactions - heads := make(TxByPrice, 0, len(txs)) + // Initialize a price and received time based heap with the head transactions + heads := make(TxByPriceAndReceiveTime, 0, len(txs)) for from, accTxs := range txs { heads = append(heads, accTxs[0]) // Ensure the sender address is from the signer diff --git a/core/types/transaction_test.go b/core/types/transaction_test.go index d608a55765cd94db03e488d4b10d44be88d38414..4b5dd226a228be9ef7cb294a661a802c19d321c7 100644 --- a/core/types/transaction_test.go +++ b/core/types/transaction_test.go @@ -21,6 +21,7 @@ import ( "crypto/ecdsa" "encoding/json" "testing" + "time" "github.com/holiman/uint256" @@ -137,6 +138,7 @@ func TestTransactionPriceNonceSort(t *testing.T) { addr := crypto.PubkeyToAddress(key.PublicKey) for i := 0; i < 25; i++ { tx, _ := SignTx(NewTransaction(uint64(start+i), common.Address{}, uint256.NewInt().SetUint64(100), 100, uint256.NewInt().SetUint64(uint64(start+i)), nil), signer, key) + tx.SetReceivedTime(time.Unix(0, int64(start))) groups[addr] = append(groups[addr], tx) } } @@ -170,6 +172,11 @@ func TestTransactionPriceNonceSort(t *testing.T) { if fromi != fromNext && txi.GasPrice().Cmp(next.GasPrice()) < 0 { t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) < tx #%d (A=%x P=%v)", i, fromi[:4], txi.GasPrice(), i+1, fromNext[:4], next.GasPrice()) } + + // Make sure receivedTime order is ascending if the txs have the same gas price + if txi.GasPrice().Cmp(next.GasPrice()) == 0 && fromi != fromNext && txi.receivedTime.UnixNano() > next.receivedTime.UnixNano() { + t.Errorf("invalid received time ordering: tx #%d (A=%x T=%d) > tx #%d (A=%x T=%d)", i, fromi[:4], txi.receivedTime.UnixNano(), i+1, fromNext[:4], next.receivedTime.UnixNano()) + } } } }