Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rpc method estimatefee #735

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
749 changes: 749 additions & 0 deletions mempool/estimatefee.go

Large diffs are not rendered by default.

424 changes: 424 additions & 0 deletions mempool/estimatefee_test.go

Large diffs are not rendered by default.

11 changes: 10 additions & 1 deletion mempool/mempool.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ type Config struct {
// indexing the unconfirmed transactions in the memory pool.
// This can be nil if the address index is not enabled.
AddrIndex *indexers.AddrIndex

// FeeEstimatator provides a feeEstimator. If it is not nil, the mempool
// records all new transactions it observes into the feeEstimator.
FeeEstimator *FeeEstimator
}

// Policy houses the policy (configuration parameters) which is used to
Expand Down Expand Up @@ -527,8 +531,8 @@ func (mp *TxPool) addTransaction(utxoView *blockchain.UtxoViewpoint, tx *btcutil
},
StartingPriority: mining.CalcPriority(tx.MsgTx(), utxoView, height),
}
mp.pool[*tx.Hash()] = txD

mp.pool[*tx.Hash()] = txD
for _, txIn := range tx.MsgTx().TxIn {
mp.outpoints[txIn.PreviousOutPoint] = tx
}
Expand All @@ -540,6 +544,11 @@ func (mp *TxPool) addTransaction(utxoView *blockchain.UtxoViewpoint, tx *btcutil
mp.cfg.AddrIndex.AddUnconfirmedTx(tx, utxoView)
}

// Record this tx for fee estimation if enabled.
if mp.cfg.FeeEstimator != nil {
mp.cfg.FeeEstimator.ObserveTransaction(txD)
}

return txD
}

Expand Down
2 changes: 2 additions & 0 deletions netsync/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,6 @@ type Config struct {

DisableCheckpoints bool
MaxPeers int

FeeEstimator *mempool.FeeEstimator
}
23 changes: 23 additions & 0 deletions netsync/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ type SyncManager struct {
headerList *list.List
startHeader *list.Element
nextCheckpoint *chaincfg.Checkpoint

// An optional fee estimator.
feeEstimator *mempool.FeeEstimator
}

// resetHeaderState sets the headers-first mode state to values appropriate for
Expand Down Expand Up @@ -1249,6 +1252,20 @@ func (sm *SyncManager) handleBlockchainNotification(notification *blockchain.Not
sm.peerNotifier.AnnounceNewTransactions(acceptedTxs)
}

// Register block with the fee estimator, if it exists.
if sm.feeEstimator != nil {
err := sm.feeEstimator.RegisterBlock(block)

// If an error is somehow generated then the fee estimator
// has entered an invalid state. Since it doesn't know how
// to recover, create a new one.
if err != nil {
sm.feeEstimator = mempool.NewFeeEstimator(
mempool.DefaultEstimateFeeMaxRollback,
mempool.DefaultEstimateFeeMinRegisteredBlocks)
}
}

// A block has been disconnected from the main block chain.
case blockchain.NTBlockDisconnected:
block, ok := notification.Data.(*btcutil.Block)
Expand All @@ -1269,6 +1286,11 @@ func (sm *SyncManager) handleBlockchainNotification(notification *blockchain.Not
sm.txMemPool.RemoveTransaction(tx, true)
}
}

// Rollback previous block recorded by the fee estimator.
if sm.feeEstimator != nil {
sm.feeEstimator.Rollback(block.Hash())
}
}
}

Expand Down Expand Up @@ -1417,6 +1439,7 @@ func New(config *Config) (*SyncManager, error) {
msgChan: make(chan interface{}, config.MaxPeers*3),
headerList: list.New(),
quit: make(chan struct{}),
feeEstimator: config.FeeEstimator,
}

best := sm.chain.BestSnapshot()
Expand Down
37 changes: 37 additions & 0 deletions rpcclient/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
29 changes: 28 additions & 1 deletion rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ var rpcHandlersBeforeInit = map[string]commandHandler{
"debuglevel": handleDebugLevel,
"decoderawtransaction": handleDecodeRawTransaction,
"decodescript": handleDecodeScript,
"estimatefee": handleEstimateFee,
"generate": handleGenerate,
"getaddednodeinfo": handleGetAddedNodeInfo,
"getbestblock": handleGetBestBlock,
Expand Down Expand Up @@ -222,7 +223,6 @@ var rpcAskWallet = map[string]struct{}{

// Commands that are currently unimplemented, but should ultimately be.
var rpcUnimplemented = map[string]struct{}{
"estimatefee": {},
"estimatepriority": {},
"getchaintips": {},
"getmempoolentry": {},
Expand Down Expand Up @@ -252,6 +252,7 @@ var rpcLimited = map[string]struct{}{
"createrawtransaction": {},
"decoderawtransaction": {},
"decodescript": {},
"estimatefee": {},
"getbestblock": {},
"getbestblockhash": {},
"getblock": {},
Expand Down Expand Up @@ -849,6 +850,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), 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
Expand Down Expand Up @@ -4182,6 +4205,10 @@ type rpcserverConfig struct {
// of to provide additional data when queried.
TxIndex *indexers.TxIndex
AddrIndex *indexers.AddrIndex

// 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.
Expand Down
10 changes: 10 additions & 0 deletions rpcserverhelp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down Expand Up @@ -658,6 +667,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)},
Expand Down
70 changes: 57 additions & 13 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,10 @@ type server struct {
// do not need to be protected for concurrent access.
txIndex *indexers.TxIndex
addrIndex *indexers.AddrIndex

// The fee estimator keeps track of how long transactions are left in
// the mempool before they are mined into blocks.
feeEstimator *mempool.FeeEstimator
}

// serverPeer extends the peer to maintain state shared by the server and
Expand Down Expand Up @@ -1953,6 +1957,14 @@ func (s *server) Stop() error {
s.rpcServer.Stop()
}

// Save fee estimator state in the database.
s.db.Update(func(tx database.Tx) error {
metadata := tx.Metadata()
metadata.Put(mempool.EstimateFeeDatabaseKey, s.feeEstimator.Save())

return nil
})

// Signal the remaining goroutines to quit.
close(s.quit)
return nil
Expand Down Expand Up @@ -2249,6 +2261,36 @@ func newServer(listenAddrs []string, db database.DB, chainParams *chaincfg.Param
return nil, err
}

// Search for a FeeEstimator state in the database. If none can be found
// or if it cannot be loaded, create a new one.
db.Update(func(tx database.Tx) error {
metadata := tx.Metadata()
feeEstimationData := metadata.Get(mempool.EstimateFeeDatabaseKey)
if feeEstimationData != nil {
// delete it from the database so that we don't try to restore the
// same thing again somehow.
metadata.Delete(mempool.EstimateFeeDatabaseKey)

// If there is an error, log it and make a new fee estimator.
var err error
s.feeEstimator, err = mempool.RestoreFeeEstimator(feeEstimationData)

if err != nil {
peerLog.Errorf("Failed to restore fee estimator %v", err)
}
}

return nil
})

// If no feeEstimator has been found, or if the one that has been found
// is behind somehow, create a new one and start over.
if s.feeEstimator == nil || s.feeEstimator.LastKnownHeight() != s.chain.BestSnapshot().Height {
s.feeEstimator = mempool.NewFeeEstimator(
mempool.DefaultEstimateFeeMaxRollback,
mempool.DefaultEstimateFeeMinRegisteredBlocks)
}

txC := mempool.Config{
Policy: mempool.Policy{
DisableRelayPriority: cfg.NoRelayPriority,
Expand All @@ -2271,6 +2313,7 @@ func newServer(listenAddrs []string, db database.DB, chainParams *chaincfg.Param
SigCache: s.sigCache,
HashCache: s.hashCache,
AddrIndex: s.addrIndex,
FeeEstimator: s.feeEstimator,
}
s.txMemPool = mempool.New(&txC)

Expand Down Expand Up @@ -2405,19 +2448,20 @@ 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,
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,
FeeEstimator: s.feeEstimator,
})
if err != nil {
return nil, err
Expand Down