core: check for gas limit exceeding txs too on new block

pull/14547/head
Péter Szilágyi 8 years ago
parent dd06c85843
commit 280609c99b
No known key found for this signature in database
GPG Key ID: E9AE538CEDF8293D
  1. 40
      core/tx_list.go
  2. 12
      core/tx_pool.go
  3. 51
      core/tx_pool_test.go

@ -220,9 +220,11 @@ func (m *txSortedMap) Flatten() types.Transactions {
// the executable/pending queue; and for storing gapped transactions for the non- // the executable/pending queue; and for storing gapped transactions for the non-
// executable/future queue, with minor behavioral changes. // executable/future queue, with minor behavioral changes.
type txList struct { type txList struct {
strict bool // Whether nonces are strictly continuous or not strict bool // Whether nonces are strictly continuous or not
txs *txSortedMap // Heap indexed sorted hash map of the transactions txs *txSortedMap // Heap indexed sorted hash map of the transactions
costcap *big.Int // Price of the highest costing transaction (reset only if exceeds balance)
costcap *big.Int // Price of the highest costing transaction (reset only if exceeds balance)
gascap *big.Int // Gas limit of the highest spending transaction (reset only if exceeds block limit)
} }
// newTxList create a new transaction list for maintaining nonce-indexable fast, // newTxList create a new transaction list for maintaining nonce-indexable fast,
@ -232,6 +234,7 @@ func newTxList(strict bool) *txList {
strict: strict, strict: strict,
txs: newTxSortedMap(), txs: newTxSortedMap(),
costcap: new(big.Int), costcap: new(big.Int),
gascap: new(big.Int),
} }
} }
@ -244,8 +247,8 @@ func (l *txList) Overlaps(tx *types.Transaction) bool {
// Add tries to insert a new transaction into the list, returning whether the // Add tries to insert a new transaction into the list, returning whether the
// transaction was accepted, and if yes, any previous transaction it replaced. // transaction was accepted, and if yes, any previous transaction it replaced.
// //
// If the new transaction is accepted into the list, the lists' cost threshold // If the new transaction is accepted into the list, the lists' cost and gas
// is also potentially updated. // thresholds are also potentially updated.
func (l *txList) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Transaction) { func (l *txList) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Transaction) {
// If there's an older better transaction, abort // If there's an older better transaction, abort
old := l.txs.Get(tx.Nonce()) old := l.txs.Get(tx.Nonce())
@ -260,6 +263,9 @@ func (l *txList) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Tran
if cost := tx.Cost(); l.costcap.Cmp(cost) < 0 { if cost := tx.Cost(); l.costcap.Cmp(cost) < 0 {
l.costcap = cost l.costcap = cost
} }
if gas := tx.Gas(); l.gascap.Cmp(gas) < 0 {
l.gascap = gas
}
return true, old return true, old
} }
@ -270,23 +276,25 @@ func (l *txList) Forward(threshold uint64) types.Transactions {
return l.txs.Forward(threshold) return l.txs.Forward(threshold)
} }
// Filter removes all transactions from the list with a cost higher than the // Filter removes all transactions from the list with a cost or gas limit higher
// provided threshold. Every removed transaction is returned for any post-removal // than the provided thresholds. Every removed transaction is returned for any
// maintenance. Strict-mode invalidated transactions are also returned. // post-removal maintenance. Strict-mode invalidated transactions are also
// returned.
// //
// This method uses the cached costcap to quickly decide if there's even a point // This method uses the cached costcap and gascap to quickly decide if there's even
// in calculating all the costs or if the balance covers all. If the threshold is // a point in calculating all the costs or if the balance covers all. If the threshold
// lower than the costcap, the costcap will be reset to a new high after removing // is lower than the costgas cap, the caps will be reset to a new high after removing
// expensive the too transactions. // the newly invalidated transactions.
func (l *txList) Filter(threshold *big.Int) (types.Transactions, types.Transactions) { func (l *txList) Filter(costLimit, gasLimit *big.Int) (types.Transactions, types.Transactions) {
// If all transactions are below the threshold, short circuit // If all transactions are below the threshold, short circuit
if l.costcap.Cmp(threshold) <= 0 { if l.costcap.Cmp(costLimit) <= 0 && l.gascap.Cmp(gasLimit) <= 0 {
return nil, nil return nil, nil
} }
l.costcap = new(big.Int).Set(threshold) // Lower the cap to the threshold l.costcap = new(big.Int).Set(costLimit) // Lower the caps to the thresholds
l.gascap = new(big.Int).Set(gasLimit)
// Filter out all the transactions above the account's funds // Filter out all the transactions above the account's funds
removed := l.txs.Filter(func(tx *types.Transaction) bool { return tx.Cost().Cmp(threshold) > 0 }) removed := l.txs.Filter(func(tx *types.Transaction) bool { return tx.Cost().Cmp(costLimit) > 0 || tx.Gas().Cmp(gasLimit) > 0 })
// If the list was strict, filter anything above the lowest nonce // If the list was strict, filter anything above the lowest nonce
var invalids types.Transactions var invalids types.Transactions

@ -663,6 +663,8 @@ func (pool *TxPool) removeTx(hash common.Hash) {
// future queue to the set of pending transactions. During this process, all // future queue to the set of pending transactions. During this process, all
// invalidated transactions (low nonce, low balance) are deleted. // invalidated transactions (low nonce, low balance) are deleted.
func (pool *TxPool) promoteExecutables(state *state.StateDB) { func (pool *TxPool) promoteExecutables(state *state.StateDB) {
gaslimit := pool.gasLimit()
// Iterate over all accounts and promote any executable transactions // Iterate over all accounts and promote any executable transactions
queued := uint64(0) queued := uint64(0)
for addr, list := range pool.queue { for addr, list := range pool.queue {
@ -673,8 +675,8 @@ func (pool *TxPool) promoteExecutables(state *state.StateDB) {
delete(pool.all, hash) delete(pool.all, hash)
pool.priced.Removed() pool.priced.Removed()
} }
// Drop all transactions that are too costly (low balance) // Drop all transactions that are too costly (low balance or out of gas)
drops, _ := list.Filter(state.GetBalance(addr)) drops, _ := list.Filter(state.GetBalance(addr), gaslimit)
for _, tx := range drops { for _, tx := range drops {
hash := tx.Hash() hash := tx.Hash()
log.Trace("Removed unpayable queued transaction", "hash", hash) log.Trace("Removed unpayable queued transaction", "hash", hash)
@ -798,6 +800,8 @@ func (pool *TxPool) promoteExecutables(state *state.StateDB) {
// executable/pending queue and any subsequent transactions that become unexecutable // executable/pending queue and any subsequent transactions that become unexecutable
// are moved back into the future queue. // are moved back into the future queue.
func (pool *TxPool) demoteUnexecutables(state *state.StateDB) { func (pool *TxPool) demoteUnexecutables(state *state.StateDB) {
gaslimit := pool.gasLimit()
// Iterate over all accounts and demote any non-executable transactions // Iterate over all accounts and demote any non-executable transactions
for addr, list := range pool.pending { for addr, list := range pool.pending {
nonce := state.GetNonce(addr) nonce := state.GetNonce(addr)
@ -809,8 +813,8 @@ func (pool *TxPool) demoteUnexecutables(state *state.StateDB) {
delete(pool.all, hash) delete(pool.all, hash)
pool.priced.Removed() pool.priced.Removed()
} }
// Drop all transactions that are too costly (low balance), and queue any invalids back for later // Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later
drops, invalids := list.Filter(state.GetBalance(addr)) drops, invalids := list.Filter(state.GetBalance(addr), gaslimit)
for _, tx := range drops { for _, tx := range drops {
hash := tx.Hash() hash := tx.Hash()
log.Trace("Removed unpayable pending transaction", "hash", hash) log.Trace("Removed unpayable pending transaction", "hash", hash)

@ -397,50 +397,79 @@ func TestTransactionDropping(t *testing.T) {
var ( var (
tx0 = transaction(0, big.NewInt(100), key) tx0 = transaction(0, big.NewInt(100), key)
tx1 = transaction(1, big.NewInt(200), key) tx1 = transaction(1, big.NewInt(200), key)
tx2 = transaction(2, big.NewInt(300), key)
tx10 = transaction(10, big.NewInt(100), key) tx10 = transaction(10, big.NewInt(100), key)
tx11 = transaction(11, big.NewInt(200), key) tx11 = transaction(11, big.NewInt(200), key)
tx12 = transaction(12, big.NewInt(300), key)
) )
pool.promoteTx(account, tx0.Hash(), tx0) pool.promoteTx(account, tx0.Hash(), tx0)
pool.promoteTx(account, tx1.Hash(), tx1) pool.promoteTx(account, tx1.Hash(), tx1)
pool.promoteTx(account, tx1.Hash(), tx2)
pool.enqueueTx(tx10.Hash(), tx10) pool.enqueueTx(tx10.Hash(), tx10)
pool.enqueueTx(tx11.Hash(), tx11) pool.enqueueTx(tx11.Hash(), tx11)
pool.enqueueTx(tx11.Hash(), tx12)
// Check that pre and post validations leave the pool as is // Check that pre and post validations leave the pool as is
if pool.pending[account].Len() != 2 { if pool.pending[account].Len() != 3 {
t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 2) t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3)
} }
if pool.queue[account].Len() != 2 { if pool.queue[account].Len() != 3 {
t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 2) t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3)
} }
if len(pool.all) != 4 { if len(pool.all) != 4 {
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), 4) t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), 4)
} }
pool.resetState() pool.resetState()
if pool.pending[account].Len() != 2 { if pool.pending[account].Len() != 3 {
t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 2) t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3)
} }
if pool.queue[account].Len() != 2 { if pool.queue[account].Len() != 3 {
t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 2) t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3)
} }
if len(pool.all) != 4 { if len(pool.all) != 4 {
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), 4) t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), 4)
} }
// Reduce the balance of the account, and check that invalidated transactions are dropped // Reduce the balance of the account, and check that invalidated transactions are dropped
state.AddBalance(account, big.NewInt(-750)) state.AddBalance(account, big.NewInt(-650))
pool.resetState() pool.resetState()
if _, ok := pool.pending[account].txs.items[tx0.Nonce()]; !ok { if _, ok := pool.pending[account].txs.items[tx0.Nonce()]; !ok {
t.Errorf("funded pending transaction missing: %v", tx0) t.Errorf("funded pending transaction missing: %v", tx0)
} }
if _, ok := pool.pending[account].txs.items[tx1.Nonce()]; ok { if _, ok := pool.pending[account].txs.items[tx1.Nonce()]; !ok {
t.Errorf("funded pending transaction missing: %v", tx0)
}
if _, ok := pool.pending[account].txs.items[tx2.Nonce()]; ok {
t.Errorf("out-of-fund pending transaction present: %v", tx1) t.Errorf("out-of-fund pending transaction present: %v", tx1)
} }
if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok { if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok {
t.Errorf("funded queued transaction missing: %v", tx10) t.Errorf("funded queued transaction missing: %v", tx10)
} }
if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; ok { if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; !ok {
t.Errorf("funded queued transaction missing: %v", tx10)
}
if _, ok := pool.queue[account].txs.items[tx12.Nonce()]; ok {
t.Errorf("out-of-fund queued transaction present: %v", tx11) t.Errorf("out-of-fund queued transaction present: %v", tx11)
} }
if len(pool.all) != 4 {
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), 4)
}
// Reduce the block gas limit, check that invalidated transactions are dropped
pool.gasLimit = func() *big.Int { return big.NewInt(100) }
pool.resetState()
if _, ok := pool.pending[account].txs.items[tx0.Nonce()]; !ok {
t.Errorf("funded pending transaction missing: %v", tx0)
}
if _, ok := pool.pending[account].txs.items[tx1.Nonce()]; ok {
t.Errorf("over-gased pending transaction present: %v", tx1)
}
if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok {
t.Errorf("funded queued transaction missing: %v", tx10)
}
if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; ok {
t.Errorf("over-gased queued transaction present: %v", tx11)
}
if len(pool.all) != 2 { if len(pool.all) != 2 {
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), 2) t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), 2)
} }

Loading…
Cancel
Save