Use token id as unique key in cache for prices (#701)
Co-authored-by: walker-16 <agpazos85@gmail.com>
This commit is contained in:
parent
923ee8d337
commit
420d342612
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue