mempool/mining: Decouple and optimize priority calcs.

This does three things:

- Splits the priority calculation logic from the TxDesc type
- Modifies the calcPriority function to perform the value age
  calculation instead of accepting it as a parameter
- Changes the starting priority to be calculated when the transaction is
  added to the pool

The first is useful as it is a step towards decoupling the mining code
from the internals of the memory pool.  Also, the concept of priority is
related to mining policy, so it makes more sense to have the
calculations separate than being defined on the memory pool tx
descriptor.

The second change has been made because everywhere that uses the
calcPriority function first has to calculate the value age anyways and
by making it part of the function it can be avoided altogether in
certain circumstances thereby provided a bit of optimization.

The third change ensure the starting priority is safe for reentrancy
which will be important once the mempool is split into a separate
package.
This commit is contained in:
Dave Collins 2015-11-25 12:34:24 -06:00
parent f41ff545be
commit 8ab565ce21
4 changed files with 26 additions and 52 deletions

View file

@ -47,7 +47,7 @@ type TxDesc struct {
Added time.Time // Time when added to pool.
Height int32 // Blockheight when added to pool.
Fee int64 // Transaction fees.
startingPriority float64 // Priority when added to the pool.
StartingPriority float64 // Priority when added to the pool.
}
// txMemPool is used as a source of transactions that need to be mined into
@ -374,7 +374,7 @@ func (mp *txMemPool) RemoveDoubleSpends(tx *btcutil.Tx) {
// helper for maybeAcceptTransaction.
//
// This function MUST be called with the mempool lock held (for writes).
func (mp *txMemPool) addTransaction(tx *btcutil.Tx, height int32, fee int64) {
func (mp *txMemPool) addTransaction(txStore blockchain.TxStore, tx *btcutil.Tx, height int32, fee int64) {
// Add the transaction to the pool and mark the referenced outpoints
// as spent by the pool.
mp.pool[*tx.Sha()] = &TxDesc{
@ -382,6 +382,7 @@ func (mp *txMemPool) addTransaction(tx *btcutil.Tx, height int32, fee int64) {
Added: time.Now(),
Height: height,
Fee: fee,
StartingPriority: calcPriority(tx.MsgTx(), txStore, height),
}
for _, txIn := range tx.MsgTx().TxIn {
mp.outpoints[txIn.PreviousOutPoint] = tx
@ -462,29 +463,6 @@ func (mp *txMemPool) indexScriptAddressToTx(pkScript []byte, tx *btcutil.Tx) err
return nil
}
// StartingPriority calculates the priority of this tx descriptor's underlying
// transaction relative to when it was first added to the mempool. The result
// is lazily computed and then cached for subsequent function calls.
func (txD *TxDesc) StartingPriority(txStore blockchain.TxStore) float64 {
// Return our cached result.
if txD.startingPriority != float64(0) {
return txD.startingPriority
}
// Compute our starting priority caching the result.
inputAge := calcInputValueAge(txD, txStore, txD.Height)
txD.startingPriority = calcPriority(txD.Tx, inputAge)
return txD.startingPriority
}
// CurrentPriority calculates the current priority of this tx descriptor's
// underlying transaction relative to the next block height.
func (txD *TxDesc) CurrentPriority(txStore blockchain.TxStore, nextBlockHeight int32) float64 {
inputAge := calcInputValueAge(txD, txStore, nextBlockHeight)
return calcPriority(txD.Tx, inputAge)
}
// checkPoolDoubleSpend checks whether or not the passed transaction is
// attempting to spend coins already spent by other transactions in the pool.
// Note it does not check for double spends against transactions already in the
@ -772,13 +750,8 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isNew, rateLimit boo
// memory pool from blocks that have been disconnected during a reorg
// are exempted.
if isNew && !cfg.NoRelayPriority && txFee < minFee {
txD := &TxDesc{
Tx: tx,
Added: time.Now(),
Height: curHeight,
Fee: txFee,
}
currentPriority := txD.CurrentPriority(txStore, nextBlockHeight)
currentPriority := calcPriority(tx.MsgTx(), txStore,
nextBlockHeight)
if currentPriority <= minHighPriority {
str := fmt.Sprintf("transaction %v has insufficient "+
"priority (%g <= %g)", txHash,
@ -823,7 +796,7 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isNew, rateLimit boo
}
// Add to transaction pool.
mp.addTransaction(tx, curHeight, txFee)
mp.addTransaction(txStore, tx, curHeight, txFee)
txmpLog.Debugf("Accepted transaction %v (pool size: %v)", txHash,
len(mp.pool))

View file

@ -540,7 +540,7 @@ mempoolLoop:
// Calculate the final transaction priority using the input
// value age sum as well as the adjusted transaction size. The
// formula is: sum(inputValue * inputAge) / adjustedTxSize
prioItem.priority = txDesc.CurrentPriority(txStore, nextBlockHeight)
prioItem.priority = calcPriority(tx.MsgTx(), txStore, nextBlockHeight)
// Calculate the fee in Satoshi/kB.
txSize := tx.MsgTx().SerializeSize()

View file

@ -79,7 +79,7 @@ func calcMinRequiredTxRelayFee(serializedSize int64, minRelayTxFee btcutil.Amoun
// of each of its input values multiplied by their age (# of confirmations).
// Thus, the final formula for the priority is:
// sum(inputValue * inputAge) / adjustedTxSize
func calcPriority(tx *btcutil.Tx, inputValueAge float64) float64 {
func calcPriority(tx *wire.MsgTx, txStore blockchain.TxStore, nextBlockHeight int32) float64 {
// In order to encourage spending multiple old unspent transaction
// outputs thereby reducing the total set, don't count the constant
// overhead for each input as well as enough bytes of the signature
@ -101,16 +101,17 @@ func calcPriority(tx *btcutil.Tx, inputValueAge float64) float64 {
//
// Thus 1 + 73 + 1 + 1 + 33 + 1 = 110
overhead := 0
for _, txIn := range tx.MsgTx().TxIn {
for _, txIn := range tx.TxIn {
// Max inputs + size can't possibly overflow here.
overhead += 41 + minInt(110, len(txIn.SignatureScript))
}
serializedTxSize := tx.MsgTx().SerializeSize()
serializedTxSize := tx.SerializeSize()
if overhead >= serializedTxSize {
return 0.0
}
inputValueAge := calcInputValueAge(tx, txStore, nextBlockHeight)
return inputValueAge / float64(serializedTxSize-overhead)
}
@ -120,9 +121,9 @@ func calcPriority(tx *btcutil.Tx, inputValueAge float64) float64 {
// age is the sum of this value for each txin. Any inputs to the transaction
// which are currently in the mempool and hence not mined into a block yet,
// contribute no additional input age to the transaction.
func calcInputValueAge(txDesc *TxDesc, txStore blockchain.TxStore, nextBlockHeight int32) float64 {
func calcInputValueAge(tx *wire.MsgTx, txStore blockchain.TxStore, nextBlockHeight int32) float64 {
var totalInputAge float64
for _, txIn := range txDesc.Tx.MsgTx().TxIn {
for _, txIn := range tx.TxIn {
originHash := &txIn.PreviousOutPoint.Hash
originIndex := txIn.PreviousOutPoint.Index

View file

@ -2404,15 +2404,15 @@ func handleGetRawMempool(s *rpcServer, cmd interface{}, closeChan <-chan struct{
mp.RLock()
defer mp.RUnlock()
for _, desc := range descs {
// Calculate the starting and current priority from the
// the tx's inputs. Use zeros if one or more of the
// input transactions can't be found for some reason.
var startingPriority, currentPriority float64
inputTxs, err := mp.fetchInputTransactions(desc.Tx, false)
// Calculate the current priority from the the tx's
// inputs. Use zero if one or more of the input
// transactions can't be found for some reason.
tx := desc.Tx
var currentPriority float64
inputTxs, err := mp.fetchInputTransactions(tx, false)
if err == nil {
startingPriority = desc.StartingPriority(inputTxs)
currentPriority = desc.CurrentPriority(inputTxs,
newestHeight+1)
currentPriority = calcPriority(tx.MsgTx(),
inputTxs, newestHeight+1)
}
mpd := &btcjson.GetRawMempoolVerboseResult{
@ -2420,7 +2420,7 @@ func handleGetRawMempool(s *rpcServer, cmd interface{}, closeChan <-chan struct{
Fee: btcutil.Amount(desc.Fee).ToBTC(),
Time: desc.Added.Unix(),
Height: int64(desc.Height),
StartingPriority: startingPriority,
StartingPriority: desc.StartingPriority,
CurrentPriority: currentPriority,
Depends: make([]string, 0),
}