From 5cc39ac2728653abc3628d81bd21296f06109369 Mon Sep 17 00:00:00 2001 From: Sean Hagstrom Date: Fri, 13 Sep 2024 12:17:09 +0100 Subject: [PATCH] feat_: add in-memory caching support for token market values --- services/wallet/market/market.go | 87 ++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/services/wallet/market/market.go b/services/wallet/market/market.go index db40775e2..376f80cb5 100644 --- a/services/wallet/market/market.go +++ b/services/wallet/market/market.go @@ -23,12 +23,21 @@ type DataPoint struct { UpdatedAt int64 } +type MarketValuesSnapshot struct { + MarketValues thirdparty.TokenMarketValues + UpdatedAt int64 +} + type DataPerTokenAndCurrency = map[string]map[string]DataPoint +type MarketValuesPerToken = map[string]MarketValuesSnapshot +type MarketValuesPerCurrencyAndToken = map[string]MarketValuesPerToken type Manager struct { feed *event.Feed priceCache DataPerTokenAndCurrency priceCacheLock sync.RWMutex + marketCache MarketValuesPerCurrencyAndToken + marketCacheLock sync.RWMutex IsConnected bool LastCheckedAt int64 IsConnectedLock sync.RWMutex @@ -47,6 +56,7 @@ func NewManager(providers []thirdparty.MarketDataProvider, feed *event.Feed) *Ma return &Manager{ feed: feed, priceCache: make(DataPerTokenAndCurrency), + marketCache: make(MarketValuesPerCurrencyAndToken), IsConnected: true, LastCheckedAt: time.Now().Unix(), circuitbreaker: cb, @@ -133,9 +143,86 @@ func (pm *Manager) FetchTokenMarketValues(symbols []string, currency string) (ma } marketValues := result.(map[string]thirdparty.TokenMarketValues) + pm.updateMarketCache(currency, marketValues) return marketValues, nil } +func (pm *Manager) GetCachedTokenMarketValues() MarketValuesPerCurrencyAndToken { + pm.marketCacheLock.RLock() + defer pm.marketCacheLock.RUnlock() + return pm.marketCache +} + +func (pm *Manager) GetOrFetchTokenMarketValues(symbols []string, currency string, maxAgeInSeconds int64) (map[string]thirdparty.TokenMarketValues, error) { + symbolsToFetchMap := make(map[string]bool) + symbolsToFetch := make([]string, 0, len(symbols)) + + now := time.Now().Unix() + tokenMarketValuesCache, ok := pm.GetCachedTokenMarketValues()[currency] + if !ok { + return pm.FetchTokenMarketValues(symbols, currency) + } + + for _, symbol := range symbols { + marketValueSnapshot, found := tokenMarketValuesCache[symbol] + if !found { + if !symbolsToFetchMap[symbol] { + symbolsToFetchMap[symbol] = true + symbolsToFetch = append(symbolsToFetch, symbol) + } + continue + } + if now-marketValueSnapshot.UpdatedAt > maxAgeInSeconds { + if !symbolsToFetchMap[symbol] { + symbolsToFetchMap[symbol] = true + symbolsToFetch = append(symbolsToFetch, symbol) + } + continue + } + } + + if len(symbolsToFetch) > 0 { + _, err := pm.FetchTokenMarketValues(symbolsToFetch, currency) + if err != nil { + return nil, err + } + } + + tokenMarketValues := pm.getCachedTokenMarketValuesFor(currency, symbols) + return tokenMarketValues, nil +} + +func (pm *Manager) updateMarketCache(currency string, marketValuesPerToken map[string]thirdparty.TokenMarketValues) { + pm.marketCacheLock.Lock() + defer pm.marketCacheLock.Unlock() + + for token, tokenMarketValues := range marketValuesPerToken { + _, present := pm.marketCache[currency] + if !present { + pm.marketCache[currency] = make(map[string]MarketValuesSnapshot) + } + + pm.marketCache[currency][token] = MarketValuesSnapshot{ + UpdatedAt: time.Now().Unix(), + MarketValues: tokenMarketValues, + } + } +} + +func (pm *Manager) getCachedTokenMarketValuesFor(currency string, symbols []string) map[string]thirdparty.TokenMarketValues { + tokenMarketValues := make(map[string]thirdparty.TokenMarketValues) + + if cachedTokenMarketValues, ok := pm.marketCache[currency]; ok { + for _, symbol := range symbols { + if marketValuesSnapshot, found := cachedTokenMarketValues[symbol]; found { + tokenMarketValues[symbol] = marketValuesSnapshot.MarketValues + } + } + } + + return tokenMarketValues +} + func (pm *Manager) FetchTokenDetails(symbols []string) (map[string]thirdparty.TokenDetails, error) { result, err := pm.makeCall(pm.providers, func(provider thirdparty.MarketDataProvider) (interface{}, error) { return provider.FetchTokenDetails(symbols)