mirror of
https://github.com/btcsuite/btcd.git
synced 2024-11-19 01:40:07 +01:00
mempool: Implement orphan expiration.
This implements orphan expiration in the mempool such that any orphans that have not had their ancestors materialize within 15 minutes of their initial arrival time will be evicted which in turn will remove any other orphans that attempted to redeem it. In order to perform the evictions with reasonable efficiency, an opportunistic scan interval of 5 minutes is used. That is to say that there is not a hard deadline on the scan interval and instead it runs when a new orphan is added to the pool if enough time has passed. The following is an example of running this code against the main network for around 30 minutes: 23:05:34 2016-10-24 [DBG] TXMP: Expired 3 orphans (remaining: 254) 23:10:38 2016-10-24 [DBG] TXMP: Expired 112 orphans (remaining: 231) 23:15:43 2016-10-24 [DBG] TXMP: Expired 95 orphans (remaining: 206) 23:20:44 2016-10-24 [DBG] TXMP: Expired 90 orphans (remaining: 191) 23:25:51 2016-10-24 [DBG] TXMP: Expired 71 orphans (remaining: 191) 23:30:55 2016-10-24 [DBG] TXMP: Expired 70 orphans (remaining: 105) 23:36:19 2016-10-24 [DBG] TXMP: Expired 55 orphans (remaining: 107) As can be seen from the above, without orphan expiration on this data set, the orphan pool would have grown an additional 496 entries.
This commit is contained in:
parent
c0def9d613
commit
e306158e25
@ -30,3 +30,12 @@ func DisableLog() {
|
|||||||
func UseLogger(logger btclog.Logger) {
|
func UseLogger(logger btclog.Logger) {
|
||||||
log = logger
|
log = logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pickNoun returns the singular or plural form of a noun depending
|
||||||
|
// on the count n.
|
||||||
|
func pickNoun(n int, singular, plural string) string {
|
||||||
|
if n == 1 {
|
||||||
|
return singular
|
||||||
|
}
|
||||||
|
return plural
|
||||||
|
}
|
||||||
|
@ -37,6 +37,15 @@ const (
|
|||||||
// mempoolHeight is the height used for the "block" height field of the
|
// mempoolHeight is the height used for the "block" height field of the
|
||||||
// contextual transaction information provided in a transaction view.
|
// contextual transaction information provided in a transaction view.
|
||||||
mempoolHeight = 0x7fffffff
|
mempoolHeight = 0x7fffffff
|
||||||
|
|
||||||
|
// orphanTTL is the maximum amount of time an orphan is allowed to
|
||||||
|
// stay in the orphan pool before it expires and is evicted during the
|
||||||
|
// next scan.
|
||||||
|
orphanTTL = time.Minute * 15
|
||||||
|
|
||||||
|
// orphanExpireScanInterval is the minimum amount of time in between
|
||||||
|
// scans of the orphan pool to evict expired transactions.
|
||||||
|
orphanExpireScanInterval = time.Minute * 5
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config is a descriptor containing the memory pool configuration.
|
// Config is a descriptor containing the memory pool configuration.
|
||||||
@ -116,6 +125,14 @@ type TxDesc struct {
|
|||||||
StartingPriority float64
|
StartingPriority float64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// orphanTx is normal transaction that references an ancestor transaction
|
||||||
|
// that is not yet available. It also contains additional information related
|
||||||
|
// to it such as an expiration time to help prevent caching the orphan forever.
|
||||||
|
type orphanTx struct {
|
||||||
|
tx *btcutil.Tx
|
||||||
|
expiration time.Time
|
||||||
|
}
|
||||||
|
|
||||||
// TxPool is used as a source of transactions that need to be mined into blocks
|
// TxPool is used as a source of transactions that need to be mined into blocks
|
||||||
// and relayed to other peers. It is safe for concurrent access from multiple
|
// and relayed to other peers. It is safe for concurrent access from multiple
|
||||||
// peers.
|
// peers.
|
||||||
@ -126,11 +143,17 @@ type TxPool struct {
|
|||||||
mtx sync.RWMutex
|
mtx sync.RWMutex
|
||||||
cfg Config
|
cfg Config
|
||||||
pool map[chainhash.Hash]*TxDesc
|
pool map[chainhash.Hash]*TxDesc
|
||||||
orphans map[chainhash.Hash]*btcutil.Tx
|
orphans map[chainhash.Hash]*orphanTx
|
||||||
orphansByPrev map[wire.OutPoint]map[chainhash.Hash]*btcutil.Tx
|
orphansByPrev map[wire.OutPoint]map[chainhash.Hash]*btcutil.Tx
|
||||||
outpoints map[wire.OutPoint]*btcutil.Tx
|
outpoints map[wire.OutPoint]*btcutil.Tx
|
||||||
pennyTotal float64 // exponentially decaying total for penny spends.
|
pennyTotal float64 // exponentially decaying total for penny spends.
|
||||||
lastPennyUnix int64 // unix time of last ``penny spend''
|
lastPennyUnix int64 // unix time of last ``penny spend''
|
||||||
|
|
||||||
|
// nextExpireScan is the time after which the orphan pool will be
|
||||||
|
// scanned in order to evict orphans. This is NOT a hard deadline as
|
||||||
|
// the scan will only run when an orphan is added to the pool as opposed
|
||||||
|
// to on an unconditional timer.
|
||||||
|
nextExpireScan time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the TxPool type implements the mining.TxSource interface.
|
// Ensure the TxPool type implements the mining.TxSource interface.
|
||||||
@ -143,13 +166,13 @@ var _ mining.TxSource = (*TxPool)(nil)
|
|||||||
func (mp *TxPool) removeOrphan(tx *btcutil.Tx, removeRedeemers bool) {
|
func (mp *TxPool) removeOrphan(tx *btcutil.Tx, removeRedeemers bool) {
|
||||||
// Nothing to do if passed tx is not an orphan.
|
// Nothing to do if passed tx is not an orphan.
|
||||||
txHash := tx.Hash()
|
txHash := tx.Hash()
|
||||||
tx, exists := mp.orphans[*txHash]
|
otx, exists := mp.orphans[*txHash]
|
||||||
if !exists {
|
if !exists {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the reference from the previous orphan index.
|
// Remove the reference from the previous orphan index.
|
||||||
for _, txIn := range tx.MsgTx().TxIn {
|
for _, txIn := range otx.tx.MsgTx().TxIn {
|
||||||
orphans, exists := mp.orphansByPrev[txIn.PreviousOutPoint]
|
orphans, exists := mp.orphansByPrev[txIn.PreviousOutPoint]
|
||||||
if exists {
|
if exists {
|
||||||
delete(orphans, *txHash)
|
delete(orphans, *txHash)
|
||||||
@ -192,6 +215,34 @@ func (mp *TxPool) RemoveOrphan(tx *btcutil.Tx) {
|
|||||||
//
|
//
|
||||||
// This function MUST be called with the mempool lock held (for writes).
|
// This function MUST be called with the mempool lock held (for writes).
|
||||||
func (mp *TxPool) limitNumOrphans() error {
|
func (mp *TxPool) limitNumOrphans() error {
|
||||||
|
// Scan through the orphan pool and remove any expired orphans when it's
|
||||||
|
// time. This is done for efficiency so the scan only happens
|
||||||
|
// periodically instead of on every orphan added to the pool.
|
||||||
|
if now := time.Now(); now.After(mp.nextExpireScan) {
|
||||||
|
origNumOrphans := len(mp.orphans)
|
||||||
|
for _, otx := range mp.orphans {
|
||||||
|
if now.After(otx.expiration) {
|
||||||
|
// Remove redeemers too because the missing
|
||||||
|
// parents are very unlikely to ever materialize
|
||||||
|
// since the orphan has already been around more
|
||||||
|
// than long enough for them to be delivered.
|
||||||
|
mp.removeOrphan(otx.tx, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set next expiration scan to occur after the scan interval.
|
||||||
|
mp.nextExpireScan = now.Add(orphanExpireScanInterval)
|
||||||
|
|
||||||
|
numOrphans := len(mp.orphans)
|
||||||
|
if numExpired := origNumOrphans - numOrphans; numExpired > 0 {
|
||||||
|
log.Debugf("Expired %d %s (remaining: %d)", numExpired,
|
||||||
|
pickNoun(numExpired, "orphan", "orphans"),
|
||||||
|
numOrphans)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing to do if adding another orphan will not cause the pool to
|
||||||
|
// exceed the limit.
|
||||||
if len(mp.orphans)+1 <= mp.cfg.Policy.MaxOrphanTxs {
|
if len(mp.orphans)+1 <= mp.cfg.Policy.MaxOrphanTxs {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -202,8 +253,10 @@ func (mp *TxPool) limitNumOrphans() error {
|
|||||||
// is not important here because an adversary would have to be
|
// is not important here because an adversary would have to be
|
||||||
// able to pull off preimage attacks on the hashing function in
|
// able to pull off preimage attacks on the hashing function in
|
||||||
// order to target eviction of specific entries anyways.
|
// order to target eviction of specific entries anyways.
|
||||||
for _, tx := range mp.orphans {
|
for _, otx := range mp.orphans {
|
||||||
mp.removeOrphan(tx, false)
|
// Don't remove redeemers in the case of a random eviction since
|
||||||
|
// it is quite possible it might be needed again shortly.
|
||||||
|
mp.removeOrphan(otx.tx, false)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,11 +272,15 @@ func (mp *TxPool) addOrphan(tx *btcutil.Tx) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Limit the number orphan transactions to prevent memory exhaustion. A
|
// Limit the number orphan transactions to prevent memory exhaustion.
|
||||||
// random orphan is evicted to make room if needed.
|
// This will periodically remove any expired orphans and evict a random
|
||||||
|
// orphan if space is still needed.
|
||||||
mp.limitNumOrphans()
|
mp.limitNumOrphans()
|
||||||
|
|
||||||
mp.orphans[*tx.Hash()] = tx
|
mp.orphans[*tx.Hash()] = &orphanTx{
|
||||||
|
tx: tx,
|
||||||
|
expiration: time.Now().Add(orphanTTL),
|
||||||
|
}
|
||||||
for _, txIn := range tx.MsgTx().TxIn {
|
for _, txIn := range tx.MsgTx().TxIn {
|
||||||
if _, exists := mp.orphansByPrev[txIn.PreviousOutPoint]; !exists {
|
if _, exists := mp.orphansByPrev[txIn.PreviousOutPoint]; !exists {
|
||||||
mp.orphansByPrev[txIn.PreviousOutPoint] =
|
mp.orphansByPrev[txIn.PreviousOutPoint] =
|
||||||
@ -1081,10 +1138,11 @@ func (mp *TxPool) LastUpdated() time.Time {
|
|||||||
// transactions until they are mined into a block.
|
// transactions until they are mined into a block.
|
||||||
func New(cfg *Config) *TxPool {
|
func New(cfg *Config) *TxPool {
|
||||||
return &TxPool{
|
return &TxPool{
|
||||||
cfg: *cfg,
|
cfg: *cfg,
|
||||||
pool: make(map[chainhash.Hash]*TxDesc),
|
pool: make(map[chainhash.Hash]*TxDesc),
|
||||||
orphans: make(map[chainhash.Hash]*btcutil.Tx),
|
orphans: make(map[chainhash.Hash]*orphanTx),
|
||||||
orphansByPrev: make(map[wire.OutPoint]map[chainhash.Hash]*btcutil.Tx),
|
orphansByPrev: make(map[wire.OutPoint]map[chainhash.Hash]*btcutil.Tx),
|
||||||
outpoints: make(map[wire.OutPoint]*btcutil.Tx),
|
nextExpireScan: time.Now().Add(orphanExpireScanInterval),
|
||||||
|
outpoints: make(map[wire.OutPoint]*btcutil.Tx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user