370 lines
12 KiB
Go
370 lines
12 KiB
Go
package protocols
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"github.com/influxdata/influxdb-client-go/v2/api"
|
|
"github.com/influxdata/influxdb-client-go/v2/api/query"
|
|
"github.com/mitchellh/mapstructure"
|
|
"github.com/wormhole-foundation/wormhole-explorer/common/dbconsts"
|
|
"go.uber.org/zap"
|
|
"time"
|
|
)
|
|
|
|
// QueryCoreProtocolTotalStartOfDay Query template for core protocols (cctp and portal_token_bridge) to fetch total values till the start of current day
|
|
const QueryCoreProtocolTotalStartOfDay = `
|
|
import "date"
|
|
|
|
startOfCurrentDay = date.truncate(t: now(), unit: 1d)
|
|
|
|
data = from(bucket: "%s")
|
|
|> range(start: 1970-01-01T00:00:00Z,stop:startOfCurrentDay)
|
|
|> filter(fn: (r) => r._measurement == "%s" and r.app_id == "%s")
|
|
|
|
tvt = data
|
|
|> filter(fn : (r) => r._field == "total_value_transferred")
|
|
|> group()
|
|
|> sum()
|
|
|> set(key:"_field",value:"total_value_transferred")
|
|
|> map(fn: (r) => ({r with _value: int(v: r._value)}))
|
|
|
|
totalMsgs = data
|
|
|> filter(fn : (r) => r._field == "total_messages")
|
|
|> group()
|
|
|> sum()
|
|
|> set(key:"_field",value:"total_messages")
|
|
|> map(fn: (r) => ({r with _value: int(v: r._value)}))
|
|
|
|
union(tables:[tvt,totalMsgs])
|
|
|> set(key:"_time",value:string(v:startOfCurrentDay))
|
|
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|
|
|> set(key:"app_id",value:"%s")
|
|
`
|
|
|
|
// QueryCoreProtocolDeltaSinceStartOfDay calculate delta since the beginning of current day
|
|
const QueryCoreProtocolDeltaSinceStartOfDay = `
|
|
import "date"
|
|
import "types"
|
|
|
|
ts = date.truncate(t: now(), unit: 1h)
|
|
startOfDay = date.truncate(t: now(), unit: 1d)
|
|
|
|
data = from(bucket: "%s")
|
|
|> range(start: startOfDay,stop:ts)
|
|
|> filter(fn: (r) => r._measurement == "%s" and r.app_id == "%s")
|
|
|
|
tvt = data
|
|
|> filter(fn : (r) => r._field == "total_value_transferred")
|
|
|> group()
|
|
|> sum()
|
|
|> set(key:"_field",value:"total_value_transferred")
|
|
|> map(fn: (r) => ({r with _value: int(v: r._value)}))
|
|
|
|
totalMsgs = data
|
|
|> filter(fn : (r) => r._field == "total_messages")
|
|
|> group()
|
|
|> sum()
|
|
|> set(key:"_field",value:"total_messages")
|
|
|> map(fn: (r) => ({r with _value: int(v: r._value)}))
|
|
|
|
union(tables:[tvt,totalMsgs])
|
|
|> set(key:"_time",value:string(v:startOfDay))
|
|
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|
|
|> set(key:"app_id",value:"%s")
|
|
`
|
|
|
|
// QueryCoreProtocolDeltaLastDay calculate last day delta
|
|
const QueryCoreProtocolDeltaLastDay = `
|
|
import "date"
|
|
import "types"
|
|
|
|
ts = date.truncate(t: now(), unit: 1h)
|
|
yesterday = date.sub(d: 1d, from: ts)
|
|
|
|
data = from(bucket: "%s")
|
|
|> range(start: yesterday,stop:ts)
|
|
|> filter(fn: (r) => r._measurement == "%s" and r.app_id == "%s")
|
|
|
|
tvt = data
|
|
|> filter(fn : (r) => r._field == "total_value_transferred")
|
|
|> group()
|
|
|> sum()
|
|
|> set(key:"_field",value:"total_value_transferred")
|
|
|> map(fn: (r) => ({r with _value: int(v: r._value)}))
|
|
|
|
totalMsgs = data
|
|
|> filter(fn : (r) => r._field == "total_messages")
|
|
|> group()
|
|
|> sum()
|
|
|> set(key:"_field",value:"total_messages")
|
|
|> map(fn: (r) => ({r with _value: int(v: r._value)}))
|
|
|
|
union(tables:[tvt,totalMsgs])
|
|
|> set(key:"_time",value:string(v:yesterday))
|
|
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|
|
|> set(key:"app_id",value:"%s")
|
|
`
|
|
|
|
const QueryTemplateProtocolStatsLastDay = `
|
|
from(bucket: "%s")
|
|
|> range(start: %s, stop: %s)
|
|
|> filter(fn: (r) => r._measurement == "%s" and r.protocol == "%s")
|
|
|> first()
|
|
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|
|
`
|
|
|
|
const QueryTemplateProtocolStats = `
|
|
data = from(bucket: "%s")
|
|
|> range(start: -2d)
|
|
|> filter(fn: (r) => r._measurement == "%s" and r.protocol == "%s")
|
|
|
|
totalMsg = data
|
|
|> filter(fn: (r) => r._field == "total_messages")
|
|
|> sort(columns:["_time"],desc:false)
|
|
|> last()
|
|
|
|
tvl = data
|
|
|> filter(fn: (r) => r._field == "total_value_locked")
|
|
|> sort(columns:["_time"],desc:false)
|
|
|> last()
|
|
|
|
volume = data
|
|
|> filter(fn: (r) => r._field == "volume")
|
|
|> sort(columns:["_time"],desc:false)
|
|
|> last()
|
|
|
|
union(tables:[totalMsg,tvl,volume])
|
|
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|
|
`
|
|
|
|
const QueryTemplateProtocolActivity = `
|
|
data =
|
|
from(bucket: "%s")
|
|
|> range(start: %s)
|
|
|> filter(fn: (r) => r._measurement == "%s" and r.protocol == "%s")
|
|
|
|
tvs = data
|
|
|> filter(fn: (r) => r._field == "total_value_secure")
|
|
|> cumulativeSum()
|
|
|> last()
|
|
|
|
tvt = data
|
|
|> filter(fn: (r) => r._field == "total_value_transferred")
|
|
|> cumulativeSum()
|
|
|> last()
|
|
|
|
volume = data
|
|
|> filter(fn: (r) => r._field == "volume")
|
|
|> sort(columns:["_time"],desc:false)
|
|
|> cumulativeSum()
|
|
|> last()
|
|
|
|
txs = data
|
|
|> filter(fn: (r) => r._field == "txs")
|
|
|> sort(columns:["_time"],desc:false)
|
|
|> cumulativeSum()
|
|
|> last()
|
|
|
|
union(tables:[tvs,tvt,volume,txs])
|
|
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|
|
`
|
|
|
|
type Repository struct {
|
|
queryAPI QueryDoer
|
|
logger *zap.Logger
|
|
bucketInfinite string
|
|
bucket30d string
|
|
coreProtocolMeasurement map[string]struct {
|
|
Daily string
|
|
Hourly string
|
|
}
|
|
}
|
|
|
|
type rowStat struct {
|
|
Protocol string `mapstructure:"protocol"`
|
|
TotalMessages uint64 `mapstructure:"total_messages"`
|
|
TotalValueLocked float64 `mapstructure:"total_value_locked"`
|
|
Volume float64 `mapstructure:"volume"`
|
|
Time time.Time `mapstructure:"_time"`
|
|
}
|
|
|
|
type intRowStat struct {
|
|
Protocol string `mapstructure:"app_id"`
|
|
TotalMessages uint64 `mapstructure:"total_messages"`
|
|
TotalValueTransferred uint64 `mapstructure:"total_value_transferred"`
|
|
}
|
|
|
|
type intStats struct {
|
|
Latest intRowStat
|
|
DeltaLast24hr intRowStat
|
|
}
|
|
|
|
type rowActivity struct {
|
|
Protocol string `mapstructure:"protocol"`
|
|
Time time.Time `mapstructure:"_time"`
|
|
TotalUsd float64 `mapstructure:"total_usd"`
|
|
TotalValueTransferred float64 `mapstructure:"total_value_transferred"`
|
|
TotalValueSecure float64 `mapstructure:"total_value_secure"`
|
|
Txs uint64 `mapstructure:"txs"`
|
|
}
|
|
|
|
type stats struct {
|
|
Latest rowStat
|
|
Last24 rowStat
|
|
}
|
|
|
|
type QueryDoer interface {
|
|
Query(ctx context.Context, query string) (QueryResult, error)
|
|
}
|
|
|
|
type queryApiWrapper struct {
|
|
qApi api.QueryAPI
|
|
}
|
|
|
|
type QueryResult interface {
|
|
Next() bool
|
|
Record() *query.FluxRecord
|
|
Err() error
|
|
Close() error
|
|
}
|
|
|
|
func WrapQueryAPI(qApi api.QueryAPI) QueryDoer {
|
|
return &queryApiWrapper{qApi: qApi}
|
|
}
|
|
|
|
func NewRepository(qApi QueryDoer, bucketInfinite, bucket30d string, logger *zap.Logger) *Repository {
|
|
return &Repository{
|
|
queryAPI: qApi,
|
|
bucketInfinite: bucketInfinite,
|
|
bucket30d: bucket30d,
|
|
logger: logger,
|
|
coreProtocolMeasurement: map[string]struct {
|
|
Daily string
|
|
Hourly string
|
|
}{
|
|
CCTP: {Daily: dbconsts.CctpStatsMeasurementDaily, Hourly: dbconsts.CctpStatsMeasurementHourly},
|
|
PortalTokenBridge: {Daily: dbconsts.TokenBridgeStatsMeasurementDaily, Hourly: dbconsts.TokenBridgeStatsMeasurementHourly},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (q *queryApiWrapper) Query(ctx context.Context, query string) (QueryResult, error) {
|
|
return q.qApi.Query(ctx, query)
|
|
}
|
|
|
|
func (r *Repository) getProtocolStats(ctx context.Context, protocol string) (rowStat, error) {
|
|
|
|
q := fmt.Sprintf(QueryTemplateProtocolStats, r.bucket30d, dbconsts.ProtocolsStatsMeasurementHourly, protocol)
|
|
|
|
statsData, err := fetchSingleRecordData[rowStat](r.logger, r.queryAPI, ctx, q, protocol)
|
|
if err != nil {
|
|
r.logger.Error("error fetching latest daily stats", zap.Error(err))
|
|
return rowStat{}, err
|
|
}
|
|
|
|
return rowStat{
|
|
Protocol: protocol,
|
|
TotalMessages: statsData.TotalMessages,
|
|
TotalValueLocked: statsData.TotalValueLocked,
|
|
Volume: statsData.Volume,
|
|
}, nil
|
|
|
|
}
|
|
|
|
func (r *Repository) getProtocolStatsLastDay(ctx context.Context, protocol string) (rowStat, error) {
|
|
|
|
to := time.Now().UTC().Truncate(24 * time.Hour)
|
|
from := to.Add(-24 * time.Hour)
|
|
q := fmt.Sprintf(QueryTemplateProtocolStatsLastDay, r.bucket30d, from.Format(time.RFC3339), to.Format(time.RFC3339), dbconsts.ProtocolsStatsMeasurementHourly, protocol)
|
|
|
|
lastDayData, err := fetchSingleRecordData[rowStat](r.logger, r.queryAPI, ctx, q, protocol)
|
|
if err != nil {
|
|
r.logger.Error("error fetching last day stats", zap.Error(err))
|
|
return rowStat{}, err
|
|
}
|
|
|
|
return lastDayData, nil
|
|
|
|
}
|
|
|
|
func (r *Repository) getProtocolActivity(ctx context.Context, protocol string) (rowActivity, error) {
|
|
|
|
q := fmt.Sprintf(QueryTemplateProtocolActivity, r.bucketInfinite, "1970-01-01T00:00:00Z", dbconsts.ProtocolsActivityMeasurementDaily, protocol)
|
|
activityDaily, err := fetchSingleRecordData[rowActivity](r.logger, r.queryAPI, ctx, q, protocol)
|
|
if err != nil {
|
|
r.logger.Error("error fetching latest daily activity", zap.Error(err))
|
|
return rowActivity{}, err
|
|
}
|
|
|
|
q = fmt.Sprintf(QueryTemplateProtocolActivity, r.bucket30d, activityDaily.Time.Format(time.RFC3339), dbconsts.ProtocolsActivityMeasurementHourly, protocol)
|
|
activityHourly, err := fetchSingleRecordData[rowActivity](r.logger, r.queryAPI, ctx, q, protocol)
|
|
|
|
return rowActivity{
|
|
Protocol: protocol,
|
|
Txs: activityDaily.Txs + activityHourly.Txs,
|
|
TotalUsd: activityDaily.TotalUsd + activityHourly.TotalUsd,
|
|
TotalValueTransferred: activityDaily.TotalValueTransferred + activityHourly.TotalValueTransferred,
|
|
TotalValueSecure: activityDaily.TotalValueSecure + activityHourly.TotalValueSecure,
|
|
}, nil
|
|
}
|
|
|
|
// returns latest and last 24 hr for core protocols (cctp and portal_token_bridge)
|
|
func (r *Repository) getCoreProtocolStats(ctx context.Context, protocol string) (intStats, error) {
|
|
|
|
// calculate total values till the start of current day
|
|
totalTillCurrentDayQuery := fmt.Sprintf(QueryCoreProtocolTotalStartOfDay, r.bucketInfinite, r.coreProtocolMeasurement[protocol].Daily, protocol, protocol)
|
|
totalsUntilToday, err := fetchSingleRecordData[intRowStat](r.logger, r.queryAPI, ctx, totalTillCurrentDayQuery, protocol)
|
|
if err != nil {
|
|
return intStats{}, err
|
|
}
|
|
|
|
// calculate delta since the beginning of current day
|
|
q2 := fmt.Sprintf(QueryCoreProtocolDeltaSinceStartOfDay, r.bucket30d, r.coreProtocolMeasurement[protocol].Hourly, protocol, protocol)
|
|
currentDayStats, errCD := fetchSingleRecordData[intRowStat](r.logger, r.queryAPI, ctx, q2, protocol)
|
|
if errCD != nil {
|
|
return intStats{}, errCD
|
|
}
|
|
|
|
latestTotal := intRowStat{
|
|
Protocol: protocol,
|
|
TotalMessages: totalsUntilToday.TotalMessages + currentDayStats.TotalMessages,
|
|
TotalValueTransferred: totalsUntilToday.TotalValueTransferred + currentDayStats.TotalValueTransferred,
|
|
}
|
|
|
|
result := intStats{
|
|
Latest: latestTotal,
|
|
}
|
|
|
|
// calculate last day delta
|
|
q3 := fmt.Sprintf(QueryCoreProtocolDeltaLastDay, r.bucket30d, r.coreProtocolMeasurement[protocol].Hourly, protocol, protocol)
|
|
deltaYesterdayStats, errQ3 := fetchSingleRecordData[intRowStat](r.logger, r.queryAPI, ctx, q3, protocol)
|
|
if errQ3 != nil {
|
|
return result, errQ3
|
|
}
|
|
|
|
result.DeltaLast24hr = deltaYesterdayStats
|
|
return result, nil
|
|
}
|
|
|
|
func fetchSingleRecordData[T any](logger *zap.Logger, queryAPI QueryDoer, ctx context.Context, query, protocol string) (T, error) {
|
|
var res T
|
|
result, err := queryAPI.Query(ctx, query)
|
|
if err != nil {
|
|
logger.Error("error executing query to fetch data", zap.Error(err), zap.String("protocol", protocol), zap.String("query", query))
|
|
return res, err
|
|
}
|
|
defer result.Close()
|
|
|
|
if !result.Next() {
|
|
if result.Err() != nil {
|
|
logger.Error("error reading query response", zap.Error(result.Err()), zap.String("protocol", protocol), zap.String("query", query))
|
|
return res, result.Err()
|
|
}
|
|
logger.Info("empty query response", zap.String("protocol", protocol), zap.String("query", query))
|
|
return res, err
|
|
}
|
|
|
|
err = mapstructure.Decode(result.Record().Values(), &res)
|
|
return res, err
|
|
}
|