github.com/decred/dcrlnd@v0.7.6/lnwallet/chainfee/estimator.go (about)

     1  package chainfee
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"math"
    10  	prand "math/rand"
    11  	"net"
    12  	"net/http"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/decred/dcrd/dcrutil/v4"
    17  	dcrjson "github.com/decred/dcrd/rpc/jsonrpc/types/v4"
    18  	"github.com/decred/dcrd/rpcclient/v8"
    19  )
    20  
    21  const (
    22  	// maxBlockTarget is the highest number of blocks confirmations that
    23  	// a WebAPIEstimator will cache fees for. This number is chosen
    24  	// because it's the highest number of confs bitcoind will return a fee
    25  	// estimate for.
    26  	maxBlockTarget uint32 = 1008
    27  
    28  	// minBlockTarget is the lowest number of blocks confirmations that
    29  	// a WebAPIEstimator will cache fees for. Requesting an estimate for
    30  	// less than this will result in an error.
    31  	minBlockTarget uint32 = 1
    32  
    33  	// minFeeUpdateTimeout represents the minimum interval in which a
    34  	// WebAPIEstimator will request fresh fees from its API.
    35  	minFeeUpdateTimeout = 5 * time.Minute
    36  
    37  	// maxFeeUpdateTimeout represents the maximum interval in which a
    38  	// WebAPIEstimator will request fresh fees from its API.
    39  	maxFeeUpdateTimeout = 20 * time.Minute
    40  )
    41  
    42  var (
    43  	// errNoFeeRateFound is used when a given conf target cannot be found
    44  	// from the fee estimator.
    45  	errNoFeeRateFound = errors.New("no fee estimation for block target")
    46  
    47  	// errEmptyCache is used when the fee rate cache is empty.
    48  	errEmptyCache = errors.New("fee rate cache is empty")
    49  )
    50  
    51  // Estimator provides the ability to estimate on-chain transaction fees for
    52  // various combinations of transaction sizes and desired confirmation time
    53  // (measured by number of blocks).
    54  type Estimator interface {
    55  	// EstimateFeePerKB takes in a target for the number of blocks until
    56  	// an initial confirmation and returns the estimated fee expressed in
    57  	// atoms/byte.
    58  	EstimateFeePerKB(numBlocks uint32) (AtomPerKByte, error)
    59  
    60  	// Start signals the Estimator to start any processes or goroutines
    61  	// it needs to perform its duty.
    62  	Start() error
    63  
    64  	// Stop stops any spawned goroutines and cleans up the resources used
    65  	// by the fee estimator.
    66  	Stop() error
    67  
    68  	// RelayFeePerKB returns the minimum fee rate required for transactions
    69  	// to be relayed. This is also the basis for calculation of the dust
    70  	// limit.
    71  	RelayFeePerKB() AtomPerKByte
    72  }
    73  
    74  // StaticEstimator will return a static value for all fee calculation requests.
    75  // It is designed to be replaced by a proper fee calculation implementation.
    76  // The fees are not accessible directly, because changing them would not be
    77  // thread safe.
    78  type StaticEstimator struct {
    79  	// feePerKB is the static fee rate in atoms-per-kB that will be
    80  	// returned by this fee estimator.
    81  	feePerKB AtomPerKByte
    82  
    83  	// relayFee is the minimum fee rate required for transactions to be
    84  	// relayed.
    85  	relayFee AtomPerKByte
    86  }
    87  
    88  // NewStaticEstimator returns a new static fee estimator instance.
    89  func NewStaticEstimator(feePerKB,
    90  	relayFee AtomPerKByte) *StaticEstimator {
    91  
    92  	return &StaticEstimator{
    93  		feePerKB: feePerKB,
    94  		relayFee: relayFee,
    95  	}
    96  }
    97  
    98  // EstimateFeePerKB will return the static value for fee calculations.
    99  //
   100  // NOTE: This method is part of the FeeEstimator interface.
   101  func (e StaticEstimator) EstimateFeePerKB(numBlocks uint32) (AtomPerKByte, error) {
   102  	return e.feePerKB, nil
   103  }
   104  
   105  // RelayFeePerKB returns the minimum fee rate required for transactions to be
   106  // relayed.
   107  //
   108  // NOTE: This method is part of the FeeEstimator interface.
   109  func (e StaticEstimator) RelayFeePerKB() AtomPerKByte {
   110  	return e.relayFee
   111  }
   112  
   113  // Start signals the Estimator to start any processes or goroutines
   114  // it needs to perform its duty.
   115  //
   116  // NOTE: This method is part of the Estimator interface.
   117  func (e StaticEstimator) Start() error {
   118  	return nil
   119  }
   120  
   121  // Stop stops any spawned goroutines and cleans up the resources used
   122  // by the fee estimator.
   123  //
   124  // NOTE: This method is part of the Estimator interface.
   125  func (e StaticEstimator) Stop() error {
   126  	return nil
   127  }
   128  
   129  // A compile-time assertion to ensure that StaticFeeEstimator implements the
   130  // Estimator interface.
   131  var _ Estimator = (*StaticEstimator)(nil)
   132  
   133  // DcrdEstimator is an implementation of the FeeEstimator interface backed by
   134  // the RPC interface of an active dcrd node. This implementation will proxy any
   135  // fee estimation requests to dcrd's RPC interface.
   136  type DcrdEstimator struct {
   137  	ctx    context.Context
   138  	cancel func()
   139  
   140  	// fallbackFeePerKB is the fall back fee rate in atoms/kB that is
   141  	// returned if the fee estimator does not yet have enough data to
   142  	// actually produce fee estimates.
   143  	fallbackFeePerKB AtomPerKByte
   144  
   145  	// minFeeManager is used to query the current minimum fee, in atoms/kB,
   146  	// that we should enforce. This will be used to determine fee rate for
   147  	// a transaction when the estimated fee rate is too low to allow the
   148  	// transaction to propagate through the network.
   149  	minFeeManager *minFeeManager
   150  
   151  	dcrdConn *rpcclient.Client
   152  }
   153  
   154  // NewDcrdEstimator creates a new DcrdFeeEstimator given a fully populated rpc
   155  // config that is able to successfully connect and authenticate with the dcrd
   156  // node, and also a fall back fee rate. The fallback fee rate is used in the
   157  // occasion that the estimator has insufficient data, or returns zero for a fee
   158  // estimate.
   159  func NewDcrdEstimator(rpcConfig rpcclient.ConnConfig,
   160  	fallBackFeeRate AtomPerKByte) (*DcrdEstimator, error) {
   161  
   162  	rpcConfig.DisableConnectOnNew = true
   163  	rpcConfig.DisableAutoReconnect = false
   164  	chainConn, err := rpcclient.New(&rpcConfig, nil)
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  
   169  	ctx, cancel := context.WithCancel(context.Background())
   170  
   171  	return &DcrdEstimator{
   172  		ctx:              ctx,
   173  		cancel:           cancel,
   174  		fallbackFeePerKB: fallBackFeeRate,
   175  		dcrdConn:         chainConn,
   176  	}, nil
   177  }
   178  
   179  // Start signals the Estimator to start any processes or goroutines
   180  // it needs to perform its duty.
   181  //
   182  // NOTE: This method is part of the FeeEstimator interface.
   183  func (b *DcrdEstimator) Start() error {
   184  	ctx := context.Background()
   185  	if err := b.dcrdConn.Connect(ctx, true); err != nil {
   186  		return err
   187  	}
   188  
   189  	// Once the connection to the backend node has been established, we
   190  	// can initialise the minimum relay fee manager which queries the
   191  	// chain backend for the minimum relay fee on construction.
   192  	minRelayFeeManager, err := newMinFeeManager(
   193  		defaultUpdateInterval, b.fetchMinRelayFee,
   194  	)
   195  	if err != nil {
   196  		return err
   197  
   198  	}
   199  	b.minFeeManager = minRelayFeeManager
   200  
   201  	return nil
   202  }
   203  
   204  func (b *DcrdEstimator) fetchMinRelayFee() (AtomPerKByte, error) {
   205  	// Once the connection to the backend node has been established, we'll
   206  	// query it for its minimum relay fee.
   207  	info, err := b.dcrdConn.GetInfo(b.ctx)
   208  	if err != nil {
   209  		return 0, err
   210  	}
   211  
   212  	relayFee, err := dcrutil.NewAmount(info.RelayFee)
   213  	if err != nil {
   214  		return 0, err
   215  	}
   216  
   217  	// By default, we'll use the backend node's minimum relay fee as the
   218  	// minimum fee rate we'll propose for transactions. However, if this
   219  	// happens to be lower than our fee floor, we'll enforce that instead.
   220  	minFeePerKB := AtomPerKByte(relayFee)
   221  	if minFeePerKB < FeePerKBFloor {
   222  		log.Warnf("Dcrd returned fee rate of %s which is "+
   223  			"lower than the floor rate", minFeePerKB)
   224  		minFeePerKB = FeePerKBFloor
   225  	}
   226  
   227  	log.Debugf("Using minimum fee rate of %s", minFeePerKB)
   228  
   229  	return minFeePerKB, nil
   230  }
   231  
   232  // Stop stops any spawned goroutines and cleans up the resources used
   233  // by the fee estimator.
   234  //
   235  // NOTE: This method is part of the FeeEstimator interface.
   236  func (b *DcrdEstimator) Stop() error {
   237  	b.cancel()
   238  	b.dcrdConn.Shutdown()
   239  	return nil
   240  }
   241  
   242  // RelayFeePerKB returns the minimum fee rate required for transactions to be
   243  // relayed.
   244  //
   245  // NOTE: This method is part of the FeeEstimator interface.
   246  func (b *DcrdEstimator) RelayFeePerKB() AtomPerKByte {
   247  	return b.minFeeManager.fetchMinFee()
   248  }
   249  
   250  // EstimateFeePerKB queries the connected chain client for a fee estimation for
   251  // the given block range.
   252  //
   253  // NOTE: This method is part of the FeeEstimator interface.
   254  func (b *DcrdEstimator) EstimateFeePerKB(numBlocks uint32) (AtomPerKByte, error) {
   255  	if numBlocks > maxBlockTarget {
   256  		log.Debugf("conf target %d exceeds the max value, "+
   257  			"use %d instead.", numBlocks, maxBlockTarget,
   258  		)
   259  		numBlocks = maxBlockTarget
   260  	}
   261  
   262  	feeEstimate, err := b.fetchEstimate(numBlocks)
   263  	switch {
   264  	// If the estimator doesn't have enough data, or returns an error, then
   265  	// to return a proper value, then we'll return the default fall back
   266  	// fee rate.
   267  	case err != nil:
   268  		log.Errorf("unable to query estimator: %v", err)
   269  		fallthrough
   270  
   271  	case feeEstimate == 0:
   272  		return b.fallbackFeePerKB, nil
   273  	}
   274  
   275  	return feeEstimate, nil
   276  }
   277  
   278  // fetchEstimate returns a fee estimate for a transaction to be confirmed in
   279  // confTarget blocks. The estimate is returned in atom/kB.
   280  func (b *DcrdEstimator) fetchEstimate(confTarget uint32) (AtomPerKByte, error) {
   281  	// First, we'll fetch the estimate for our confirmation target.
   282  	dcrPerKB, err := b.dcrdConn.EstimateSmartFee(b.ctx, int64(confTarget),
   283  		dcrjson.EstimateSmartFeeConservative)
   284  	if err != nil {
   285  		return 0, err
   286  	}
   287  
   288  	// Next, we'll convert the returned value to atoms, as it's
   289  	// currently returned in DCR.
   290  	atoms, err := dcrutil.NewAmount(dcrPerKB.FeeRate)
   291  	if err != nil {
   292  		return 0, err
   293  	}
   294  	atomsPerKB := AtomPerKByte(atoms)
   295  
   296  	// Finally, we'll enforce our fee floor.
   297  	minFee := b.minFeeManager.fetchMinFee()
   298  	if atomsPerKB < minFee {
   299  		log.Debugf("Estimated fee rate of %s is too low, "+
   300  			"using fee floor of %s", atomsPerKB, minFee)
   301  		atomsPerKB = minFee
   302  	}
   303  
   304  	log.Debugf("Returning %s for conf target of %d",
   305  		atomsPerKB, confTarget)
   306  
   307  	return atomsPerKB, nil
   308  }
   309  
   310  // A compile-time assertion to ensure that DcrdFeeEstimator implements the
   311  // FeeEstimator interface.
   312  var _ Estimator = (*DcrdEstimator)(nil)
   313  
   314  // WebAPIFeeSource is an interface allows the WebAPIEstimator to query an
   315  // arbitrary HTTP-based fee estimator. Each new set/network will gain an
   316  // implementation of this interface in order to allow the WebAPIEstimator to
   317  // be fully generic in its logic.
   318  type WebAPIFeeSource interface {
   319  	// GenQueryURL generates the full query URL. The value returned by this
   320  	// method should be able to be used directly as a path for an HTTP GET
   321  	// request.
   322  	GenQueryURL() string
   323  
   324  	// ParseResponse attempts to parse the body of the response generated
   325  	// by the above query URL. Typically this will be JSON, but the
   326  	// specifics are left to the WebAPIFeeSource implementation.
   327  	ParseResponse(r io.Reader) (map[uint32]uint32, error)
   328  }
   329  
   330  // SparseConfFeeSource is an implementation of the WebAPIFeeSource that utilizes
   331  // a user-specified fee estimation API for Bitcoin. It expects the response
   332  // to be in the JSON format: `fee_by_block_target: { ... }` where the value maps
   333  // block targets to fee estimates (in sat per kilovbyte).
   334  type SparseConfFeeSource struct {
   335  	// URL is the fee estimation API specified by the user.
   336  	URL string
   337  }
   338  
   339  // GenQueryURL generates the full query URL. The value returned by this
   340  // method should be able to be used directly as a path for an HTTP GET
   341  // request.
   342  //
   343  // NOTE: Part of the WebAPIFeeSource interface.
   344  func (s SparseConfFeeSource) GenQueryURL() string {
   345  	return s.URL
   346  }
   347  
   348  // ParseResponse attempts to parse the body of the response generated by the
   349  // above query URL. Typically this will be JSON, but the specifics are left to
   350  // the WebAPIFeeSource implementation.
   351  //
   352  // NOTE: Part of the WebAPIFeeSource interface.
   353  func (s SparseConfFeeSource) ParseResponse(r io.Reader) (map[uint32]uint32, error) {
   354  	type jsonResp struct {
   355  		FeeByBlockTarget map[uint32]uint32 `json:"fee_by_block_target"`
   356  	}
   357  
   358  	resp := jsonResp{
   359  		FeeByBlockTarget: make(map[uint32]uint32),
   360  	}
   361  	jsonReader := json.NewDecoder(r)
   362  	if err := jsonReader.Decode(&resp); err != nil {
   363  		return nil, err
   364  	}
   365  
   366  	return resp.FeeByBlockTarget, nil
   367  }
   368  
   369  // A compile-time assertion to ensure that SparseConfFeeSource implements the
   370  // WebAPIFeeSource interface.
   371  var _ WebAPIFeeSource = (*SparseConfFeeSource)(nil)
   372  
   373  // WebAPIEstimator is an implementation of the Estimator interface that
   374  // queries an HTTP-based fee estimation from an existing web API.
   375  type WebAPIEstimator struct {
   376  	started sync.Once
   377  	stopped sync.Once
   378  
   379  	// apiSource is the backing web API source we'll use for our queries.
   380  	apiSource WebAPIFeeSource
   381  
   382  	// updateFeeTicker is the ticker responsible for updating the Estimator's
   383  	// fee estimates every time it fires.
   384  	updateFeeTicker *time.Ticker
   385  
   386  	// feeByBlockTarget is our cache for fees pulled from the API. When a
   387  	// fee estimate request comes in, we pull the estimate from this array
   388  	// rather than re-querying the API, to prevent an inadvertent DoS attack.
   389  	feesMtx          sync.Mutex
   390  	feeByBlockTarget map[uint32]uint32
   391  
   392  	// netGetter performs a GET http request to the specified URL and
   393  	// returns the response. It is exposed here to allow tests to mock the
   394  	// network.
   395  	netGetter func(url string) (*http.Response, error)
   396  
   397  	// noCache determines whether the web estimator should cache fee
   398  	// estimates.
   399  	noCache bool
   400  
   401  	quit chan struct{}
   402  	wg   sync.WaitGroup
   403  }
   404  
   405  // defaultNetGetter performs a GET request to the specified URL or times out in
   406  // at most 10 seconds.
   407  func defaultNetGetter(url string) (*http.Response, error) {
   408  	// Rather than use the default http.Client, we'll make a custom one
   409  	// which will allow us to control how long we'll wait to read the
   410  	// response from the service. This way, if the service is down or
   411  	// overloaded, we can exit early and use our default fee.
   412  	netTransport := &http.Transport{
   413  		Dial: (&net.Dialer{
   414  			Timeout: 5 * time.Second,
   415  		}).Dial,
   416  		TLSHandshakeTimeout: 5 * time.Second,
   417  	}
   418  	netClient := &http.Client{
   419  		Timeout:   time.Second * 10,
   420  		Transport: netTransport,
   421  	}
   422  
   423  	return netClient.Get(url)
   424  }
   425  
   426  // NewWebAPIEstimator creates a new WebAPIEstimator from a given URL and a
   427  // fallback default fee. The fees are updated whenever a new block is mined.
   428  func NewWebAPIEstimator(api WebAPIFeeSource, noCache bool) *WebAPIEstimator {
   429  	return &WebAPIEstimator{
   430  		apiSource:        api,
   431  		feeByBlockTarget: make(map[uint32]uint32),
   432  		netGetter:        defaultNetGetter,
   433  		noCache:          noCache,
   434  		quit:             make(chan struct{}),
   435  	}
   436  }
   437  
   438  // EstimateFeePerKB takes in a target for the number of blocks until an initial
   439  // confirmation and returns the estimated fee expressed in sat/kw.
   440  //
   441  // NOTE: This method is part of the FeeEstimator interface.
   442  func (w *WebAPIEstimator) EstimateFeePerKB(numBlocks uint32) (
   443  	AtomPerKByte, error) {
   444  
   445  	if numBlocks > maxBlockTarget {
   446  		numBlocks = maxBlockTarget
   447  	} else if numBlocks < minBlockTarget {
   448  		return 0, fmt.Errorf("conf target of %v is too low, minimum "+
   449  			"accepted is %v", numBlocks, minBlockTarget)
   450  	}
   451  
   452  	// Get fee estimates now if we don't refresh periodically.
   453  	if w.noCache {
   454  		w.updateFeeEstimates()
   455  	}
   456  
   457  	feePerKb, err := w.getCachedFee(numBlocks)
   458  
   459  	// If the estimator returns an error, a zero value fee rate will be
   460  	// returned. We will log the error and return the fall back fee rate
   461  	// instead.
   462  	if err != nil {
   463  		log.Errorf("unable to query estimator: %v", err)
   464  	}
   465  	atomsPerKB := AtomPerKByte(feePerKb)
   466  
   467  	// If the result is too low, then we'll clamp it to our current fee
   468  	// floor.
   469  	if atomsPerKB < FeePerKBFloor {
   470  		atomsPerKB = FeePerKBFloor
   471  	}
   472  
   473  	log.Debugf("Web API returning %v atoms/KB for conf target of %v",
   474  		atomsPerKB, numBlocks)
   475  
   476  	return atomsPerKB, nil
   477  }
   478  
   479  // Start signals the Estimator to start any processes or goroutines it needs
   480  // to perform its duty.
   481  //
   482  // NOTE: This method is part of the Estimator interface.
   483  func (w *WebAPIEstimator) Start() error {
   484  	// No update loop is needed when we don't cache.
   485  	if w.noCache {
   486  		return nil
   487  	}
   488  
   489  	var err error
   490  	w.started.Do(func() {
   491  		log.Infof("Starting web API fee estimator")
   492  
   493  		w.updateFeeTicker = time.NewTicker(w.randomFeeUpdateTimeout())
   494  		w.updateFeeEstimates()
   495  
   496  		w.wg.Add(1)
   497  		go w.feeUpdateManager()
   498  
   499  	})
   500  	return err
   501  }
   502  
   503  // Stop stops any spawned goroutines and cleans up the resources used by the
   504  // fee estimator.
   505  //
   506  // NOTE: This method is part of the Estimator interface.
   507  func (w *WebAPIEstimator) Stop() error {
   508  	// Update loop is not running when we don't cache.
   509  	if w.noCache {
   510  		return nil
   511  	}
   512  
   513  	w.stopped.Do(func() {
   514  		log.Infof("Stopping web API fee estimator")
   515  
   516  		w.updateFeeTicker.Stop()
   517  
   518  		close(w.quit)
   519  		w.wg.Wait()
   520  	})
   521  	return nil
   522  }
   523  
   524  // RelayFeePerKB returns the minimum fee rate required for transactions to be
   525  // relayed.
   526  //
   527  // NOTE: This method is part of the FeeEstimator interface.
   528  func (w *WebAPIEstimator) RelayFeePerKB() AtomPerKByte {
   529  	return FeePerKBFloor
   530  }
   531  
   532  // randomFeeUpdateTimeout returns a random timeout between minFeeUpdateTimeout
   533  // and maxFeeUpdateTimeout that will be used to determine how often the Estimator
   534  // should retrieve fresh fees from its API.
   535  func (w *WebAPIEstimator) randomFeeUpdateTimeout() time.Duration {
   536  	lower := int64(minFeeUpdateTimeout)
   537  	upper := int64(maxFeeUpdateTimeout)
   538  	return time.Duration(prand.Int63n(upper-lower) + lower)
   539  }
   540  
   541  // getCachedFee takes a conf target and returns the cached fee rate. When the
   542  // fee rate cannot be found, it will search the cache by decrementing the conf
   543  // target until a fee rate is found. If still not found, it will return the fee
   544  // rate of the minimum conf target cached, in other words, the most expensive
   545  // fee rate it knows of.
   546  func (w *WebAPIEstimator) getCachedFee(numBlocks uint32) (uint32, error) {
   547  	w.feesMtx.Lock()
   548  	defer w.feesMtx.Unlock()
   549  
   550  	// If the cache is empty, return an error.
   551  	if len(w.feeByBlockTarget) == 0 {
   552  		return 0, fmt.Errorf("web API error: %w", errEmptyCache)
   553  	}
   554  
   555  	// Search the conf target from the cache. We expect a query to the web
   556  	// API has been made and the result has been cached at this point.
   557  	fee, ok := w.feeByBlockTarget[numBlocks]
   558  
   559  	// If the conf target can be found, exit early.
   560  	if ok {
   561  		return fee, nil
   562  	}
   563  
   564  	// The conf target cannot be found. We will first search the cache
   565  	// using a lower conf target. This is a conservative approach as the
   566  	// fee rate returned will be larger than what's requested.
   567  	for target := numBlocks; target >= minBlockTarget; target-- {
   568  		fee, ok := w.feeByBlockTarget[target]
   569  		if !ok {
   570  			continue
   571  		}
   572  
   573  		log.Warnf("Web API does not have a fee rate for target=%d, "+
   574  			"using the fee rate for target=%d instead",
   575  			numBlocks, target)
   576  
   577  		// Return the fee rate found, which will be more expensive than
   578  		// requested. We will not cache the fee rate here in the hope
   579  		// that the web API will later populate this value.
   580  		return fee, nil
   581  	}
   582  
   583  	// There are no lower conf targets cached, which is likely when the
   584  	// requested conf target is 1. We will search the cache using a higher
   585  	// conf target, which gives a fee rate that's cheaper than requested.
   586  	//
   587  	// NOTE: we can only get here iff the requested conf target is smaller
   588  	// than the minimum conf target cached, so we return the minimum conf
   589  	// target from the cache.
   590  	minTargetCached := uint32(math.MaxUint32)
   591  	for target := range w.feeByBlockTarget {
   592  		if target < minTargetCached {
   593  			minTargetCached = target
   594  		}
   595  	}
   596  
   597  	fee, ok = w.feeByBlockTarget[minTargetCached]
   598  	if !ok {
   599  		// We should never get here, just a vanity check.
   600  		return 0, fmt.Errorf("web API error: %w, conf target: %d",
   601  			errNoFeeRateFound, numBlocks)
   602  	}
   603  
   604  	// Log an error instead of a warning as a cheaper fee rate may delay
   605  	// the confirmation for some important transactions.
   606  	log.Errorf("Web API does not have a fee rate for target=%d, "+
   607  		"using the fee rate for target=%d instead",
   608  		numBlocks, minTargetCached)
   609  
   610  	return fee, nil
   611  }
   612  
   613  // updateFeeEstimates re-queries the API for fresh fees and caches them.
   614  func (w *WebAPIEstimator) updateFeeEstimates() {
   615  	// With the client created, we'll query the API source to fetch the URL
   616  	// that we should use to query for the fee estimation.
   617  	targetURL := w.apiSource.GenQueryURL()
   618  	resp, err := w.netGetter(targetURL)
   619  	if err != nil {
   620  		log.Errorf("unable to query web api for fee response: %v",
   621  			err)
   622  		return
   623  	}
   624  	defer resp.Body.Close()
   625  
   626  	// Once we've obtained the response, we'll instruct the WebAPIFeeSource
   627  	// to parse out the body to obtain our final result.
   628  	feesByBlockTarget, err := w.apiSource.ParseResponse(resp.Body)
   629  	if err != nil {
   630  		log.Errorf("unable to query web api for fee response: %v",
   631  			err)
   632  		return
   633  	}
   634  
   635  	w.feesMtx.Lock()
   636  	w.feeByBlockTarget = feesByBlockTarget
   637  	w.feesMtx.Unlock()
   638  }
   639  
   640  // feeUpdateManager updates the fee estimates whenever a new block comes in.
   641  func (w *WebAPIEstimator) feeUpdateManager() {
   642  	defer w.wg.Done()
   643  
   644  	for {
   645  		select {
   646  		case <-w.updateFeeTicker.C:
   647  			w.updateFeeEstimates()
   648  		case <-w.quit:
   649  			return
   650  		}
   651  	}
   652  }
   653  
   654  // A compile-time assertion to ensure that WebAPIEstimator implements the
   655  // Estimator interface.
   656  var _ Estimator = (*WebAPIEstimator)(nil)