[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
|
// GetMempoolInfoResult models the data returned from the getmempoolinfo
|
||||||
// command.
|
// command.
|
||||||
type GetMempoolInfoResult struct {
|
type GetMempoolInfoResult struct {
|
||||||
Size int64 `json:"size"`
|
Size int64 `json:"size"` // Current tx count
|
||||||
Bytes int64 `json:"bytes"`
|
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.
|
// NetworksResult models the networks data from the getnetworkinfo command.
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"container/list"
|
"container/list"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
@ -156,6 +157,15 @@ type Policy struct {
|
||||||
RejectReplacement bool
|
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
|
// TxDesc is a descriptor containing a transaction in the mempool along with
|
||||||
// additional metadata.
|
// additional metadata.
|
||||||
type TxDesc struct {
|
type TxDesc struct {
|
||||||
|
@ -166,6 +176,20 @@ type TxDesc struct {
|
||||||
StartingPriority float64
|
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
|
// orphanTx is normal transaction that references an ancestor transaction
|
||||||
// that is not yet available. It also contains additional information related
|
// 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.
|
// to it such as an expiration time to help prevent caching the orphan forever.
|
||||||
|
@ -175,6 +199,18 @@ type orphanTx struct {
|
||||||
expiration time.Time
|
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
|
// 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.
|
||||||
|
@ -196,6 +232,9 @@ type TxPool struct {
|
||||||
// the scan will only run when an orphan is added to the pool as opposed
|
// the scan will only run when an orphan is added to the pool as opposed
|
||||||
// to on an unconditional timer.
|
// to on an unconditional timer.
|
||||||
nextExpireScan time.Time
|
nextExpireScan time.Time
|
||||||
|
|
||||||
|
// stats are aggregated over pool, orphans, etc.
|
||||||
|
stats aggregateInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the TxPool type implements the mining.TxSource interface.
|
// 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.
|
// Remove the transaction from the orphan pool.
|
||||||
delete(mp.orphans, *txHash)
|
delete(mp.orphans, *txHash)
|
||||||
|
|
||||||
|
// Update stats.
|
||||||
|
otx.decr(&mp.stats)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveOrphan removes the passed orphan transaction from the orphan pool and
|
// 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.
|
// orphan if space is still needed.
|
||||||
mp.limitNumOrphans()
|
mp.limitNumOrphans()
|
||||||
|
|
||||||
mp.orphans[*tx.Hash()] = &orphanTx{
|
otx := &orphanTx{
|
||||||
tx: tx,
|
tx: tx,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
expiration: time.Now().Add(orphanTTL),
|
expiration: time.Now().Add(orphanTTL),
|
||||||
}
|
}
|
||||||
|
mp.orphans[*tx.Hash()] = otx
|
||||||
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] =
|
||||||
|
@ -349,6 +392,9 @@ func (mp *TxPool) addOrphan(tx *btcutil.Tx, tag Tag) {
|
||||||
mp.orphansByPrev[txIn.PreviousOutPoint][*tx.Hash()] = tx
|
mp.orphansByPrev[txIn.PreviousOutPoint][*tx.Hash()] = tx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update stats.
|
||||||
|
otx.incr(&mp.stats)
|
||||||
|
|
||||||
log.Debugf("Stored orphan transaction %v (total: %d)", tx.Hash(),
|
log.Debugf("Stored orphan transaction %v (total: %d)", tx.Hash(),
|
||||||
len(mp.orphans))
|
len(mp.orphans))
|
||||||
}
|
}
|
||||||
|
@ -498,6 +544,9 @@ func (mp *TxPool) removeTransaction(tx *btcutil.Tx, removeRedeemers bool) {
|
||||||
}
|
}
|
||||||
delete(mp.pool, *txHash)
|
delete(mp.pool, *txHash)
|
||||||
|
|
||||||
|
// Update stats.
|
||||||
|
txDesc.decr(&mp.stats)
|
||||||
|
|
||||||
// Inform associated fee estimator that the transaction has been removed
|
// Inform associated fee estimator that the transaction has been removed
|
||||||
// from the mempool
|
// from the mempool
|
||||||
if mp.cfg.RemoveTxFromFeeEstimation != nil {
|
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)
|
mp.cfg.AddTxToFeeEstimation(txD.Tx.Hash(), txD.Fee, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update stats.
|
||||||
|
txD.incr(&mp.stats)
|
||||||
|
|
||||||
return txD
|
return txD
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1503,6 +1555,24 @@ func (mp *TxPool) MiningDescs() []*mining.TxDesc {
|
||||||
return descs
|
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
|
// RawMempoolVerbose returns all the entries in the mempool as a fully
|
||||||
// populated btcjson result.
|
// 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.
|
// handleGetMempoolInfo implements the getmempoolinfo command.
|
||||||
func handleGetMempoolInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
|
func handleGetMempoolInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
|
||||||
mempoolTxns := s.cfg.TxMemPool.TxDescs()
|
return s.cfg.TxMemPool.MempoolInfo(), nil
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleGetMempoolEntry implements the getmempoolentry command.
|
// handleGetMempoolEntry implements the getmempoolentry command.
|
||||||
|
|
|
@ -455,6 +455,10 @@ var helpDescsEnUS = map[string]string{
|
||||||
// GetMempoolInfoResult help.
|
// GetMempoolInfoResult help.
|
||||||
"getmempoolinforesult-bytes": "Size in bytes of the mempool",
|
"getmempoolinforesult-bytes": "Size in bytes of the mempool",
|
||||||
"getmempoolinforesult-size": "Number of transactions in 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 help.
|
||||||
"getmininginforesult-blocks": "Height of the latest best block",
|
"getmininginforesult-blocks": "Height of the latest best block",
|
||||||
|
|
Loading…
Reference in a new issue