Use token id as unique key in cache for prices (#701)

Co-authored-by: walker-16 <agpazos85@gmail.com>
This commit is contained in:
ftocal 2023-09-22 16:14:02 -03:00 committed by GitHub
parent 923ee8d337
commit 420d342612
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 39 additions and 42 deletions

View File

@ -43,11 +43,11 @@ func (c *VaaConverter) Convert(vaaBytes []byte) (string, error) {
} }
// Look up token metadata // Look up token metadata
tokenMetadata, ok := domain.GetTokenByAddress(vaa.EmitterChain, payload.OriginAddress.String()) tokenMetadata, ok := domain.GetTokenByAddress(payload.OriginChain, payload.OriginAddress.String())
if !ok { if !ok {
// if not found, add to missing tokens // if not found, add to missing tokens
c.MissingTokens[payload.OriginAddress] = vaa.EmitterChain c.MissingTokens[payload.OriginAddress] = payload.OriginChain
c.MissingTokensCounter[payload.OriginAddress] = c.MissingTokensCounter[payload.OriginAddress] + 1 c.MissingTokensCounter[payload.OriginAddress] = c.MissingTokensCounter[payload.OriginAddress] + 1
return "", fmt.Errorf("unknown token: %s %s", payload.OriginChain.String(), payload.OriginAddress.String()) return "", fmt.Errorf("unknown token: %s %s", payload.OriginChain.String(), payload.OriginAddress.String())
@ -58,7 +58,7 @@ func (c *VaaConverter) Convert(vaaBytes []byte) (string, error) {
{ {
p := metric.MakePointForVaaVolumeParams{ p := metric.MakePointForVaaVolumeParams{
Vaa: vaa, Vaa: vaa,
TokenPriceFunc: func(_ domain.Symbol, timestamp time.Time) (decimal.Decimal, error) { TokenPriceFunc: func(_ string, timestamp time.Time) (decimal.Decimal, error) {
// fetch the historic price from cache // fetch the historic price from cache
price, err := c.PriceCache.GetPriceByTime(tokenMetadata.CoingeckoID, timestamp) price, err := c.PriceCache.GetPriceByTime(tokenMetadata.CoingeckoID, timestamp)

View File

@ -85,9 +85,9 @@ func (m *Metric) Push(ctx context.Context, vaa *sdk.VAA) error {
m.logger, m.logger,
vaa, vaa,
m.transferPrices, m.transferPrices,
func(symbol domain.Symbol, timestamp time.Time) (decimal.Decimal, error) { func(tokenID string, timestamp time.Time) (decimal.Decimal, error) {
priceData, err := m.notionalCache.Get(symbol) priceData, err := m.notionalCache.Get(tokenID)
if err != nil { if err != nil {
return decimal.NewFromInt(0), err return decimal.NewFromInt(0), err
} }
@ -192,9 +192,9 @@ func (m *Metric) volumeMeasurement(ctx context.Context, vaa *sdk.VAA) error {
p := MakePointForVaaVolumeParams{ p := MakePointForVaaVolumeParams{
Logger: m.logger, Logger: m.logger,
Vaa: vaa, Vaa: vaa,
TokenPriceFunc: func(symbol domain.Symbol, timestamp time.Time) (decimal.Decimal, error) { TokenPriceFunc: func(tokenID string, timestamp time.Time) (decimal.Decimal, error) {
priceData, err := m.notionalCache.Get(symbol) priceData, err := m.notionalCache.Get(tokenID)
if err != nil { if err != nil {
return decimal.NewFromInt(0), err return decimal.NewFromInt(0), err
} }
@ -257,7 +257,7 @@ type MakePointForVaaVolumeParams struct {
Vaa *sdk.VAA Vaa *sdk.VAA
// TokenPriceFunc returns the price of the given token at the specified timestamp. // TokenPriceFunc returns the price of the given token at the specified timestamp.
TokenPriceFunc func(symbol domain.Symbol, timestamp time.Time) (decimal.Decimal, error) TokenPriceFunc func(tokenID string, timestamp time.Time) (decimal.Decimal, error)
// Logger is an optional parameter, in case the caller wants additional visibility. // Logger is an optional parameter, in case the caller wants additional visibility.
Logger *zap.Logger Logger *zap.Logger
@ -349,7 +349,7 @@ func MakePointForVaaVolume(params *MakePointForVaaVolumeParams) (*write.Point, e
} }
// Try to obtain the token notional value from the cache // Try to obtain the token notional value from the cache
notionalUSD, err := params.TokenPriceFunc(tokenMeta.Symbol, params.Vaa.Timestamp) notionalUSD, err := params.TokenPriceFunc(tokenMeta.GetTokenID(), params.Vaa.Timestamp)
if err != nil { if err != nil {
params.Metrics.IncMissingNotional(tokenMeta.Symbol.String()) params.Metrics.IncMissingNotional(tokenMeta.Symbol.String())
if params.Logger != nil { if params.Logger != nil {

View File

@ -34,7 +34,7 @@ func upsertTransferPrices(
logger *zap.Logger, logger *zap.Logger,
vaa *sdk.VAA, vaa *sdk.VAA,
transferPrices *mongo.Collection, transferPrices *mongo.Collection,
tokenPriceFunc func(symbol domain.Symbol, timestamp time.Time) (decimal.Decimal, error), tokenPriceFunc func(tokenID string, timestamp time.Time) (decimal.Decimal, error),
) error { ) error {
// Do not generate this metric for PythNet VAAs // Do not generate this metric for PythNet VAAs
@ -57,7 +57,7 @@ func upsertTransferPrices(
} }
// Try to obtain the token notional value from the cache // Try to obtain the token notional value from the cache
notionalUSD, err := tokenPriceFunc(tokenMeta.Symbol, vaa.Timestamp) notionalUSD, err := tokenPriceFunc(tokenMeta.GetTokenID(), vaa.Timestamp)
if err != nil { if err != nil {
logger.Warn("failed to obtain notional for this token", logger.Warn("failed to obtain notional for this token",
zap.String("vaaId", vaa.MessageID()), zap.String("vaaId", vaa.MessageID()),

View File

@ -10,14 +10,13 @@ import (
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
"go.uber.org/zap" "go.uber.org/zap"
) )
const ( const (
wormscanNotionalUpdated = "NOTIONAL_UPDATED" wormscanNotionalUpdated = "NOTIONAL_UPDATED"
wormscanNotionalCacheKeyRegex = "WORMSCAN:NOTIONAL:SYMBOL:*" wormscanTokenNotionalCacheKeyRegex = "WORMSCAN:NOTIONAL:TOKEN:*"
KeyFormatString = "WORMSCAN:NOTIONAL:SYMBOL:%s" KeyTokenFormatString = "WORMSCAN:NOTIONAL:TOKEN:%s"
) )
var ( var (
@ -27,7 +26,7 @@ var (
// NotionalLocalCacheReadable is the interface for notional local cache. // NotionalLocalCacheReadable is the interface for notional local cache.
type NotionalLocalCacheReadable interface { type NotionalLocalCacheReadable interface {
Get(symbol domain.Symbol) (PriceData, error) Get(tokenID string) (PriceData, error)
Close() error Close() error
} }
@ -48,7 +47,6 @@ func (p PriceData) MarshalBinary() ([]byte, error) {
type NotionalCache struct { type NotionalCache struct {
client *redis.Client client *redis.Client
pubSub *redis.PubSub pubSub *redis.PubSub
channel string
notionalMap sync.Map notionalMap sync.Map
prefix string prefix string
logger *zap.Logger logger *zap.Logger
@ -60,12 +58,10 @@ func NewNotionalCache(ctx context.Context, redisClient *redis.Client, prefix str
if redisClient == nil { if redisClient == nil {
return nil, errors.New("redis client is nil") return nil, errors.New("redis client is nil")
} }
pubsub := redisClient.Subscribe(ctx, formatChannel(prefix, channel))
pubsub := redisClient.Subscribe(ctx, channel)
return &NotionalCache{ return &NotionalCache{
client: redisClient, client: redisClient,
pubSub: pubsub, pubSub: pubsub,
channel: formatChannel(prefix, channel),
notionalMap: sync.Map{}, notionalMap: sync.Map{},
prefix: prefix, prefix: prefix,
logger: log}, nil logger: log}, nil
@ -131,6 +127,7 @@ func (c *NotionalCache) subscribe(ctx context.Context) {
go func() { go func() {
for msg := range ch { for msg := range ch {
c.logger.Info("receive message from channel", zap.String("channel", msg.Channel), zap.String("payload", msg.Payload))
if wormscanNotionalUpdated == msg.Payload { if wormscanNotionalUpdated == msg.Payload {
// update notional cache // update notional cache
c.loadCache(ctx) c.loadCache(ctx)
@ -145,11 +142,11 @@ func (c *NotionalCache) Close() error {
} }
// Get notional cache value. // Get notional cache value.
func (c *NotionalCache) Get(symbol domain.Symbol) (PriceData, error) { func (c *NotionalCache) Get(tokenID string) (PriceData, error) {
var notional PriceData var notional PriceData
// get notional cache key // get notional cache key
key := fmt.Sprintf(KeyFormatString, symbol) key := fmt.Sprintf(KeyTokenFormatString, tokenID)
key = c.renderKey(key) key = c.renderKey(key)
// get notional cache value // get notional cache value
@ -163,7 +160,7 @@ func (c *NotionalCache) Get(symbol domain.Symbol) (PriceData, error) {
if !ok { if !ok {
c.logger.Error("invalid notional cache field", c.logger.Error("invalid notional cache field",
zap.Any("field", field), zap.Any("field", field),
zap.String("symbol", symbol.String())) zap.String("tokenId", tokenID))
return notional, ErrInvalidCacheField return notional, ErrInvalidCacheField
} }
return notional, nil return notional, nil
@ -178,7 +175,7 @@ func (c *NotionalCache) renderKey(key string) string {
} }
func (c *NotionalCache) renderRegExp() string { func (c *NotionalCache) renderRegExp() string {
return "*" + c.renderKey(wormscanNotionalCacheKeyRegex) return "*" + c.renderKey(wormscanTokenNotionalCacheKeyRegex)
} }
func formatChannel(prefix string, channel string) string { func formatChannel(prefix string, channel string) string {

View File

@ -30,6 +30,10 @@ var (
tokenMetadataByCoingeckoID = make(map[string]*TokenMetadata) tokenMetadataByCoingeckoID = make(map[string]*TokenMetadata)
) )
func (t *TokenMetadata) GetTokenID() string {
return fmt.Sprintf("%d/%s", t.TokenChain, t.TokenAddress)
}
func init() { func init() {
for i := range tokenMetadata { for i := range tokenMetadata {

View File

@ -77,10 +77,10 @@ func (j *NotionalJob) Run() error {
} }
// updateNotionalCache updates the notional value of assets in cache. // updateNotionalCache updates the notional value of assets in cache.
func (j *NotionalJob) updateNotionalCache(notionals map[domain.Symbol]notional.PriceData) error { func (j *NotionalJob) updateNotionalCache(notionals map[string]notional.PriceData) error {
for chainID, n := range notionals { for chainID, n := range notionals {
key := j.renderKey(fmt.Sprintf(notional.KeyFormatString, chainID)) key := j.renderKey(fmt.Sprintf(notional.KeyTokenFormatString, chainID))
err := j.cacheClient.Set(key, n, 0).Err() err := j.cacheClient.Set(key, n, 0).Err()
if err != nil { if err != nil {
return err return err
@ -93,28 +93,24 @@ func (j *NotionalJob) updateNotionalCache(notionals map[domain.Symbol]notional.P
// convertToSymbols converts the coingecko response into a symbol map // convertToSymbols converts the coingecko response into a symbol map
// //
// The returned map has symbols as keys, and price data as the values. // The returned map has symbols as keys, and price data as the values.
func (j *NotionalJob) convertToSymbols(m map[string]coingecko.NotionalUSD) map[domain.Symbol]notional.PriceData { func (j *NotionalJob) convertToSymbols(m map[string]coingecko.NotionalUSD) map[string]notional.PriceData {
w := make(map[domain.Symbol]notional.PriceData, len(m)) w := make(map[string]notional.PriceData, len(m))
now := time.Now() now := time.Now()
for k, v := range m { for _, v := range domain.GetAllTokens() {
notionalUSD, ok := m[v.CoingeckoID]
// Do not update the dictionary when the token price is nil
if v.Price == nil {
j.logger.Info("skipping nil price", zap.String("coingeckoID", k))
continue
}
// Translate coingecko IDs into their associated ticker symbols
tokenMeta, ok := domain.GetTokenByCoingeckoID(k)
if !ok { if !ok {
j.logger.Info("skipping unknown coingecko ID", zap.String("coingeckoID", k)) j.logger.Info("skipping unknown coingecko ID", zap.String("coingeckoID", v.CoingeckoID))
continue continue
} }
// Do not update the dictionary when the token price is nil
// Set price data for the current symbol if notionalUSD.Price == nil {
w[tokenMeta.Symbol] = notional.PriceData{NotionalUsd: *v.Price, UpdatedAt: now} j.logger.Info("skipping nil price", zap.String("coingeckoID", v.CoingeckoID))
continue
}
// Set price data for the current token
w[v.GetTokenID()] = notional.PriceData{NotionalUsd: *notionalUSD.Price, UpdatedAt: now}
} }
return w return w