diff --git a/mempool.go b/mempool.go index f9e19e9e..3b46eb13 100644 --- a/mempool.go +++ b/mempool.go @@ -81,10 +81,11 @@ const ( // TxDesc is a descriptor containing a transaction in the mempool and the // metadata we store about it. type TxDesc struct { - Tx *btcutil.Tx // Transaction. - Added time.Time // Time when added to pool. - Height int64 // Blockheight when added to pool. - Fee int64 // Transaction fees. + Tx *btcutil.Tx // Transaction. + Added time.Time // Time when added to pool. + Height int64 // Blockheight when added to pool. + Fee int64 // Transaction fees. + startingPriority float64 // Priority when added to the pool. } // txMemPool is used as a source of transactions that need to be mined into @@ -670,6 +671,69 @@ func (mp *txMemPool) addTransaction(tx *btcutil.Tx, height, fee int64) { mp.lastUpdated = time.Now() } +// 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 btcchain.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) + txSize := txD.Tx.MsgTx().SerializeSize() + txD.startingPriority = calcPriority(txD.Tx, txSize, 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 btcchain.TxStore, nextBlockHeight int64) float64 { + inputAge := calcInputValueAge(txD, txStore, nextBlockHeight) + txSize := txD.Tx.MsgTx().SerializeSize() + return calcPriority(txD.Tx, txSize, inputAge) +} + +// calcInputValueAge is a helper function used to calculate the input age of +// a transaction. The input age for a txin is the number of confirmations +// since the referenced txout multiplied by its output value. +// The total input age is the sum of this value for each txin. If the tx +// depends on one currently in the mempool, then its input age is zero. +func calcInputValueAge(txDesc *TxDesc, txStore btcchain.TxStore, + nextBlockHeight int64) float64 { + var totalInputAge float64 + for _, txIn := range txDesc.Tx.MsgTx().TxIn { + originHash := &txIn.PreviousOutPoint.Hash + originIndex := txIn.PreviousOutPoint.Index + + // Don't attempt to accumulate the total input age if the txIn + // in question doesn't exist. + if txData, exists := txStore[*originHash]; exists && txData.Tx != nil { + originTxOut := txData.Tx.MsgTx().TxOut[originIndex] + + // Transactions with dependencies currently in the + // mempool have their block height set to a special + // constant. Their input age should computed as zero + // since their parent hasn't made it into a block yet. + var inputAge int64 + if txData.BlockHeight == mempoolHeight { + inputAge = 0 + } else { + inputAge = nextBlockHeight - txData.BlockHeight + } + + // Sum the input value times age. + inputValue := originTxOut.Value + totalInputAge += float64(inputValue * inputAge) + } + } + + return totalInputAge +} + // 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 diff --git a/mining.go b/mining.go index 423d7a2e..c1f4a262 100644 --- a/mining.go +++ b/mining.go @@ -513,15 +513,10 @@ mempoolLoop: continue } - // Calculate the input value age sum for the transaction. This - // is comprised of the sum all of input amounts multiplied by - // their respective age (number of confirmations since the - // referenced input transaction). While doing the above, also - // setup dependencies for any transactions which reference other - // transactions in the mempool so they can be properly ordered - // below. + // Setup dependencies for any transactions which reference + // other transactions in the mempool so they can be properly + // ordered below. prioItem := &txPrioItem{tx: txDesc.Tx} - inputValueAge := float64(0.0) for _, txIn := range tx.MsgTx().TxIn { originHash := &txIn.PreviousOutPoint.Hash originIndex := txIn.PreviousOutPoint.Index @@ -550,9 +545,8 @@ mempoolLoop: } prioItem.dependsOn[*originHash] = struct{}{} - // No need to calculate or sum input value age - // for this input since it's zero due to - // the input age multiplier of 0. + // Skip the check below. We already know the + // referenced transaction is available. continue } @@ -566,25 +560,19 @@ mempoolLoop: originIndex, originHash) continue mempoolLoop } - - // Sum the input value times age. - originTxOut := txData.Tx.MsgTx().TxOut[originIndex] - inputValue := originTxOut.Value - inputAge := nextBlockHeight - txData.BlockHeight - inputValueAge += float64(inputValue * inputAge) } // 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 - txSize := tx.MsgTx().SerializeSize() - prioItem.priority = calcPriority(tx, txSize, inputValueAge) + prioItem.priority = txDesc.CurrentPriority(txStore, nextBlockHeight) // Calculate the fee in Satoshi/KB. // NOTE: This is a more precise value than the one calculated // during calcMinRelayFee which rounds up to the nearest full // kilobyte boundary. This is beneficial since it provides an // incentive to create smaller transactions. + txSize := tx.MsgTx().SerializeSize() prioItem.feePerKB = float64(txDesc.Fee) / (float64(txSize) / 1000) prioItem.fee = txDesc.Fee diff --git a/rpcserver.go b/rpcserver.go index 14032834..d57e3a8b 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -2290,19 +2290,39 @@ func handleGetPeerInfo(s *rpcServer, cmd btcjson.Cmd, closeChan <-chan struct{}) // handleGetRawMempool implements the getrawmempool command. func handleGetRawMempool(s *rpcServer, cmd btcjson.Cmd, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*btcjson.GetRawMempoolCmd) - descs := s.server.txMemPool.TxDescs() + mp := s.server.txMemPool + descs := mp.TxDescs() if c.Verbose { result := make(map[string]*btcjson.GetRawMempoolResult, len(descs)) + + _, newestHeight, err := s.server.db.NewestSha() + if err != nil { + rpcsLog.Errorf("Cannot get newest sha: %v", err) + return nil, btcjson.ErrBlockNotFound + } + + mp.RLock() + defer mp.RUnlock() for _, desc := range descs { + // Calculate the starting and ending priority from the + // the tx's inputs. If we can't find the input for some + // reason, then we display zero in place. + inputTxs, err := mp.fetchInputTransactions(desc.Tx) + var startingPriority, endingPriority float64 + if err == nil { + startingPriority = desc.StartingPriority(inputTxs) + endingPriority = desc.CurrentPriority(inputTxs, + newestHeight+1) + } + mpd := &btcjson.GetRawMempoolResult{ - Size: int32(desc.Tx.MsgTx().SerializeSize()), - Fee: float64(desc.Fee) / - btcutil.SatoshiPerBitcoin, + Size: int32(desc.Tx.MsgTx().SerializeSize()), + Fee: btcutil.Amount(desc.Fee).ToUnit(btcutil.AmountSatoshi), Time: desc.Added.Unix(), Height: desc.Height, - StartingPriority: 0, // We don't mine. - CurrentPriority: 0, // We don't mine. + StartingPriority: startingPriority, + CurrentPriority: endingPriority, Depends: make([]string, 0), } for _, txIn := range desc.Tx.MsgTx().TxIn {