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 }