decred.org/dcrdex@v1.0.3/server/dex/feemgr.go (about)

     1  package dex
     2  
     3  import (
     4  	"context"
     5  	"strconv"
     6  	"sync/atomic"
     7  	"time"
     8  
     9  	"decred.org/dcrdex/server/asset"
    10  	"decred.org/dcrdex/server/market"
    11  )
    12  
    13  // FeeManager manages fee fetchers and a fee cache.
    14  type FeeManager struct {
    15  	assets map[uint32]*asset.BackedAsset
    16  	cache  map[uint32]*uint64
    17  }
    18  
    19  var _ market.FeeSource = (*FeeManager)(nil)
    20  
    21  // NewFeeManager is the constructor for a FeeManager.
    22  func NewFeeManager() *FeeManager {
    23  	return &FeeManager{
    24  		assets: make(map[uint32]*asset.BackedAsset),
    25  		cache:  make(map[uint32]*uint64),
    26  	}
    27  }
    28  
    29  // AddFetcher adds a fee fetcher (a *BackedAsset) and primes the cache. The
    30  // asset's MaxFeeRate are used to limit the rates returned by the LastRate
    31  // method as well as the rates returned by child FeeFetchers.
    32  func (m *FeeManager) AddFetcher(asset *asset.BackedAsset) {
    33  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    34  	defer cancel()
    35  	rate, err := asset.Backend.FeeRate(ctx)
    36  	if err != nil {
    37  		log.Warnf("Error priming fee cache for %s: %v", asset.Symbol, err)
    38  	}
    39  	if rate > asset.MaxFeeRate {
    40  		rate = asset.MaxFeeRate
    41  	}
    42  	m.cache[asset.ID] = &rate
    43  	m.assets[asset.ID] = asset
    44  }
    45  
    46  // FeeFetcher creates and returns an asset-specific fetcher that satisfies
    47  // market.FeeFetcher, implemented by *feeFetcher.
    48  func (m *FeeManager) FeeFetcher(assetID uint32) market.FeeFetcher {
    49  	asset := m.assets[assetID]
    50  	if asset == nil {
    51  		panic("no fetcher for " + strconv.Itoa(int(assetID)))
    52  	}
    53  	return newFeeFetcher(asset, m.cache[assetID])
    54  }
    55  
    56  // LastRate is the last rate cached for the specified asset.
    57  func (m *FeeManager) LastRate(assetID uint32) uint64 {
    58  	r := m.cache[assetID]
    59  	if r == nil {
    60  		return 0
    61  	}
    62  	return atomic.LoadUint64(r)
    63  }
    64  
    65  // feeFetcher implements market.FeeFetcher and updates the last fee rate cache.
    66  type feeFetcher struct {
    67  	*asset.BackedAsset
    68  	lastRate *uint64
    69  }
    70  
    71  var _ market.FeeFetcher = (*feeFetcher)(nil)
    72  
    73  // newFeeFetcher is the constructor for a *feeFetcher.
    74  func newFeeFetcher(asset *asset.BackedAsset, lastRate *uint64) *feeFetcher {
    75  	return &feeFetcher{
    76  		BackedAsset: asset,
    77  		lastRate:    lastRate,
    78  	}
    79  }
    80  
    81  // FeeRate fetches a new fee rate and updates the cache.
    82  func (f *feeFetcher) FeeRate(ctx context.Context) uint64 {
    83  	r, err := f.Backend.FeeRate(ctx)
    84  	if err != nil {
    85  		log.Errorf("Error retrieving fee rate for %s: %v", f.Symbol, err)
    86  		return 0 // Do not store as last rate.
    87  	}
    88  	if r <= 0 {
    89  		return 0
    90  	}
    91  	if r > f.Asset.MaxFeeRate {
    92  		r = f.Asset.MaxFeeRate
    93  	}
    94  	atomic.StoreUint64(f.lastRate, r)
    95  	return r
    96  }
    97  
    98  // LastRate is the last rate cached. This may be used as a fallback if FeeRate
    99  // times out, or as a quick rate when rate freshness is not critical.
   100  func (f *feeFetcher) LastRate() uint64 {
   101  	return atomic.LoadUint64(f.lastRate)
   102  }
   103  
   104  // MaxFeeRate is a getter for the BackedAsset's dex.Asset.MaxFeeRate. This is
   105  // provided so consumers that operate on the returned FeeRate can respect the
   106  // configured limit e.g. ScaleFeeRate in (*Market).processReadyEpoch.
   107  func (f *feeFetcher) MaxFeeRate() uint64 {
   108  	return f.Asset.MaxFeeRate
   109  }
   110  
   111  // SwapFeeRate returns the tx fee that needs to be used to initiate a swap.
   112  // This fee will be the max fee rate if the asset supports dynamic tx fees,
   113  // and otherwise it will be the current market fee rate.
   114  func (f *feeFetcher) SwapFeeRate(ctx context.Context) uint64 {
   115  	if f.Backend.Info().SupportsDynamicTxFee {
   116  		return f.MaxFeeRate()
   117  	}
   118  	return f.FeeRate(ctx)
   119  }