[rpc mempool] Add support for usage, total_fee, mempoolminfee, minrelaytxfee to RPC getmempoolinfo.

This commit is contained in:
Jonathan Moody 2022-07-14 17:44:39 -04:00 committed by Roy Lee
parent 5499a2c1b3
commit 13e31d033a
5 changed files with 168 additions and 18 deletions

View file

@ -337,8 +337,12 @@ type GetChainTipsResult struct {
// GetMempoolInfoResult models the data returned from the getmempoolinfo
// command.
type GetMempoolInfoResult struct {
Size int64 `json:"size"`
Bytes int64 `json:"bytes"`
Size int64 `json:"size"` // Current tx count
Bytes int64 `json:"bytes"` // Sum of all virtual transaction sizes as defined in BIP 141. Differs from actual serialized size because witness data is discounted
Usage int64 `json:"usage"` // Total memory usage for the mempool
TotalFee float64 `json:"total_fee"` // Total fees for the mempool in BTC, ignoring modified fees through prioritizetransaction
MemPoolMinFee float64 `json:"mempoolminfee"` // Minimum fee rate in BTC/kvB for tx to be accepted. Is the maximum of minrelaytxfee and minimum mempool fee
MinRelayTxFee float64 `json:"minrelaytxfee"` // Current minimum relay fee for transactions
}
// NetworksResult models the networks data from the getnetworkinfo command.

View file

@ -8,6 +8,7 @@ import (
"container/list"
"fmt"
"math"
"reflect"
"sync"
"sync/atomic"
"time"
@ -156,6 +157,15 @@ type Policy struct {
RejectReplacement bool
}
// aggregateInfo tracks aggregated serialized size, memory usage, and fees
// for TxDesc in the mempool.
type aggregateInfo struct {
totalCount int64
totalBytes int64
totalMem int64
totalFee int64
}
// TxDesc is a descriptor containing a transaction in the mempool along with
// additional metadata.
type TxDesc struct {
@ -166,6 +176,20 @@ type TxDesc struct {
StartingPriority float64
}
func (txD *TxDesc) incr(info *aggregateInfo) {
info.totalCount += 1
info.totalBytes += int64(txD.Tx.MsgTx().SerializeSize())
info.totalMem += int64(dynamicMemUsage(reflect.ValueOf(txD), false, 0))
info.totalFee += txD.Fee
}
func (txD *TxDesc) decr(info *aggregateInfo) {
info.totalCount -= 1
info.totalBytes -= int64(txD.Tx.MsgTx().SerializeSize())
info.totalMem -= int64(dynamicMemUsage(reflect.ValueOf(txD), false, 0))
info.totalFee -= txD.Fee
}
// 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.
@ -175,6 +199,18 @@ type orphanTx struct {
expiration time.Time
}
func (otx *orphanTx) incr(info *aggregateInfo) {
info.totalCount += 1
info.totalBytes += int64(otx.tx.MsgTx().SerializeSize())
info.totalMem += int64(dynamicMemUsage(reflect.ValueOf(otx), true, 0))
}
func (otx *orphanTx) decr(info *aggregateInfo) {
info.totalCount -= 1
info.totalBytes -= int64(otx.tx.MsgTx().SerializeSize())
info.totalMem -= int64(dynamicMemUsage(reflect.ValueOf(otx), false, 0))
}
// 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
// peers.
@ -196,6 +232,9 @@ type TxPool struct {
// the scan will only run when an orphan is added to the pool as opposed
// to on an unconditional timer.
nextExpireScan time.Time
// stats are aggregated over pool, orphans, etc.
stats aggregateInfo
}
// Ensure the TxPool type implements the mining.TxSource interface.
@ -240,6 +279,9 @@ func (mp *TxPool) removeOrphan(tx *btcutil.Tx, removeRedeemers bool) {
// Remove the transaction from the orphan pool.
delete(mp.orphans, *txHash)
// Update stats.
otx.decr(&mp.stats)
}
// RemoveOrphan removes the passed orphan transaction from the orphan pool and
@ -336,11 +378,12 @@ func (mp *TxPool) addOrphan(tx *btcutil.Tx, tag Tag) {
// orphan if space is still needed.
mp.limitNumOrphans()
mp.orphans[*tx.Hash()] = &orphanTx{
otx := &orphanTx{
tx: tx,
tag: tag,
expiration: time.Now().Add(orphanTTL),
}
mp.orphans[*tx.Hash()] = otx
for _, txIn := range tx.MsgTx().TxIn {
if _, exists := mp.orphansByPrev[txIn.PreviousOutPoint]; !exists {
mp.orphansByPrev[txIn.PreviousOutPoint] =
@ -349,6 +392,9 @@ func (mp *TxPool) addOrphan(tx *btcutil.Tx, tag Tag) {
mp.orphansByPrev[txIn.PreviousOutPoint][*tx.Hash()] = tx
}
// Update stats.
otx.incr(&mp.stats)
log.Debugf("Stored orphan transaction %v (total: %d)", tx.Hash(),
len(mp.orphans))
}
@ -498,6 +544,9 @@ func (mp *TxPool) removeTransaction(tx *btcutil.Tx, removeRedeemers bool) {
}
delete(mp.pool, *txHash)
// Update stats.
txDesc.decr(&mp.stats)
// Inform associated fee estimator that the transaction has been removed
// from the mempool
if mp.cfg.RemoveTxFromFeeEstimation != nil {
@ -579,6 +628,9 @@ func (mp *TxPool) addTransaction(utxoView *blockchain.UtxoViewpoint, tx *btcutil
mp.cfg.AddTxToFeeEstimation(txD.Tx.Hash(), txD.Fee, size)
}
// Update stats.
txD.incr(&mp.stats)
return txD
}
@ -1503,6 +1555,24 @@ func (mp *TxPool) MiningDescs() []*mining.TxDesc {
return descs
}
func (mp *TxPool) MempoolInfo() *btcjson.GetMempoolInfoResult {
mp.mtx.RLock()
policy := mp.cfg.Policy
stats := mp.stats
mp.mtx.RUnlock()
ret := &btcjson.GetMempoolInfoResult{
Size: stats.totalCount,
Usage: stats.totalMem,
Bytes: stats.totalBytes,
TotalFee: btcutil.Amount(stats.totalFee).ToBTC(),
MemPoolMinFee: btcutil.Amount(calcMinRequiredTxRelayFee(1000, policy.MinRelayTxFee)).ToBTC(),
MinRelayTxFee: policy.MinRelayTxFee.ToBTC(),
}
return ret
}
// RawMempoolVerbose returns all the entries in the mempool as a fully
// populated btcjson result.
//

84
mempool/memusage.go Normal file
View file

@ -0,0 +1,84 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package mempool
import (
"reflect"
)
func dynamicMemUsage(v reflect.Value, debug bool, level int) uintptr {
t := v.Type()
bytes := t.Size()
if debug {
println("[", level, "]", t.Kind().String(), "(", t.String(), ") ->", t.Size())
}
// For complex types, we need to peek inside slices/arrays/structs/maps and chase pointers.
switch t.Kind() {
case reflect.Pointer, reflect.Interface:
if !v.IsNil() {
bytes += dynamicMemUsage(v.Elem(), debug, level+1)
}
case reflect.Array, reflect.Slice:
for j := 0; j < v.Len(); j++ {
vi := v.Index(j)
k := vi.Type().Kind()
if debug {
println("[", level, "] index:", j, "kind:", k.String())
}
elemB := uintptr(0)
if t.Kind() == reflect.Array {
if (k == reflect.Pointer || k == reflect.Interface) && !vi.IsNil() {
elemB += dynamicMemUsage(vi.Elem(), debug, level+1)
}
} else { // slice
elemB += dynamicMemUsage(vi, debug, level+1)
}
if k == reflect.Uint8 {
// short circuit for byte slice/array
bytes += elemB * uintptr(v.Len())
if debug {
println("...", v.Len(), "elements")
}
break
}
bytes += elemB
}
case reflect.Map:
iter := v.MapRange()
for iter.Next() {
vk := iter.Key()
vv := iter.Value()
if debug {
println("[", level, "] key:", vk.Type().Kind().String())
}
bytes += dynamicMemUsage(vk, debug, level+1)
if debug {
println("[", level, "] value:", vv.Type().Kind().String())
}
bytes += dynamicMemUsage(vv, debug, level+1)
if debug {
println("...", v.Len(), "map elements")
}
debug = false
}
case reflect.Struct:
for _, f := range reflect.VisibleFields(t) {
vf := v.FieldByIndex(f.Index)
k := vf.Type().Kind()
if debug {
println("[", level, "] field:", f.Name, "kind:", k.String())
}
if (k == reflect.Pointer || k == reflect.Interface) && !vf.IsNil() {
bytes += dynamicMemUsage(vf.Elem(), debug, level+1)
} else if k == reflect.Array || k == reflect.Slice {
bytes -= vf.Type().Size()
bytes += dynamicMemUsage(vf, debug, level+1)
}
}
}
return bytes
}

View file

@ -2466,19 +2466,7 @@ func handleGetInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (in
// handleGetMempoolInfo implements the getmempoolinfo command.
func handleGetMempoolInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
mempoolTxns := s.cfg.TxMemPool.TxDescs()
var numBytes int64
for _, txD := range mempoolTxns {
numBytes += int64(txD.Tx.MsgTx().SerializeSize())
}
ret := &btcjson.GetMempoolInfoResult{
Size: int64(len(mempoolTxns)),
Bytes: numBytes,
}
return ret, nil
return s.cfg.TxMemPool.MempoolInfo(), nil
}
// handleGetMempoolEntry implements the getmempoolentry command.

View file

@ -455,6 +455,10 @@ var helpDescsEnUS = map[string]string{
// GetMempoolInfoResult help.
"getmempoolinforesult-bytes": "Size in bytes of the mempool",
"getmempoolinforesult-size": "Number of transactions in the mempool",
"getmempoolinforesult-usage": "Total memory usage for the mempool",
"getmempoolinforesult-total_fee": "Total fees for the mempool in LBC, ignoring modified fees through prioritizetransaction",
"getmempoolinforesult-mempoolminfee": "Minimum fee rate in LBC/kvB for tx to be accepted. Is the maximum of minrelaytxfee and minimum mempool fee",
"getmempoolinforesult-minrelaytxfee": "Current minimum relay fee for transactions",
// GetMiningInfoResult help.
"getmininginforesult-blocks": "Height of the latest best block",