[rpc mempool] Add support for usage, total_fee, mempoolminfee, minrelaytxfee to RPC getmempoolinfo.
This commit is contained in:
parent
5499a2c1b3
commit
13e31d033a
5 changed files with 168 additions and 18 deletions
|
@ -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.
|
||||
|
|
|
@ -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
84
mempool/memusage.go
Normal 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
|
||||
}
|
14
rpcserver.go
14
rpcserver.go
|
@ -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.
|
||||
|
|
|
@ -453,8 +453,12 @@ var helpDescsEnUS = map[string]string{
|
|||
"getmempoolinfo--synopsis": "Returns memory pool information",
|
||||
|
||||
// GetMempoolInfoResult help.
|
||||
"getmempoolinforesult-bytes": "Size in bytes of the mempool",
|
||||
"getmempoolinforesult-size": "Number of transactions in the mempool",
|
||||
"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",
|
||||
|
|
Loading…
Reference in a new issue