decred.org/dcrdex@v1.0.5/server/dex/feemgr.go (about) 1 package dex 2 3 import ( 4 "context" 5 "sync" 6 "sync/atomic" 7 "time" 8 9 "decred.org/dcrdex/server/asset" 10 "decred.org/dcrdex/server/market" 11 ) 12 13 // FeeManager manages FeeFetchers. 14 type FeeManager struct { 15 fetchers map[uint32]*feeFetcher 16 } 17 18 var _ market.FeeSource = (*FeeManager)(nil) 19 20 // NewFeeManager is the constructor for a FeeManager. 21 func NewFeeManager() *FeeManager { 22 return &FeeManager{ 23 fetchers: make(map[uint32]*feeFetcher), 24 } 25 } 26 27 // AddFetcher adds a FeeFetcher for an asset. 28 func (m *FeeManager) AddFetcher(asset *asset.BackedAsset) { 29 m.fetchers[asset.ID] = newFeeFetcher(asset) 30 } 31 32 // FeeFetcher returns the specified asset's FeeFetcher. 33 func (m *FeeManager) FeeFetcher(assetID uint32) market.FeeFetcher { 34 return m.fetchers[assetID] 35 } 36 37 // LastRate is the last rate cached for the specified asset. 38 func (m *FeeManager) LastRate(assetID uint32) uint64 { 39 return m.fetchers[assetID].LastRate() 40 } 41 42 // feeFetcher implements market.FeeFetcher and updates the last fee rate cache. 43 type feeFetcher struct { 44 *asset.BackedAsset 45 lastRate *uint64 46 47 // Stash the old rate for a short time to avoid a race condition where 48 // a client gets a rate right before the server gets a higher rate, then the 49 // client tries to use the old rate and is rejected. 50 stashedRate struct { 51 sync.RWMutex 52 rate uint64 53 stamp time.Time 54 } 55 } 56 57 var _ market.FeeFetcher = (*feeFetcher)(nil) 58 59 // newFeeFetcher is the constructor for a *feeFetcher. 60 func newFeeFetcher(asset *asset.BackedAsset) *feeFetcher { 61 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 62 defer cancel() 63 r, err := asset.Backend.FeeRate(ctx) 64 if err != nil { 65 log.Warnf("Error priming fee cache for %s: %v", asset.Symbol, err) 66 } 67 if r > asset.MaxFeeRate { 68 r = asset.MaxFeeRate 69 } 70 return &feeFetcher{ 71 BackedAsset: asset, 72 lastRate: &r, 73 } 74 } 75 76 // Use the lower rate for a minute after a rate increase to avoid races. 77 const stashedRateExpiry = time.Minute 78 79 // FeeRate fetches a new fee rate and updates the cache. 80 func (f *feeFetcher) FeeRate(ctx context.Context) uint64 { 81 r, err := f.Backend.FeeRate(ctx) 82 if err != nil { 83 log.Errorf("Error retrieving fee rate for %s: %v", f.Symbol, err) 84 return 0 // Do not store as last rate. 85 } 86 if r <= 0 { 87 return 0 88 } 89 if r > f.Asset.MaxFeeRate { 90 r = f.Asset.MaxFeeRate 91 } 92 oldRate := atomic.SwapUint64(f.lastRate, r) 93 if oldRate < r { 94 f.stashedRate.Lock() 95 f.stashedRate.rate = oldRate 96 f.stashedRate.stamp = time.Now() 97 f.stashedRate.Unlock() 98 return oldRate 99 } 100 f.stashedRate.RLock() 101 if time.Since(f.stashedRate.stamp) < stashedRateExpiry && f.stashedRate.rate < r { 102 r = f.stashedRate.rate 103 } 104 f.stashedRate.RUnlock() 105 return r 106 } 107 108 // LastRate is the last rate cached. This may be used as a fallback if FeeRate 109 // times out, or as a quick rate when rate freshness is not critical. 110 func (f *feeFetcher) LastRate() uint64 { 111 r := atomic.LoadUint64(f.lastRate) 112 f.stashedRate.RLock() 113 if time.Since(f.stashedRate.stamp) < stashedRateExpiry && f.stashedRate.rate < r { 114 r = f.stashedRate.rate 115 } 116 f.stashedRate.RUnlock() 117 return r 118 } 119 120 // MaxFeeRate is a getter for the BackedAsset's dex.Asset.MaxFeeRate. This is 121 // provided so consumers that operate on the returned FeeRate can respect the 122 // configured limit e.g. ScaleFeeRate in (*Market).processReadyEpoch. 123 func (f *feeFetcher) MaxFeeRate() uint64 { 124 return f.Asset.MaxFeeRate 125 } 126 127 // SwapFeeRate returns the tx fee that needs to be used to initiate a swap. 128 // This fee will be the max fee rate if the asset supports dynamic tx fees, 129 // and otherwise it will be the current market fee rate. 130 func (f *feeFetcher) SwapFeeRate(ctx context.Context) uint64 { 131 if f.Backend.Info().SupportsDynamicTxFee { 132 return f.MaxFeeRate() 133 } 134 return f.FeeRate(ctx) 135 }