2023-02-28 12:58:26 -08:00
|
|
|
package metric
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-05-04 16:17:03 -07:00
|
|
|
"fmt"
|
|
|
|
"math/big"
|
2023-03-07 11:25:42 -08:00
|
|
|
"strconv"
|
|
|
|
"time"
|
2023-02-28 12:58:26 -08:00
|
|
|
|
|
|
|
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
|
|
|
|
"github.com/influxdata/influxdb-client-go/v2/api"
|
2023-05-23 07:27:23 -07:00
|
|
|
"github.com/influxdata/influxdb-client-go/v2/api/write"
|
2023-05-31 12:24:40 -07:00
|
|
|
"github.com/shopspring/decimal"
|
2023-09-25 12:56:05 -07:00
|
|
|
"github.com/wormhole-foundation/wormhole-explorer/analytics/cmd/token"
|
2023-07-03 07:51:20 -07:00
|
|
|
"github.com/wormhole-foundation/wormhole-explorer/analytics/internal/metrics"
|
2023-05-04 16:17:03 -07:00
|
|
|
wormscanNotionalCache "github.com/wormhole-foundation/wormhole-explorer/common/client/cache/notional"
|
|
|
|
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
|
|
|
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
|
2023-06-07 08:18:38 -07:00
|
|
|
"go.mongodb.org/mongo-driver/mongo"
|
2023-02-28 12:58:26 -08:00
|
|
|
"go.uber.org/zap"
|
|
|
|
)
|
|
|
|
|
2023-07-03 07:51:20 -07:00
|
|
|
const (
|
2023-10-02 07:21:13 -07:00
|
|
|
VaaCountMeasurement = "vaa_count"
|
|
|
|
VaaVolumeMeasurement = "vaa_volume_v2"
|
|
|
|
VaaAllMessagesMeasurement = "vaa_count_all_messages"
|
2023-07-03 07:51:20 -07:00
|
|
|
)
|
|
|
|
|
2023-02-28 12:58:26 -08:00
|
|
|
// Metric definition.
|
|
|
|
type Metric struct {
|
2023-06-07 08:18:38 -07:00
|
|
|
db *mongo.Database
|
|
|
|
// transferPrices contains the notional price for each token bridge transfer.
|
2023-09-25 12:56:05 -07:00
|
|
|
transferPrices *mongo.Collection
|
|
|
|
influxCli influxdb2.Client
|
|
|
|
apiBucketInfinite api.WriteAPIBlocking
|
|
|
|
apiBucket30Days api.WriteAPIBlocking
|
|
|
|
apiBucket24Hours api.WriteAPIBlocking
|
|
|
|
notionalCache wormscanNotionalCache.NotionalLocalCacheReadable
|
|
|
|
metrics metrics.Metrics
|
|
|
|
getTransferredTokenByVaa token.GetTransferredTokenByVaa
|
2023-11-28 05:16:40 -08:00
|
|
|
tokenProvider *domain.TokenProvider
|
2023-09-25 12:56:05 -07:00
|
|
|
logger *zap.Logger
|
2023-02-28 12:58:26 -08:00
|
|
|
}
|
|
|
|
|
2023-03-07 11:25:42 -08:00
|
|
|
// New create a new *Metric.
|
2023-05-04 16:17:03 -07:00
|
|
|
func New(
|
|
|
|
ctx context.Context,
|
2023-06-07 08:18:38 -07:00
|
|
|
db *mongo.Database,
|
2023-05-04 16:17:03 -07:00
|
|
|
influxCli influxdb2.Client,
|
2023-05-10 14:18:32 -07:00
|
|
|
organization string,
|
|
|
|
bucketInifite string,
|
|
|
|
bucket30Days string,
|
2023-05-18 07:14:36 -07:00
|
|
|
bucket24Hours string,
|
2023-05-10 14:18:32 -07:00
|
|
|
notionalCache wormscanNotionalCache.NotionalLocalCacheReadable,
|
2023-07-03 07:51:20 -07:00
|
|
|
metrics metrics.Metrics,
|
2023-09-25 12:56:05 -07:00
|
|
|
getTransferredTokenByVaa token.GetTransferredTokenByVaa,
|
2023-11-28 05:16:40 -08:00
|
|
|
tokenProvider *domain.TokenProvider,
|
2023-05-04 16:17:03 -07:00
|
|
|
logger *zap.Logger,
|
|
|
|
) (*Metric, error) {
|
|
|
|
|
2023-05-10 14:18:32 -07:00
|
|
|
apiBucketInfinite := influxCli.WriteAPIBlocking(organization, bucketInifite)
|
|
|
|
apiBucket30Days := influxCli.WriteAPIBlocking(organization, bucket30Days)
|
2023-05-18 07:14:36 -07:00
|
|
|
apiBucket24Hours := influxCli.WriteAPIBlocking(organization, bucket24Hours)
|
|
|
|
apiBucket24Hours.EnableBatching()
|
2023-05-04 16:17:03 -07:00
|
|
|
|
|
|
|
m := Metric{
|
2023-09-25 12:56:05 -07:00
|
|
|
db: db,
|
|
|
|
transferPrices: db.Collection("transferPrices"),
|
|
|
|
influxCli: influxCli,
|
|
|
|
apiBucketInfinite: apiBucketInfinite,
|
|
|
|
apiBucket24Hours: apiBucket24Hours,
|
|
|
|
apiBucket30Days: apiBucket30Days,
|
|
|
|
logger: logger,
|
|
|
|
notionalCache: notionalCache,
|
|
|
|
metrics: metrics,
|
|
|
|
getTransferredTokenByVaa: getTransferredTokenByVaa,
|
2023-11-28 05:16:40 -08:00
|
|
|
tokenProvider: tokenProvider,
|
2023-05-04 16:17:03 -07:00
|
|
|
}
|
|
|
|
return &m, nil
|
|
|
|
}
|
|
|
|
|
2023-03-07 11:25:42 -08:00
|
|
|
// Push implement MetricPushFunc definition.
|
2023-11-27 07:31:35 -08:00
|
|
|
func (m *Metric) Push(ctx context.Context, params *Params) error {
|
2023-05-04 16:17:03 -07:00
|
|
|
|
2023-11-27 07:31:35 -08:00
|
|
|
var err1, err2, err3, err4 error
|
2023-06-07 08:18:38 -07:00
|
|
|
|
2023-11-27 07:31:35 -08:00
|
|
|
isVaaSigned := params.VaaIsSigned
|
2023-06-07 08:18:38 -07:00
|
|
|
|
2023-11-27 07:31:35 -08:00
|
|
|
if isVaaSigned {
|
|
|
|
err1 = m.vaaCountMeasurement(ctx, params)
|
|
|
|
|
|
|
|
err2 = m.vaaCountAllMessagesMeasurement(ctx, params)
|
2023-09-25 12:56:05 -07:00
|
|
|
}
|
|
|
|
|
2023-11-27 07:31:35 -08:00
|
|
|
if params.Vaa.EmitterChain != sdk.ChainIDPythNet {
|
2023-05-04 16:17:03 -07:00
|
|
|
|
2023-11-27 07:31:35 -08:00
|
|
|
transferredToken, err := m.getTransferredTokenByVaa(ctx, params.Vaa)
|
|
|
|
if err != nil {
|
2024-01-30 10:27:48 -08:00
|
|
|
if !token.IsUnknownTokenErr(err) {
|
2023-11-27 07:31:35 -08:00
|
|
|
m.logger.Error("Failed to obtain transferred token for this VAA",
|
|
|
|
zap.String("trackId", params.TrackID),
|
|
|
|
zap.String("vaaId", params.Vaa.MessageID()),
|
|
|
|
zap.Error(err))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2023-06-07 08:18:38 -07:00
|
|
|
|
2023-11-27 07:31:35 -08:00
|
|
|
if transferredToken != nil {
|
|
|
|
|
|
|
|
if isVaaSigned {
|
2023-11-28 05:16:40 -08:00
|
|
|
err3 = m.volumeMeasurement(ctx, params, transferredToken.Clone())
|
2023-06-07 08:18:38 -07:00
|
|
|
}
|
2023-11-27 07:31:35 -08:00
|
|
|
|
2024-02-27 10:17:59 -08:00
|
|
|
err4 = UpsertTransferPrices(
|
2023-11-27 07:31:35 -08:00
|
|
|
ctx,
|
|
|
|
m.logger,
|
|
|
|
params.Vaa,
|
|
|
|
m.transferPrices,
|
2024-02-27 10:17:59 -08:00
|
|
|
func(tokenID, _ string, timestamp time.Time) (decimal.Decimal, error) {
|
2023-11-27 07:31:35 -08:00
|
|
|
|
|
|
|
priceData, err := m.notionalCache.Get(tokenID)
|
|
|
|
if err != nil {
|
|
|
|
return decimal.NewFromInt(0), err
|
|
|
|
}
|
|
|
|
return priceData.NotionalUsd, nil
|
|
|
|
},
|
|
|
|
transferredToken.Clone(),
|
2023-11-28 05:16:40 -08:00
|
|
|
m.tokenProvider,
|
2023-11-27 07:31:35 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
m.logger.Warn("Cannot obtain transferred token for this VAA",
|
2024-01-30 10:27:48 -08:00
|
|
|
zap.Error(err),
|
2023-11-27 07:31:35 -08:00
|
|
|
zap.String("trackId", params.TrackID),
|
|
|
|
zap.String("vaaId", params.Vaa.MessageID()),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2023-06-07 08:18:38 -07:00
|
|
|
|
|
|
|
//TODO if we had go 1.20, we could just use `errors.Join(err1, err2, err3, ...)` here.
|
|
|
|
if err1 != nil || err2 != nil || err3 != nil || err4 != nil {
|
|
|
|
return fmt.Errorf("err1=%w, err2=%w, err3=%w err4=%w", err1, err2, err3, err4)
|
2023-05-10 09:15:37 -07:00
|
|
|
}
|
2023-05-18 07:14:36 -07:00
|
|
|
|
2023-11-28 05:16:40 -08:00
|
|
|
if params.Vaa.EmitterChain != sdk.ChainIDPythNet {
|
|
|
|
m.logger.Info("Transaction processed successfully",
|
|
|
|
zap.String("trackId", params.TrackID),
|
2024-01-30 10:27:48 -08:00
|
|
|
zap.Bool("isVaaSigned", isVaaSigned),
|
2023-11-28 05:16:40 -08:00
|
|
|
zap.String("vaaId", params.Vaa.MessageID()))
|
|
|
|
}
|
|
|
|
|
2023-05-10 09:15:37 -07:00
|
|
|
return nil
|
2023-02-28 12:58:26 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Close influx client.
|
|
|
|
func (m *Metric) Close() {
|
2023-05-18 07:14:36 -07:00
|
|
|
|
|
|
|
const flushTimeout = 5 * time.Second
|
|
|
|
|
|
|
|
// wait a bounded amount of time for all buckets to flush
|
|
|
|
ctx, cancelFunc := context.WithTimeout(context.Background(), flushTimeout)
|
|
|
|
m.apiBucket24Hours.Flush(ctx)
|
|
|
|
m.apiBucket30Days.Flush(ctx)
|
|
|
|
m.apiBucketInfinite.Flush(ctx)
|
|
|
|
cancelFunc()
|
|
|
|
|
2023-02-28 12:58:26 -08:00
|
|
|
m.influxCli.Close()
|
|
|
|
}
|
|
|
|
|
2023-05-04 16:17:03 -07:00
|
|
|
// vaaCountMeasurement creates a new point for the `vaa_count` measurement.
|
2023-11-27 07:31:35 -08:00
|
|
|
func (m *Metric) vaaCountMeasurement(ctx context.Context, p *Params) error {
|
2023-04-12 13:51:16 -07:00
|
|
|
|
2023-05-23 07:27:23 -07:00
|
|
|
// Create a new point
|
2023-11-27 07:31:35 -08:00
|
|
|
point, err := MakePointForVaaCount(p.Vaa)
|
2023-05-23 07:27:23 -07:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to generate data point for vaa count measurement: %w", err)
|
|
|
|
}
|
|
|
|
if point == nil {
|
|
|
|
// Some VAAs don't generate any data points for this metric (e.g.: PythNet)
|
2023-05-18 07:14:36 -07:00
|
|
|
return nil
|
|
|
|
}
|
2024-03-12 08:21:13 -07:00
|
|
|
|
|
|
|
// Ignore vaa older than 30 days
|
|
|
|
thirtyDaysBefore := time.Now().AddDate(0, 0, -30)
|
|
|
|
if p.Vaa.Timestamp.Before(thirtyDaysBefore) {
|
|
|
|
return nil
|
|
|
|
}
|
2023-05-18 07:14:36 -07:00
|
|
|
|
2023-05-04 16:17:03 -07:00
|
|
|
// Write the point to influx
|
2023-05-23 07:27:23 -07:00
|
|
|
err = m.apiBucket30Days.WritePoint(ctx, point)
|
2023-02-28 12:58:26 -08:00
|
|
|
if err != nil {
|
2023-11-28 05:16:40 -08:00
|
|
|
m.logger.Error("Failed to write metric",
|
2023-05-23 07:27:23 -07:00
|
|
|
zap.String("measurement", point.Name()),
|
2023-11-27 07:31:35 -08:00
|
|
|
zap.Uint16("chain_id", uint16(p.Vaa.EmitterChain)),
|
2023-04-12 13:51:16 -07:00
|
|
|
zap.Error(err),
|
|
|
|
)
|
2023-10-02 07:21:13 -07:00
|
|
|
m.metrics.IncFailedMeasurement(VaaCountMeasurement)
|
2023-02-28 12:58:26 -08:00
|
|
|
return err
|
|
|
|
}
|
2023-10-02 07:21:13 -07:00
|
|
|
m.metrics.IncSuccessfulMeasurement(VaaCountMeasurement)
|
2023-04-12 13:51:16 -07:00
|
|
|
|
2023-02-28 12:58:26 -08:00
|
|
|
return nil
|
|
|
|
}
|
2023-05-04 16:17:03 -07:00
|
|
|
|
2023-05-18 07:14:36 -07:00
|
|
|
// vaaCountAllMessagesMeasurement creates a new point for the `vaa_count_all_messages` measurement.
|
2023-11-27 07:31:35 -08:00
|
|
|
func (m *Metric) vaaCountAllMessagesMeasurement(ctx context.Context, params *Params) error {
|
2023-05-18 07:14:36 -07:00
|
|
|
|
|
|
|
// Quite often we get VAAs that are older than 24 hours.
|
|
|
|
// We do not want to generate metrics for those, and moreover influxDB
|
|
|
|
// returns an error when we try to do so.
|
2023-11-27 07:31:35 -08:00
|
|
|
if time.Since(params.Vaa.Timestamp) > time.Hour*24 {
|
2023-05-18 07:14:36 -07:00
|
|
|
m.logger.Debug("vaa is older than 24 hours, skipping",
|
2023-11-27 07:31:35 -08:00
|
|
|
zap.String("trackId", params.TrackID),
|
|
|
|
zap.Time("timestamp", params.Vaa.Timestamp),
|
|
|
|
zap.String("vaaId", params.Vaa.UniqueID()),
|
2023-05-18 07:14:36 -07:00
|
|
|
)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a new point
|
|
|
|
point := influxdb2.
|
2023-10-02 07:21:13 -07:00
|
|
|
NewPointWithMeasurement(VaaAllMessagesMeasurement).
|
2023-11-27 07:31:35 -08:00
|
|
|
AddTag("chain_id", strconv.Itoa(int(params.Vaa.EmitterChain))).
|
2023-05-18 07:14:36 -07:00
|
|
|
AddField("count", 1).
|
2023-11-27 07:31:35 -08:00
|
|
|
SetTime(generateUniqueTimestamp(params.Vaa))
|
2023-05-18 07:14:36 -07:00
|
|
|
|
|
|
|
// Write the point to influx
|
|
|
|
err := m.apiBucket24Hours.WritePoint(ctx, point)
|
|
|
|
if err != nil {
|
2023-11-28 05:16:40 -08:00
|
|
|
m.logger.Error("Failed to write metric",
|
2023-10-02 07:21:13 -07:00
|
|
|
zap.String("measurement", VaaAllMessagesMeasurement),
|
2023-11-27 07:31:35 -08:00
|
|
|
zap.Uint16("chain_id", uint16(params.Vaa.EmitterChain)),
|
2023-05-18 07:14:36 -07:00
|
|
|
zap.Error(err),
|
|
|
|
)
|
2023-10-02 07:21:13 -07:00
|
|
|
m.metrics.IncFailedMeasurement(VaaAllMessagesMeasurement)
|
2023-05-18 07:14:36 -07:00
|
|
|
return err
|
|
|
|
}
|
2023-10-02 07:21:13 -07:00
|
|
|
m.metrics.IncSuccessfulMeasurement(VaaAllMessagesMeasurement)
|
2023-05-18 07:14:36 -07:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-10-02 07:21:13 -07:00
|
|
|
// volumeMeasurement creates a new point for the `vaa_volume_v2` measurement.
|
2023-11-28 05:16:40 -08:00
|
|
|
func (m *Metric) volumeMeasurement(ctx context.Context, params *Params, token *token.TransferredToken) error {
|
2023-05-04 16:17:03 -07:00
|
|
|
|
2023-05-23 07:27:23 -07:00
|
|
|
// Generate a data point for the volume metric
|
|
|
|
p := MakePointForVaaVolumeParams{
|
|
|
|
Logger: m.logger,
|
2023-11-28 05:16:40 -08:00
|
|
|
Vaa: params.Vaa,
|
2023-09-22 12:14:02 -07:00
|
|
|
TokenPriceFunc: func(tokenID string, timestamp time.Time) (decimal.Decimal, error) {
|
2023-05-23 07:27:23 -07:00
|
|
|
|
2023-09-22 12:14:02 -07:00
|
|
|
priceData, err := m.notionalCache.Get(tokenID)
|
2023-05-23 07:27:23 -07:00
|
|
|
if err != nil {
|
2023-05-31 12:24:40 -07:00
|
|
|
return decimal.NewFromInt(0), err
|
2023-05-23 07:27:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return priceData.NotionalUsd, nil
|
|
|
|
},
|
2023-09-25 12:56:05 -07:00
|
|
|
Metrics: m.metrics,
|
|
|
|
TransferredToken: token,
|
2023-11-28 05:16:40 -08:00
|
|
|
TokenProvider: m.tokenProvider,
|
2023-05-23 07:27:23 -07:00
|
|
|
}
|
|
|
|
point, err := MakePointForVaaVolume(&p)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if point == nil {
|
|
|
|
// Some VAAs don't generate any data points for this metric (e.g.: PythNet, non-token-bridge VAAs)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write the point to influx
|
|
|
|
err = m.apiBucketInfinite.WritePoint(ctx, point)
|
|
|
|
if err != nil {
|
2023-10-02 07:21:13 -07:00
|
|
|
m.metrics.IncFailedMeasurement(VaaVolumeMeasurement)
|
2023-05-23 07:27:23 -07:00
|
|
|
return err
|
|
|
|
}
|
2023-11-28 05:16:40 -08:00
|
|
|
m.logger.Debug("Wrote a data point for the volume metric",
|
|
|
|
zap.String("vaaId", params.Vaa.MessageID()),
|
|
|
|
zap.String("trackId", params.TrackID),
|
2023-05-29 06:54:09 -07:00
|
|
|
zap.String("measurement", point.Name()),
|
|
|
|
zap.Any("tags", point.TagList()),
|
|
|
|
zap.Any("fields", point.FieldList()),
|
|
|
|
)
|
2023-10-02 07:21:13 -07:00
|
|
|
m.metrics.IncSuccessfulMeasurement(VaaVolumeMeasurement)
|
2023-05-23 07:27:23 -07:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// MakePointForVaaCount generates a data point for the VAA count measurement.
|
|
|
|
//
|
|
|
|
// Some VAAs will not generate a measurement, so the caller must always check
|
|
|
|
// whether the returned point is nil.
|
|
|
|
func MakePointForVaaCount(vaa *sdk.VAA) (*write.Point, error) {
|
|
|
|
|
2023-05-18 07:14:36 -07:00
|
|
|
// Do not generate this metric for PythNet VAAs
|
|
|
|
if vaa.EmitterChain == sdk.ChainIDPythNet {
|
2023-05-23 07:27:23 -07:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a new point
|
|
|
|
point := influxdb2.
|
2023-10-02 07:21:13 -07:00
|
|
|
NewPointWithMeasurement(VaaCountMeasurement).
|
2023-05-23 07:27:23 -07:00
|
|
|
AddTag("chain_id", strconv.Itoa(int(vaa.EmitterChain))).
|
|
|
|
AddField("count", 1).
|
2023-05-29 06:54:09 -07:00
|
|
|
SetTime(generateUniqueTimestamp(vaa))
|
2023-05-23 07:27:23 -07:00
|
|
|
|
|
|
|
return point, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// MakePointForVaaVolumeParams contains input parameters for the function `MakePointForVaaVolume`
|
|
|
|
type MakePointForVaaVolumeParams struct {
|
|
|
|
|
|
|
|
// Vaa is the VAA for which we want to compute the volume metric
|
|
|
|
Vaa *sdk.VAA
|
|
|
|
|
|
|
|
// TokenPriceFunc returns the price of the given token at the specified timestamp.
|
2023-09-22 12:14:02 -07:00
|
|
|
TokenPriceFunc func(tokenID string, timestamp time.Time) (decimal.Decimal, error)
|
2023-05-23 07:27:23 -07:00
|
|
|
|
|
|
|
// Logger is an optional parameter, in case the caller wants additional visibility.
|
|
|
|
Logger *zap.Logger
|
2023-07-03 07:51:20 -07:00
|
|
|
|
|
|
|
// Metrics is in case the caller wants additional visibility.
|
|
|
|
Metrics metrics.Metrics
|
2023-09-25 12:56:05 -07:00
|
|
|
|
|
|
|
// TransferredToken is the token that was transferred in the VAA.
|
|
|
|
TransferredToken *token.TransferredToken
|
2023-11-28 05:16:40 -08:00
|
|
|
|
|
|
|
// TokenProvider is used to obtain token metadata.
|
|
|
|
TokenProvider *domain.TokenProvider
|
2023-05-23 07:27:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// MakePointForVaaVolume builds the InfluxDB volume metric for a given VAA
|
|
|
|
//
|
|
|
|
// Some VAAs will not generate a measurement, so the caller must always check
|
|
|
|
// whether the returned point is nil.
|
|
|
|
func MakePointForVaaVolume(params *MakePointForVaaVolumeParams) (*write.Point, error) {
|
|
|
|
|
|
|
|
// Do not generate this metric for PythNet VAAs
|
|
|
|
if params.Vaa.EmitterChain == sdk.ChainIDPythNet {
|
|
|
|
return nil, nil
|
2023-05-18 07:14:36 -07:00
|
|
|
}
|
|
|
|
|
2023-06-08 09:03:15 -07:00
|
|
|
// Do not generate this metric when the emitter chain is unset
|
|
|
|
if params.Vaa.EmitterChain.String() == sdk.ChainIDUnset.String() {
|
|
|
|
if params.Logger != nil {
|
2023-11-28 05:16:40 -08:00
|
|
|
params.Logger.Warn("Emitter chain is unset",
|
2023-06-08 09:03:15 -07:00
|
|
|
zap.String("vaaId", params.Vaa.MessageID()),
|
|
|
|
zap.Uint16("emitterChain", uint16(params.Vaa.EmitterChain)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2023-09-25 12:56:05 -07:00
|
|
|
// Do not generate this metric when the TransferredToken is undefined
|
|
|
|
if params.TransferredToken == nil {
|
2023-06-08 09:03:15 -07:00
|
|
|
if params.Logger != nil {
|
2023-11-27 07:31:35 -08:00
|
|
|
params.Logger.Warn("Transferred token is undefined",
|
2023-06-08 09:03:15 -07:00
|
|
|
zap.String("vaaId", params.Vaa.MessageID()),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2023-05-29 06:54:09 -07:00
|
|
|
// Create a data point
|
2023-10-02 07:21:13 -07:00
|
|
|
point := influxdb2.NewPointWithMeasurement(VaaVolumeMeasurement).
|
2023-05-29 06:54:09 -07:00
|
|
|
// This is always set to the portal token bridge app ID, but we may have other apps in the future
|
2023-09-25 12:56:05 -07:00
|
|
|
AddTag("app_id", params.TransferredToken.AppId).
|
2023-05-29 06:54:09 -07:00
|
|
|
AddTag("emitter_chain", fmt.Sprintf("%d", params.Vaa.EmitterChain)).
|
|
|
|
// Receiver chain
|
2023-09-25 12:56:05 -07:00
|
|
|
AddTag("destination_chain", fmt.Sprintf("%d", params.TransferredToken.ToChain)).
|
2023-05-29 06:54:09 -07:00
|
|
|
// Original mint address
|
2023-09-25 12:56:05 -07:00
|
|
|
AddTag("token_address", params.TransferredToken.TokenAddress.String()).
|
2023-05-29 06:54:09 -07:00
|
|
|
// Original mint chain
|
2023-09-25 12:56:05 -07:00
|
|
|
AddTag("token_chain", fmt.Sprintf("%d", params.TransferredToken.TokenChain)).
|
2023-10-02 07:21:13 -07:00
|
|
|
// Measurement version
|
|
|
|
AddTag("version", "v2").
|
2023-05-29 06:54:09 -07:00
|
|
|
SetTime(params.Vaa.Timestamp)
|
|
|
|
|
2023-05-04 16:17:03 -07:00
|
|
|
// Get the token metadata
|
|
|
|
//
|
|
|
|
// This is complementary data about the token that is not present in the VAA itself.
|
2023-11-28 05:16:40 -08:00
|
|
|
tokenMeta, ok := params.TokenProvider.GetTokenByAddress(params.TransferredToken.TokenChain, params.TransferredToken.TokenAddress.String())
|
2023-05-04 16:17:03 -07:00
|
|
|
if !ok {
|
2023-09-25 12:56:05 -07:00
|
|
|
params.Metrics.IncMissingToken(params.TransferredToken.TokenChain.String(), params.TransferredToken.TokenAddress.String())
|
2023-05-29 06:54:09 -07:00
|
|
|
// We don't have metadata for this token, so we can't compute the volume-related fields
|
|
|
|
// (i.e.: amount, notional, volume, symbol, etc.)
|
|
|
|
//
|
|
|
|
// InfluxDB will reject data points that don't have any fields, so we need to
|
|
|
|
// add a dummy field.
|
|
|
|
//
|
|
|
|
// Moreover, many flux queries depend on the existence of the `volume` field,
|
|
|
|
// and would break if we had measurements without it.
|
2023-11-28 05:16:40 -08:00
|
|
|
params.Logger.Warn("Cannot obtain this token",
|
|
|
|
zap.String("vaaId", params.Vaa.MessageID()),
|
|
|
|
zap.String("tokenAddress", params.TransferredToken.TokenAddress.String()),
|
|
|
|
zap.Uint16("tokenChain", uint16(params.TransferredToken.TokenChain)),
|
|
|
|
zap.Any("tokenMetadata", tokenMeta),
|
|
|
|
)
|
2023-05-29 06:54:09 -07:00
|
|
|
point.AddField("volume", uint64(0))
|
|
|
|
return point, nil
|
2023-05-04 16:17:03 -07:00
|
|
|
}
|
2023-09-25 12:56:05 -07:00
|
|
|
params.Metrics.IncFoundToken(params.TransferredToken.TokenChain.String(), params.TransferredToken.TokenAddress.String())
|
2023-05-04 16:17:03 -07:00
|
|
|
|
|
|
|
// Normalize the amount to 8 decimals
|
2023-09-25 12:56:05 -07:00
|
|
|
amount := params.TransferredToken.Amount
|
2023-05-04 16:17:03 -07:00
|
|
|
if tokenMeta.Decimals < 8 {
|
|
|
|
|
|
|
|
// factor = 10 ^ (8 - tokenMeta.Decimals)
|
|
|
|
var factor big.Int
|
|
|
|
factor.Exp(big.NewInt(10), big.NewInt(int64(8-tokenMeta.Decimals)), nil)
|
|
|
|
|
|
|
|
amount = amount.Mul(amount, &factor)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to obtain the token notional value from the cache
|
2023-09-22 12:14:02 -07:00
|
|
|
notionalUSD, err := params.TokenPriceFunc(tokenMeta.GetTokenID(), params.Vaa.Timestamp)
|
2023-05-04 16:17:03 -07:00
|
|
|
if err != nil {
|
2023-07-03 07:51:20 -07:00
|
|
|
params.Metrics.IncMissingNotional(tokenMeta.Symbol.String())
|
2023-05-23 07:27:23 -07:00
|
|
|
if params.Logger != nil {
|
2023-11-28 05:16:40 -08:00
|
|
|
params.Logger.Warn("Failed to obtain notional for this token",
|
2023-05-23 07:27:23 -07:00
|
|
|
zap.String("vaaId", params.Vaa.MessageID()),
|
2023-09-25 12:56:05 -07:00
|
|
|
zap.String("tokenAddress", params.TransferredToken.TokenAddress.String()),
|
|
|
|
zap.Uint16("tokenChain", uint16(params.TransferredToken.TokenChain)),
|
2023-05-23 07:27:23 -07:00
|
|
|
zap.Any("tokenMetadata", tokenMeta),
|
|
|
|
zap.Error(err),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return nil, nil
|
2023-05-04 16:17:03 -07:00
|
|
|
}
|
2023-07-03 07:51:20 -07:00
|
|
|
params.Metrics.IncFoundNotional(tokenMeta.Symbol.String())
|
2023-05-04 16:17:03 -07:00
|
|
|
|
|
|
|
// Convert the notional value to an integer with an implicit precision of 8 decimals
|
2023-05-31 12:24:40 -07:00
|
|
|
notionalBigInt := notionalUSD.
|
|
|
|
Truncate(8).
|
|
|
|
Mul(decimal.NewFromInt(1e8)).
|
|
|
|
BigInt()
|
2023-05-04 16:17:03 -07:00
|
|
|
|
|
|
|
// Calculate the volume, with an implicit precision of 8 decimals
|
|
|
|
var volume big.Int
|
|
|
|
volume.Mul(amount, notionalBigInt)
|
|
|
|
volume.Div(&volume, big.NewInt(1e8))
|
|
|
|
|
2023-05-29 06:54:09 -07:00
|
|
|
// Add volume-related fields to the data point.
|
2023-05-04 16:17:03 -07:00
|
|
|
//
|
|
|
|
// We're converting big integers to int64 because influxdb doesn't support bigint/numeric types.
|
2023-05-29 06:54:09 -07:00
|
|
|
point.
|
2023-05-30 07:14:19 -07:00
|
|
|
AddField("symbol", tokenMeta.Symbol.String()).
|
2023-05-08 13:51:18 -07:00
|
|
|
// Amount of tokens transferred, integer, 8 decimals of precision
|
2023-05-10 14:18:32 -07:00
|
|
|
AddField("amount", amount.Uint64()).
|
2023-05-23 07:27:23 -07:00
|
|
|
// Token price at the time the VAA was emitted, integer, 8 decimals of precision
|
2023-05-10 14:18:32 -07:00
|
|
|
AddField("notional", notionalBigInt.Uint64()).
|
2023-05-08 13:51:18 -07:00
|
|
|
// Volume in USD, integer, 8 decimals of precision
|
2023-05-10 14:18:32 -07:00
|
|
|
AddField("volume", volume.Uint64()).
|
2023-05-29 06:54:09 -07:00
|
|
|
SetTime(generateUniqueTimestamp(params.Vaa))
|
2023-05-04 16:17:03 -07:00
|
|
|
|
2023-05-23 07:27:23 -07:00
|
|
|
return point, nil
|
2023-05-04 16:17:03 -07:00
|
|
|
}
|
2023-05-29 06:54:09 -07:00
|
|
|
|
|
|
|
// generateUniqueTimestamp generates a unique timestamp for each VAA.
|
|
|
|
//
|
|
|
|
// Most VAA timestamps only have millisecond resolution, so it is possible that two VAAs
|
|
|
|
// will have the same timestamp.
|
|
|
|
// By the way InfluxDB works, two points with the same timesamp will overwrite each other.
|
|
|
|
//
|
|
|
|
// Hence, we are forced to generate a deterministic unique timestamp for each VAA.
|
|
|
|
func generateUniqueTimestamp(vaa *sdk.VAA) time.Time {
|
|
|
|
|
|
|
|
// We're adding 1 a nanosecond offset per sequence.
|
|
|
|
// Then, we're taking the modulo of 10^6 to ensure that the offset
|
|
|
|
// will always be lower than one millisecond.
|
|
|
|
//
|
|
|
|
// We could also hash the chain, emitter and seq fields,
|
|
|
|
// but the current approach is good enough for the time being.
|
|
|
|
offset := time.Duration(vaa.Sequence % 1_000_000)
|
|
|
|
|
|
|
|
return vaa.Timestamp.Add(time.Nanosecond * offset)
|
|
|
|
}
|