Skip to content

Commit

Permalink
mempool: Optimize orphan map limiting.
Browse files Browse the repository at this point in the history
This optimizes the way in which the mempool oprhan map is limited in the
same way the server block manager maps were previously optimized.

Previously the code would read a cryptographically random value large
enough to construct a hash, find the first entry larger than that value,
and evict it.

That approach is quite inefficient and could easily become a
bottleneck when processing transactions due to the need to read from a
source such as /dev/urandom and all of the subsequent hash comparisons.

Luckily, strong cryptographic randomness is not needed here. The primary
intent of limiting the maps is to control memory usage with a secondary
concern of making it difficult for adversaries to force eviction of
specific entries.

Consequently, this changes the code to make use of the pseudorandom
iteration order of Go's maps along with the preimage resistance of the
hashing function to provide the desired functionality.  It has
previously been discussed that the specific pseudorandom iteration order
is not guaranteed by the Go spec even though in practice that is how it
is implemented.  This is not a concern however because even if the
specific compiler doesn't implement that, the preimage resistance of the
hashing function alone is enough.

The following is a before and after comparison of the function for both
speed and memory allocations:

benchmark                    old ns/op     new ns/op     delta
----------------------------------------------------------------
BenchmarkLimitNumOrphans     3727          243           -93.48%

benchmark                    old allocs    new allocs    delta
-----------------------------------------------------------------
BenchmarkLimitNumOrphans     4             0             -100.00%
  • Loading branch information
davecgh committed Oct 24, 2016
1 parent e606259 commit 0e71867
Showing 1 changed file with 17 additions and 30 deletions.
47 changes: 17 additions & 30 deletions mempool/mempool.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ package mempool

import (
"container/list"
"crypto/rand"
"fmt"
"math"
"math/big"
"sync"
"sync/atomic"
"time"
Expand Down Expand Up @@ -182,35 +180,19 @@ func (mp *TxPool) RemoveOrphan(txHash *chainhash.Hash) {
//
// This function MUST be called with the mempool lock held (for writes).
func (mp *TxPool) limitNumOrphans() error {
if len(mp.orphans)+1 > mp.cfg.Policy.MaxOrphanTxs &&
mp.cfg.Policy.MaxOrphanTxs > 0 {

// Generate a cryptographically random hash.
randHashBytes := make([]byte, chainhash.HashSize)
_, err := rand.Read(randHashBytes)
if err != nil {
return err
}
randHashNum := new(big.Int).SetBytes(randHashBytes)

// Try to find the first entry that is greater than the random
// hash. Use the first entry (which is already pseudorandom due
// to Go's range statement over maps) as a fallback if none of
// the hashes in the orphan pool are larger than the random
// hash.
var foundHash *chainhash.Hash
for txHash := range mp.orphans {
if foundHash == nil {
foundHash = &txHash
}
txHashNum := blockchain.HashToBig(&txHash)
if txHashNum.Cmp(randHashNum) > 0 {
foundHash = &txHash
break
}
}
if len(mp.orphans)+1 <= mp.cfg.Policy.MaxOrphanTxs {
return nil
}

mp.removeOrphan(foundHash)
// Remove a random entry from the map. For most compilers, Go's
// range statement iterates starting at a random item although
// that is not 100% guaranteed by the spec. The iteration order
// is not important here because an adversary would have to be
// able to pull off preimage attacks on the hashing function in
// order to target eviction of specific entries anyways.
for txHash := range mp.orphans {
mp.removeOrphan(&txHash)
break
}

return nil
Expand All @@ -220,6 +202,11 @@ func (mp *TxPool) limitNumOrphans() error {
//
// This function MUST be called with the mempool lock held (for writes).
func (mp *TxPool) addOrphan(tx *btcutil.Tx) {
// Nothing to do if no orphans are allowed.
if mp.cfg.Policy.MaxOrphanTxs <= 0 {
return
}

// Limit the number orphan transactions to prevent memory exhaustion. A
// random orphan is evicted to make room if needed.
mp.limitNumOrphans()
Expand Down

0 comments on commit 0e71867

Please sign in to comment.