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

core, txpool: less allocations when handling transactions #21232

Merged
merged 8 commits into from
Jul 1, 2020
Prev Previous commit
Next Next commit
core, crypto: various allocation savings regarding tx handling
  • Loading branch information
holiman authored and MariusVanDerWijden committed Jul 1, 2020
commit 90a2f2de7ca1cf952f59c995720369e1c682fe33
10 changes: 5 additions & 5 deletions common/math/integer.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package math

import (
"fmt"
"math/bits"
"strconv"
)

Expand Down Expand Up @@ -87,13 +88,12 @@ func SafeSub(x, y uint64) (uint64, bool) {

// SafeAdd returns the result and whether overflow occurred.
func SafeAdd(x, y uint64) (uint64, bool) {
return x + y, y > MaxUint64-x
sum, carry := bits.Add64(x, y, 0)
return sum, carry != 0
}

// SafeMul returns multiplication result and whether overflow occurred.
func SafeMul(x, y uint64) (uint64, bool) {
if x == 0 || y == 0 {
return 0, false
}
return x * y, y > MaxUint64/x
hi, lo := bits.Mul64(x, y)
return lo, hi != 0
}
37 changes: 29 additions & 8 deletions core/tx_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,7 @@ func (m *txSortedMap) Len() int {
return len(m.items)
}

// Flatten creates a nonce-sorted slice of transactions based on the loosely
// sorted internal representation. The result of the sorting is cached in case
// it's requested again before any modifications are made to the contents.
func (m *txSortedMap) Flatten() types.Transactions {
func (m *txSortedMap) flatten() types.Transactions {
// If the sorting was not cached yet, create and cache it
if m.cache == nil {
m.cache = make(types.Transactions, 0, len(m.items))
Expand All @@ -209,12 +206,27 @@ func (m *txSortedMap) Flatten() types.Transactions {
}
sort.Sort(types.TxByNonce(m.cache))
}
return m.cache
}

// Flatten creates a nonce-sorted slice of transactions based on the loosely
// sorted internal representation. The result of the sorting is cached in case
// it's requested again before any modifications are made to the contents.
func (m *txSortedMap) Flatten() types.Transactions {
// Copy the cache to prevent accidental modifications
txs := make(types.Transactions, len(m.cache))
cache := m.flatten()
txs := make(types.Transactions, len(cache))
copy(txs, m.cache)
return txs
}

// LastElement returns the last element of a flattened list, thus, the
// transaction with the highest nonce
func (m *txSortedMap) LastElement() *types.Transaction {
cache := m.flatten()
return cache[len(m.cache)-1]
}

// txList is a "list" of transactions belonging to an account, sorted by account
// nonce. The same type can be used both for storing contiguous transactions for
// the executable/pending queue; and for storing gapped transactions for the non-
Expand Down Expand Up @@ -259,11 +271,11 @@ func (l *txList) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Tran
return false, nil
}
}
cost, overflow := tx.Cost()
cost, overflow := tx.CostU64()
if overflow {
log.Warn("transaction cost overflown, txHash: %v txCost: %v", tx.Hash(), cost)
return false, nil
}

// Otherwise overwrite the old transaction with the current one
l.txs.Put(tx)
if l.costcap < cost {
Expand Down Expand Up @@ -300,7 +312,10 @@ func (l *txList) Filter(costLimit uint64, gasLimit uint64) (types.Transactions,
l.gascap = gasLimit

// Filter out all the transactions above the account's funds
removed := l.txs.Filter(func(tx *types.Transaction) bool { cost, _ := tx.Cost(); return cost > costLimit || tx.Gas() > gasLimit })
removed := l.txs.Filter(func(tx *types.Transaction) bool {
cost, _ := tx.CostU64()
return cost > costLimit || tx.Gas() > gasLimit
})

// If the list was strict, filter anything above the lowest nonce
var invalids types.Transactions
Expand Down Expand Up @@ -367,6 +382,12 @@ func (l *txList) Flatten() types.Transactions {
return l.txs.Flatten()
}

// LastElement returns the last element of a flattened list, thus, the
// transaction with the highest nonce
func (l *txList) LastElement() *types.Transaction {
return l.txs.LastElement()
}

// priceHeap is a heap.Interface implementation over transactions for retrieving
// price-sorted transactions to discard when the pool fills up.
type priceHeap []*types.Transaction
Expand Down
11 changes: 4 additions & 7 deletions core/tx_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -543,12 +543,9 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
}
// Transactor should have enough funds to cover the costs
// cost == V + GP * GL
if !pool.currentState.GetBalance(from).IsUint64() {
return ErrGasUintOverflow
}
cost, overflow := tx.Cost()
cost, overflow := tx.CostU64()
if overflow {
return ErrGasUintOverflow
return ErrInsufficientFunds
}
if pool.currentState.GetBalance(from).Uint64() < cost {
return ErrInsufficientFunds
Expand Down Expand Up @@ -1066,8 +1063,8 @@ func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirt

// Update all accounts to the latest known pending nonce
for addr, list := range pool.pending {
txs := list.Flatten() // Heavy but will be cached and is needed by the miner anyway
pool.pendingNonces.set(addr, txs[len(txs)-1].Nonce()+1)
highestPending := list.LastElement()
pool.pendingNonces.set(addr, highestPending.Nonce()+1)
}
pool.mu.Unlock()

Expand Down
11 changes: 10 additions & 1 deletion core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,16 @@ func (tx *Transaction) WithSignature(signer Signer, sig []byte) (*Transaction, e
}

// Cost returns amount + gasprice * gaslimit.
func (tx *Transaction) Cost() (uint64, bool) {
func (tx *Transaction) Cost() *big.Int {
total := new(big.Int).Mul(tx.data.Price, new(big.Int).SetUint64(tx.data.GasLimit))
total.Add(total, tx.data.Amount)
return total
}

func (tx *Transaction) CostU64() (uint64, bool) {
if tx.data.Price.BitLen() > 63 || tx.data.Amount.BitLen() > 63 {
return 0, false
}
cost, overflowMul := math.SafeMul(tx.data.Price.Uint64(), tx.data.GasLimit)
total, overflowAdd := math.SafeAdd(cost, tx.data.Amount.Uint64())
return total, overflowMul || overflowAdd
Expand Down