From 4fd446028fc600090ef5f0c5c5156afe602f76a6 Mon Sep 17 00:00:00 2001 From: Daniel Krawisz Date: Mon, 13 Nov 2017 16:39:16 -0600 Subject: [PATCH] Enable estimatefee rpc command. --- mempool/estimatefee.go | 6 +++--- mempool/estimatefee_test.go | 8 ++++---- rpcclient/chain.go | 37 +++++++++++++++++++++++++++++++++++++ rpcserver.go | 29 ++++++++++++++++++++++++++++- rpcserverhelp.go | 10 ++++++++++ server.go | 29 +++++++++++++++-------------- 6 files changed, 97 insertions(+), 22 deletions(-) diff --git a/mempool/estimatefee.go b/mempool/estimatefee.go index 7f56403d..724f3c4e 100644 --- a/mempool/estimatefee.go +++ b/mempool/estimatefee.go @@ -12,9 +12,9 @@ import ( "sort" "sync" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/mining" - "github.com/btcsuite/btcutil" + "github.com/roasbeef/btcd/chaincfg/chainhash" + "github.com/roasbeef/btcd/mining" + "github.com/roasbeef/btcutil" ) // TODO incorporate Alex Morcos' modifications to Gavin's initial model diff --git a/mempool/estimatefee_test.go b/mempool/estimatefee_test.go index ae3dec4b..df73e322 100644 --- a/mempool/estimatefee_test.go +++ b/mempool/estimatefee_test.go @@ -8,10 +8,10 @@ import ( "math/rand" "testing" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/mining" - "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/roasbeef/btcd/chaincfg/chainhash" + "github.com/roasbeef/btcd/mining" + "github.com/roasbeef/btcd/wire" + "github.com/roasbeef/btcutil" ) // newTestFeeEstimator creates a feeEstimator with some different parameters diff --git a/rpcclient/chain.go b/rpcclient/chain.go index 41faea18..3d8f9c81 100644 --- a/rpcclient/chain.go +++ b/rpcclient/chain.go @@ -557,6 +557,43 @@ func (c *Client) GetRawMempoolVerbose() (map[string]btcjson.GetRawMempoolVerbose return c.GetRawMempoolVerboseAsync().Receive() } +// FutureEstimateFeeResult is a future promise to deliver the result of a +// EstimateFeeAsync RPC invocation (or an applicable error). +type FutureEstimateFeeResult chan *response + +// Receive waits for the response promised by the future and returns the info +// provided by the server. +func (r FutureEstimateFeeResult) Receive() (float64, error) { + res, err := receiveFuture(r) + if err != nil { + return -1, err + } + + // Unmarshal result as a getinfo result object. + var fee float64 + err = json.Unmarshal(res, &fee) + if err != nil { + return -1, err + } + + return fee, nil +} + +// EstimateFeeAsync returns an instance of a type that can be used to get the result +// of the RPC at some future time by invoking the Receive function on the +// returned instance. +// +// See EstimateFee for the blocking version and more details. +func (c *Client) EstimateFeeAsync(numBlocks int64) FutureEstimateFeeResult { + cmd := btcjson.NewEstimateFeeCmd(numBlocks) + return c.sendCmd(cmd) +} + +// EstimateFee provides an estimated fee in bitcoins per kilobyte. +func (c *Client) EstimateFee(numBlocks int64) (float64, error) { + return c.EstimateFeeAsync(numBlocks).Receive() +} + // FutureVerifyChainResult is a future promise to deliver the result of a // VerifyChainAsync, VerifyChainLevelAsyncRPC, or VerifyChainBlocksAsync // invocation (or an applicable error). diff --git a/rpcserver.go b/rpcserver.go index b10d5287..d437775a 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -132,6 +132,7 @@ var rpcHandlersBeforeInit = map[string]commandHandler{ "debuglevel": handleDebugLevel, "decoderawtransaction": handleDecodeRawTransaction, "decodescript": handleDecodeScript, + "estimatefee": handleEstimateFee, "generate": handleGenerate, "getaddednodeinfo": handleGetAddedNodeInfo, "getbestblock": handleGetBestBlock, @@ -224,7 +225,6 @@ var rpcAskWallet = map[string]struct{}{ // Commands that are currently unimplemented, but should ultimately be. var rpcUnimplemented = map[string]struct{}{ - "estimatefee": {}, "estimatepriority": {}, "getchaintips": {}, "getmempoolentry": {}, @@ -254,6 +254,7 @@ var rpcLimited = map[string]struct{}{ "createrawtransaction": {}, "decoderawtransaction": {}, "decodescript": {}, + "estimatefee": {}, "getbestblock": {}, "getbestblockhash": {}, "getblock": {}, @@ -853,6 +854,28 @@ func handleDecodeScript(s *rpcServer, cmd interface{}, closeChan <-chan struct{} return reply, nil } +// handleEstimateFee handles estimatefee commands. +func handleEstimateFee(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + c := cmd.(*btcjson.EstimateFeeCmd) + + if s.cfg.FeeEstimator == nil { + return nil, errors.New("Fee estimation disabled") + } + + if c.NumBlocks <= 0 { + return -1.0, errors.New("Parameter NumBlocks must be positive") + } + + feeRate, err := s.cfg.FeeEstimator.EstimateFee(uint32(c.NumBlocks)) + + if err != nil { + return -1.0, err + } + + // Convert to satoshis per kb. + return float64(feeRate.ToSatoshiPerKb()), nil +} + // handleGenerate handles generate commands. func handleGenerate(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { // Respond with an error if there are no addresses to pay the @@ -4247,6 +4270,10 @@ type rpcserverConfig struct { TxIndex *indexers.TxIndex AddrIndex *indexers.AddrIndex CfIndex *indexers.CfIndex + + // The fee estimator keeps track of how long transactions are left in + // the mempool before they are mined into blocks. + FeeEstimator *mempool.FeeEstimator } // newRPCServer returns a new instance of the rpcServer struct. diff --git a/rpcserverhelp.go b/rpcserverhelp.go index 19a3d724..c22448e1 100644 --- a/rpcserverhelp.go +++ b/rpcserverhelp.go @@ -115,6 +115,15 @@ var helpDescsEnUS = map[string]string{ "decodescript--synopsis": "Returns a JSON object with information about the provided hex-encoded script.", "decodescript-hexscript": "Hex-encoded script", + // EstimateFeeCmd help. + "estimatefee--synopsis": "Estimate the fee per kilobyte in satoshis " + + "required for a transaction to be mined before a certain number of " + + "blocks have been generated.", + "estimatefee-numblocks": "The maximum number of blocks which can be " + + "generated before the transaction is mined.", + "estimatefee--result0": "Estimated fee per kilobyte in satoshis for a block to " + + "be mined in the next NumBlocks blocks.", + // GenerateCmd help "generate--synopsis": "Generates a set number of blocks (simnet or regtest only) and returns a JSON\n" + " array of their hashes.", @@ -663,6 +672,7 @@ var rpcResultTypes = map[string][]interface{}{ "debuglevel": {(*string)(nil), (*string)(nil)}, "decoderawtransaction": {(*btcjson.TxRawDecodeResult)(nil)}, "decodescript": {(*btcjson.DecodeScriptResult)(nil)}, + "estimatefee": {(*float64)(nil)}, "generate": {(*[]string)(nil)}, "getaddednodeinfo": {(*[]string)(nil), (*[]btcjson.GetAddedNodeInfoResult)(nil)}, "getbestblock": {(*btcjson.GetBestBlockResult)(nil)}, diff --git a/server.go b/server.go index c9132471..fa9370ac 100644 --- a/server.go +++ b/server.go @@ -2572,20 +2572,21 @@ func newServer(listenAddrs []string, db database.DB, chainParams *chaincfg.Param } s.rpcServer, err = newRPCServer(&rpcserverConfig{ - Listeners: rpcListeners, - StartupTime: s.startupTime, - ConnMgr: &rpcConnManager{&s}, - SyncMgr: &rpcSyncMgr{&s, s.syncManager}, - TimeSource: s.timeSource, - Chain: s.chain, - ChainParams: chainParams, - DB: db, - TxMemPool: s.txMemPool, - Generator: blockTemplateGenerator, - CPUMiner: s.cpuMiner, - TxIndex: s.txIndex, - AddrIndex: s.addrIndex, - CfIndex: s.cfIndex, + Listeners: rpcListeners, + StartupTime: s.startupTime, + ConnMgr: &rpcConnManager{&s}, + SyncMgr: &rpcSyncMgr{&s, s.syncManager}, + TimeSource: s.timeSource, + Chain: s.chain, + ChainParams: chainParams, + DB: db, + TxMemPool: s.txMemPool, + Generator: blockTemplateGenerator, + CPUMiner: s.cpuMiner, + TxIndex: s.txIndex, + AddrIndex: s.addrIndex, + CfIndex: s.cfIndex, + FeeEstimator: feeEstimator, }) if err != nil { return nil, err